This commit is contained in:
vchikalkin 2024-03-29 00:57:06 +03:00
parent 77f5e9f965
commit 9d6581ad57
11 changed files with 234 additions and 217 deletions

View File

@ -74,8 +74,10 @@ export class ProxyController {
return (Object.keys(queryTTL) as Array<keyof typeof queryTTL>).reduce(
(acc, queryName) => {
const queries = list.filter((x) => x.split(' ').at(0) === queryName);
const ttl = queryTTL[queryName];
acc[queryName] = { queries, ttl };
if (queries.length) {
const ttl = queryTTL[queryName];
acc[queryName] = { queries, ttl };
}
return acc;
},

View File

@ -1,3 +0,0 @@
.collapse {
flex-grow: 1;
}

View File

@ -1,58 +0,0 @@
import style from './Admin.module.css';
import { getQueryValue } from '@/api/cache/query';
import type { ResponseQueries } from '@/api/cache/types';
import type { CollapseProps } from 'antd';
import { Button, Collapse } from 'antd';
import { useState } from 'react';
import styled from 'styled-components';
type IProps = {
readonly data: string;
readonly index: number;
readonly name: string;
readonly onClick: (name: string, key: string) => void;
};
const Wrapper = styled.div`
display: flex;
justify-content: space-between;
`;
export function AdminRow(props: IProps) {
const { data, index, name, onClick } = props;
const defaultItems: CollapseProps['items'] = [
{
children: <p>{data}</p>,
key: index.toString(),
label: data,
},
];
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [queryText, setQueryText] = useState<ResponseQueries>({});
const [items, setItems] = useState<CollapseProps['items']>(defaultItems);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [key, setKey] = useState(data);
const handleClick = () => {
onClick(name, key);
};
const handleChange = async () => {
const query = await getQueryValue(key);
setQueryText(query);
setItems(
[...items].map((obj) => ({
...obj,
children: <p>{JSON.stringify(query)}</p>,
}))
);
};
return (
<Wrapper>
<Collapse className={style.collapse} onChange={handleChange} items={items} />
<Button onClick={handleClick}>Удалить</Button>
</Wrapper>
);
}

View File

@ -1,39 +0,0 @@
import { AdminRow } from './AdminRow';
import type { CollapseProps } from 'antd';
import { Collapse } from 'ui/elements';
export function AdminRows({
index,
name,
onClick,
queries,
}: {
readonly index: number;
readonly name: string;
readonly onClick: (name: string, key: string) => void;
readonly queries: string[];
}) {
if (queries.length === 0) {
const items: CollapseProps['items'] = [
{
children: null,
key: index.toString(),
label: name,
},
];
return <Collapse items={items} />;
}
const rows: CollapseProps['items'] = [
{
children: queries.map((x, ind) => (
<AdminRow key={x} data={x} index={ind} name={name} onClick={onClick} />
)),
key: index.toString(),
label: name,
},
];
return <Collapse items={rows} />;
}

View File

@ -1,69 +0,0 @@
import { AdminRows } from './AdminRows';
import { deleleteQueriesByKey, deleteQuery, getQueries } from '@/api/cache/query';
import type { ResponseQueries } from '@/api/cache/types';
import { useEffect, useState } from 'react';
import type { QueryItem } from 'shared/types/cache';
import styled from 'styled-components';
import { Button } from 'ui/elements';
const Wrapper = styled.div`
display: flex;
justify-content: space-between;
> * {
&:first-child {
flex-grow: 1;
}
}
`;
export function AdminTable() {
const [data, setData] = useState<ResponseQueries>({});
const handleClick = async (name: string, key: string) => {
await deleteQuery(key);
const index = data[name].queries.indexOf(key);
const updatedQuery: QueryItem = { ...data[name] };
if (index >= 0 && updatedQuery.queries.length > 1) {
setData({ ...data, [name]: { ...data[name], queries: data[name].queries.splice(index, 1) } });
} else if (index === 0) {
setData({ ...data, [name]: { ...data[name], queries: [] } });
}
};
const handleClickQueriesGroup = async (name: string) => {
await deleleteQueriesByKey(name);
if (data[name].queries.length > 0) {
const updatedQuery = { ...data[name], queries: [] };
setData({ ...data, [name]: updatedQuery });
}
};
useEffect(() => {
const getRows = async () => {
const queryList = await getQueries();
setData(queryList);
};
getRows();
}, []);
return (
<>
{Object.keys(data).map((key, index) => (
<Wrapper key={key}>
<AdminRows
key={key}
queries={data[key].queries}
name={key}
index={index}
onClick={handleClick}
/>
<Button onClick={() => handleClickQueriesGroup(key)}>Удалить все</Button>
</Wrapper>
))}
</>
);
}

View File

@ -0,0 +1,130 @@
// import { AdminRows } from './AdminRows';
import Background from '../Layout/Background';
import * as cacheApi from '@/api/cache/query';
import { min } from '@/styles/mq';
import { useQuery } from '@tanstack/react-query';
import { memo, useMemo, useState } from 'react';
import type { QueryItem } from 'shared/types/cache';
import styled from 'styled-components';
import { Button, Collapse, Divider } from 'ui/elements';
import { Flex } from 'ui/grid';
type QueryProps = {
readonly handleDeleteQuery: () => void;
readonly queryKey: string;
};
const StyledPre = styled.pre`
max-height: 300px;
overflow-y: auto;
`;
const Query = memo(({ handleDeleteQuery, queryKey }: QueryProps) => {
const { data, refetch } = useQuery({
enabled: false,
queryFn: ({ signal }) => signal && cacheApi.getQueryValue(queryKey, { signal }),
queryKey: ['admin', 'cache', 'query', queryKey],
refetchOnWindowFocus: false,
});
const [activeKey, setActiveKey] = useState<string | undefined>(undefined);
const content = (
<>
<StyledPre>{JSON.stringify(data, null, 2)}</StyledPre>
<Flex justifyContent="flex-end">
<Button type="primary" danger onClick={handleDeleteQuery}>
Удалить
</Button>
</Flex>
</>
);
return (
<Collapse
bordered={false}
activeKey={activeKey}
items={[
{
children: data ? content : 'Загрузка...',
key: queryKey,
label: queryKey,
},
]}
onChange={() => {
if (activeKey) {
setActiveKey(undefined);
} else {
setActiveKey(queryKey);
refetch();
}
}}
/>
);
});
type QueryListProps = QueryItem;
const QueryList = memo(({ queries }: QueryListProps) => {
const [deletedQueries, setDeletedQueries] = useState<QueryItem['queries']>([]);
const activeQueries = useMemo(
() => queries.filter((queryKey) => !deletedQueries.includes(queryKey)),
[deletedQueries, queries]
);
function deleteQuery(queryName: string) {
cacheApi.deleteQuery(queryName).then(() => setDeletedQueries([...deletedQueries, queryName]));
}
return activeQueries.map((queryKey) => (
<Query
key={queryKey}
queryKey={queryKey}
handleDeleteQuery={() => {
deleteQuery(queryKey);
}}
/>
));
});
const Wrapper = styled(Background)`
padding: 4px 6px;
width: 100%;
${min('tablet')} {
min-height: 790px;
}
${min('laptop')} {
padding: 4px 6px 10px;
width: 1024px;
}
`;
export function CacheQueries() {
const { data: queries } = useQuery({
queryFn: ({ signal }) => signal && cacheApi.getQueries({ signal }),
queryKey: ['admin', 'cache', 'queries'],
refetchOnWindowFocus: false,
});
if (!queries) {
return <div>Загрузка...</div>;
}
return (
<Wrapper>
<Divider>Управление кэшем</Divider>
<Collapse
accordion
items={Object.keys(queries).map((queryGroupName) => ({
children: <QueryList {...queries[queryGroupName]} />,
key: queryGroupName,
label: queryGroupName,
}))}
/>
</Wrapper>
);
}

View File

@ -1,3 +0,0 @@
export * from './AdminRow';
export * from './AdminRows';
export * from './AdminTable';

View File

@ -0,0 +1,9 @@
import { CacheQueries } from './Cache';
export function AdminPanel() {
return (
<div>
<CacheQueries />
</div>
);
}

View File

@ -22,3 +22,18 @@ export const Grid = styled(Box)`
margin: 8px 10% !important;
}
`;
export const AdminGrid = styled(Box)`
display: flex;
flex-direction: column;
gap: 10px;
${min('laptop')} {
display: grid;
place-items: center;
}
${min('desktop-xl')} {
margin: 8px 10% !important;
}
`;

View File

@ -11,8 +11,8 @@ const {
URL_CACHE_GET_QUERY_VALUE,
} = getUrls();
export function getQueries() {
return withHandleError(axios.get<ResponseQueries>(URL_CACHE_GET_QUERIES)).then(
export function getQueries({ signal }: { signal: AbortSignal }) {
return withHandleError(axios.get<ResponseQueries>(URL_CACHE_GET_QUERIES, { signal })).then(
({ data }) => data
);
}
@ -31,7 +31,7 @@ export function reset() {
return withHandleError(axios.delete(URL_CACHE_RESET_QUERIES)).then(({ data }) => data);
}
export function deleleteQueriesByKey(queriesGroup: string) {
export function deleteQueriesByKey(queriesGroup: string) {
return withHandleError(
axios.delete(URL_CACHE_DELETE_QUERIES_BY_KEY, {
params: {
@ -41,12 +41,13 @@ export function deleleteQueriesByKey(queriesGroup: string) {
).then(({ data }) => data);
}
export function getQueryValue(queryKey: string) {
export function getQueryValue(queryKey: string, { signal }: { signal: AbortSignal }) {
return withHandleError(
axios.get<ResponseQueries>(URL_CACHE_GET_QUERY_VALUE, {
axios.get<object>(URL_CACHE_GET_QUERY_VALUE, {
params: {
queryKey,
},
signal,
})
).then(({ data }) => data);
}

View File

@ -1,47 +1,23 @@
import { makeGetServerSideProps } from '.';
import { AdminTable } from '@/Components/Admin';
import { getQueries } from '@/api/cache/query';
import { getUser } from '@/api/user/query';
import initializeApollo from '@/apollo/client';
import { AdminPanel } from '@/Components/Admin';
import { Error } from '@/Components/Common/Error';
import Background from '@/Components/Layout/Background';
import { Grid } from '@/Components/Layout/Page';
import { AdminGrid } from '@/Components/Layout/Page';
import { unlimitedRoles } from '@/config/users';
import * as hooks from '@/process/hooks';
import { useStore } from '@/stores/hooks';
import { min } from '@/styles/mq';
import * as CRMTypes from '@/graphql/crm.types';
import { dehydrate, QueryClient } from '@tanstack/react-query';
import Head from 'next/head';
import styled from 'styled-components';
function Content() {
const store = useStore();
store.$process.add('Unlimited');
hooks.useSentryScope();
hooks.useMainData();
hooks.useGetUsers();
hooks.useInsuranceData();
hooks.useReactions();
const Wrapper = styled(Background)`
padding: 4px 6px;
${min('tablet')} {
min-height: 790px;
}
${min('laptop')} {
padding: 4px 6px 10px;
}
`;
return (
<Grid>
<AdminGrid>
<Head>
<title>Админка</title>
<title>Панель управления</title>
<meta name="description" content="Админка" />
</Head>
<Wrapper>
<AdminTable />
</Wrapper>
</Grid>
<AdminPanel />
</AdminGrid>
);
}
@ -51,6 +27,62 @@ export default function Admin(props) {
return <Content />;
}
export const getServerSideProps = makeGetServerSideProps({
roles: unlimitedRoles,
});
/** @type {import('next').GetServerSideProps} */
export async function getServerSideProps({ req }) {
const { cookie = '' } = req.headers;
const queryClient = new QueryClient();
const user = await queryClient.fetchQuery(['user'], ({ signal }) =>
getUser({
headers: {
cookie,
},
signal,
})
);
const apolloClient = initializeApollo();
try {
const {
data: { systemuser },
} = await apolloClient.query({
fetchPolicy: 'network-only',
query: CRMTypes.GetSystemUserDocument,
variables: {
domainname: user.domainName,
},
});
if (!systemuser?.roles?.some((x) => x?.name && unlimitedRoles.includes(x.name))) {
return {
props: {
initialQueryState: dehydrate(queryClient),
statusCode: 403,
},
};
}
await queryClient.prefetchQuery(['admin', 'cache', 'queries'], ({ signal }) =>
getQueries({ signal })
);
return {
props: {
calculation: {},
initialApolloState: apolloClient.cache.extract(),
initialQueryState: dehydrate(queryClient),
statusCode: 200,
},
};
} catch (error) {
return {
props: {
error: JSON.stringify(error),
initialQueryState: dehydrate(queryClient),
statusCode: 500,
},
};
}
}