ui(admin): add search and sort to ServersContainer
This commit is contained in:
parent
ae88a01bea
commit
bca2338863
16 changed files with 1097 additions and 1381 deletions
|
@ -48,7 +48,7 @@ class ServerController extends ApplicationApiController
|
|||
|
||||
$servers = QueryBuilder::for(Server::query())
|
||||
->allowedFilters(['uuid', 'name', 'image', 'external_id'])
|
||||
->allowedSorts(['id', 'uuid'])
|
||||
->allowedSorts(['id', 'uuid', 'owner_id', 'node_id', 'status'])
|
||||
->paginate($perPage);
|
||||
|
||||
return $this->fractal->collection($servers)
|
||||
|
@ -72,7 +72,7 @@ class ServerController extends ApplicationApiController
|
|||
|
||||
return $this->fractal->item($server)
|
||||
->transformWith($this->getTransformer(ServerTransformer::class))
|
||||
->respond(201);
|
||||
->respond(Response::HTTP_CREATED);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -125,7 +125,9 @@ class ServerTransferController extends Controller
|
|||
$server = $this->connection->transaction(function () use ($server, $transfer) {
|
||||
$allocations = [$transfer->old_allocation];
|
||||
if (!empty($transfer->old_additional_allocations)) {
|
||||
array_push($allocations, $transfer->old_additional_allocations);
|
||||
foreach ($transfer->old_additional_allocations as $allocation) {
|
||||
$allocations[] = $allocation;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the old allocations for the server and re-assign the server to the new
|
||||
|
@ -169,7 +171,9 @@ class ServerTransferController extends Controller
|
|||
|
||||
$allocations = [$transfer->new_allocation];
|
||||
if (!empty($transfer->new_additional_allocations)) {
|
||||
array_push($allocations, $transfer->new_additional_allocations);
|
||||
foreach ($transfer->new_additional_allocations as $allocation) {
|
||||
$allocations[] = $allocation;
|
||||
}
|
||||
}
|
||||
|
||||
Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]);
|
||||
|
|
|
@ -42,18 +42,61 @@ export const rawDataToServer = ({ attributes }: FractalResponseData): Server =>
|
|||
},
|
||||
});
|
||||
|
||||
export interface Filters {
|
||||
uuid?: string;
|
||||
name?: string;
|
||||
image?: string;
|
||||
/* eslint-disable camelcase */
|
||||
external_id?: string;
|
||||
/* eslint-enable camelcase */
|
||||
}
|
||||
|
||||
interface ctx {
|
||||
page: number;
|
||||
setPage: (value: number | ((s: number) => number)) => void;
|
||||
|
||||
filters: Filters | null;
|
||||
setFilters: (filters: Filters | null) => void;
|
||||
|
||||
sort: string | null;
|
||||
setSort: (sort: string | null) => void;
|
||||
|
||||
sortDirection: boolean;
|
||||
setSortDirection: (direction: boolean) => void;
|
||||
}
|
||||
|
||||
export const Context = createContext<ctx>({ page: 1, setPage: () => 1 });
|
||||
export const Context = createContext<ctx>({
|
||||
page: 1,
|
||||
setPage: () => 1,
|
||||
|
||||
filters: null,
|
||||
setFilters: () => null,
|
||||
|
||||
sort: null,
|
||||
setSort: () => null,
|
||||
|
||||
sortDirection: false,
|
||||
setSortDirection: () => false,
|
||||
});
|
||||
|
||||
export default (include: string[] = []) => {
|
||||
const { page } = useContext(Context);
|
||||
const { page, filters, sort, sortDirection } = useContext(Context);
|
||||
|
||||
return useSWR<PaginatedResult<Server>>([ 'servers', page ], async () => {
|
||||
const { data } = await http.get('/api/application/servers', { params: { include: include.join(','), page } });
|
||||
const params = {};
|
||||
if (filters !== null) {
|
||||
Object.keys(filters).forEach(key => {
|
||||
// @ts-ignore
|
||||
params['filter[' + key + ']'] = filters[key];
|
||||
});
|
||||
}
|
||||
|
||||
if (sort !== null) {
|
||||
// @ts-ignore
|
||||
params.sort = (sortDirection ? '-' : '') + sort;
|
||||
}
|
||||
|
||||
return useSWR<PaginatedResult<Server>>([ 'servers', page, filters, sort, sortDirection ], async () => {
|
||||
const { data } = await http.get('/api/application/servers', { params: { include: include.join(','), page, ...params } });
|
||||
|
||||
return ({
|
||||
items: (data.data || []).map(rawDataToServer),
|
||||
|
|
|
@ -94,7 +94,7 @@ export interface PaginatedResult<T> {
|
|||
pagination: PaginationDataSet;
|
||||
}
|
||||
|
||||
interface PaginationDataSet {
|
||||
export interface PaginationDataSet {
|
||||
total: number;
|
||||
count: number;
|
||||
perPage: number;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, Suspense } from 'react';
|
||||
import React, { lazy, useEffect, Suspense } from 'react';
|
||||
import ReactGA from 'react-ga';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { Route, Router, Switch, useLocation } from 'react-router-dom';
|
||||
|
@ -15,9 +15,8 @@ import GlobalStylesheet from '@/assets/css/GlobalStylesheet';
|
|||
import { history } from '@/components/history';
|
||||
import { setupInterceptors } from '@/api/interceptors';
|
||||
import TailwindGlobalStyles from '@/components/GlobalStyles';
|
||||
import AdminRouter from '@/routers/AdminRouter';
|
||||
|
||||
// const ChunkedAdminRouter = lazy(() => import(/* webpackChunkName: "admin" */'@/routers/AdminRouter'));
|
||||
const ChunkedAdminRouter = lazy(() => import(/* webpackChunkName: "admin" */'@/routers/AdminRouter'));
|
||||
|
||||
interface ExtendedWindow extends Window {
|
||||
SiteConfiguration?: SiteSettings;
|
||||
|
@ -94,7 +93,7 @@ const App = () => {
|
|||
<Switch>
|
||||
<Route path="/server/:id" component={ServerRouter}/>
|
||||
<Route path="/auth" component={AuthenticationRouter}/>
|
||||
<Route path="/admin" component={AdminRouter}/>
|
||||
<Route path="/admin" component={ChunkedAdminRouter}/>
|
||||
<Route path="/" component={DashboardRouter}/>
|
||||
<Route path={'*'} component={NotFound}/>
|
||||
</Switch>
|
||||
|
|
|
@ -1,26 +1,33 @@
|
|||
import React from 'react';
|
||||
import Input from '@/components/elements/Input';
|
||||
import InputSpinner from '@/components/elements/InputSpinner';
|
||||
import { debounce } from 'debounce';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { TableCheckbox } from '@/components/admin/AdminCheckbox';
|
||||
import Spinner from '@/components/elements/Spinner';
|
||||
import styled from 'styled-components/macro';
|
||||
import tw from 'twin.macro';
|
||||
import { PaginatedResult } from '@/api/http';
|
||||
import { PaginatedResult, PaginationDataSet } from '@/api/http';
|
||||
|
||||
export const TableHeader = ({ name }: { name?: string }) => {
|
||||
export const TableHeader = ({ name, onClick, direction }: { name?: string, onClick?: (e: React.MouseEvent) => void, direction?: number | null }) => {
|
||||
if (!name) {
|
||||
return <th css={tw`px-6 py-2`}/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<th css={tw`px-6 py-2`}>
|
||||
<th css={tw`px-6 py-2`} onClick={onClick}>
|
||||
<span css={tw`flex flex-row items-center cursor-pointer`}>
|
||||
<span css={tw`text-xs font-medium tracking-wider uppercase text-neutral-300 whitespace-nowrap`}>{name}</span>
|
||||
<span css={tw`text-xs font-medium tracking-wider uppercase text-neutral-300 whitespace-nowrap select-none`}>{name}</span>
|
||||
|
||||
<div css={tw`ml-1`}>
|
||||
<svg fill="none" viewBox="0 0 20 20" css={tw`w-4 h-4 text-neutral-400`}>
|
||||
<path stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" d="M13 7L10 4L7 7"/>
|
||||
<path stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" d="M7 13L10 16L13 13"/>
|
||||
</svg>
|
||||
</div>
|
||||
{direction !== undefined ?
|
||||
<div css={tw`ml-1`}>
|
||||
<svg fill="none" viewBox="0 0 20 20" css={tw`w-4 h-4 text-neutral-400`}>
|
||||
{(direction === null || direction === 1) ? <path stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" d="M13 7L10 4L7 7"/> : null}
|
||||
{(direction === null || direction === 2) ? <path stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" d="M7 13L10 16L13 13"/> : null}
|
||||
</svg>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
</span>
|
||||
</th>
|
||||
);
|
||||
|
@ -54,7 +61,7 @@ export const TableRow = ({ children }: { children: React.ReactNode }) => {
|
|||
};
|
||||
|
||||
interface Props<T> {
|
||||
data: PaginatedResult<T>;
|
||||
data?: PaginatedResult<T>;
|
||||
onPageSelect: (page: number) => void;
|
||||
|
||||
children: React.ReactNode;
|
||||
|
@ -78,7 +85,20 @@ const PaginationArrow = styled.button`
|
|||
}
|
||||
`;
|
||||
|
||||
export function Pagination<T> ({ data: { pagination }, onPageSelect, children }: Props<T>) {
|
||||
export function Pagination<T> ({ data, onPageSelect, children }: Props<T>) {
|
||||
let pagination: PaginationDataSet;
|
||||
if (data === undefined) {
|
||||
pagination = {
|
||||
total: 0,
|
||||
count: 0,
|
||||
perPage: 0,
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
};
|
||||
} else {
|
||||
pagination = data.pagination;
|
||||
}
|
||||
|
||||
const setPage = (page: number) => {
|
||||
if (page < 1 || page > pagination.totalPages) {
|
||||
return;
|
||||
|
@ -173,11 +193,27 @@ export const NoItems = () => {
|
|||
interface Params {
|
||||
checked: boolean;
|
||||
onSelectAllClick: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onSearch?: (query: string) => Promise<void>;
|
||||
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ContentWrapper = ({ checked, onSelectAllClick, children }: Params) => {
|
||||
export const ContentWrapper = ({ checked, onSelectAllClick, onSearch, children }: Params) => {
|
||||
const [ loading, setLoading ] = useState(false);
|
||||
const [ inputText, setInputText ] = useState('');
|
||||
|
||||
const search = useCallback(
|
||||
debounce((query: string) => {
|
||||
if (onSearch === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
onSearch(query).then(() => setLoading(false));
|
||||
}, 200),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div css={tw`flex flex-row items-center h-12 px-6`}>
|
||||
|
@ -194,15 +230,19 @@ export const ContentWrapper = ({ checked, onSelectAllClick, children }: Params)
|
|||
</svg>
|
||||
</div>
|
||||
|
||||
{/* <div css={tw`flex flex-row items-center px-2 py-1 ml-auto rounded cursor-pointer bg-neutral-600`}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" css={tw`w-6 h-6 text-neutral-300`}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"/>
|
||||
</svg>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" css={tw`w-4 h-4 ml-1 text-neutral-200`}>
|
||||
<path clipRule="evenodd" fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"/>
|
||||
</svg>
|
||||
</div> */}
|
||||
<div css={tw`flex flex-row items-center ml-auto`}>
|
||||
<InputSpinner visible={loading}>
|
||||
<Input
|
||||
value={inputText}
|
||||
css={tw`h-8`}
|
||||
placeholder="Search..."
|
||||
onChange={e => {
|
||||
setInputText(e.currentTarget.value);
|
||||
search(e.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
</InputSpinner>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{children}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import CopyOnClick from '@/components/elements/CopyOnClick';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import getDatabases, { Context as DatabasesContext } from '@/api/admin/databases/getDatabases';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
|
@ -10,6 +9,7 @@ import AdminContentBlock from '@/components/admin/AdminContentBlock';
|
|||
import AdminCheckbox from '@/components/admin/AdminCheckbox';
|
||||
import AdminTable, { TableBody, TableHead, TableHeader, TableRow, Pagination, Loading, NoItems, ContentWrapper } from '@/components/admin/AdminTable';
|
||||
import Button from '@/components/elements/Button';
|
||||
import CopyOnClick from '@/components/elements/CopyOnClick';
|
||||
|
||||
const RowCheckbox = ({ id }: { id: number}) => {
|
||||
const isChecked = AdminContext.useStoreState(state => state.databases.selectedDatabases.indexOf(id) >= 0);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import LocationDeleteButton from '@/components/admin/locations/LocationDeleteButton';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import tw from 'twin.macro';
|
||||
import { useHistory, useRouteMatch } from 'react-router-dom';
|
||||
import { useRouteMatch } from 'react-router-dom';
|
||||
import { action, Action, Actions, createContextStore, useStoreActions } from 'easy-peasy';
|
||||
import { Location } from '@/api/admin/locations/getLocations';
|
||||
import getLocation from '@/api/admin/locations/getLocation';
|
||||
|
@ -15,7 +17,6 @@ import Field from '@/components/elements/Field';
|
|||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||
import { Form, Formik, FormikHelpers } from 'formik';
|
||||
import updateLocation from '@/api/admin/locations/updateLocation';
|
||||
import LocationDeleteButton from '@/components/admin/locations/LocationDeleteButton';
|
||||
|
||||
interface ctx {
|
||||
location: Location | undefined;
|
||||
|
@ -99,12 +100,12 @@ const EditInformationContainer = () => {
|
|||
</div>
|
||||
|
||||
<div css={tw`w-full flex flex-row items-center mt-6`}>
|
||||
<div css={tw`flex`}>
|
||||
<LocationDeleteButton
|
||||
locationId={location.id}
|
||||
onDeleted={() => history.push('/admin/locations')}
|
||||
/>
|
||||
</div>
|
||||
<div css={tw`flex`}>
|
||||
<LocationDeleteButton
|
||||
locationId={location.id}
|
||||
onDeleted={() => history.push('/admin/locations')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div css={tw`flex ml-auto`}>
|
||||
<Button type={'submit'} disabled={isSubmitting || !isValid}>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import tw from 'twin.macro';
|
||||
import { useHistory, useRouteMatch } from 'react-router-dom';
|
||||
import { useRouteMatch } from 'react-router-dom';
|
||||
import { action, Action, Actions, createContextStore, useStoreActions } from 'easy-peasy';
|
||||
import { Mount } from '@/api/admin/mounts/getMounts';
|
||||
import getMount from '@/api/admin/mounts/getMount';
|
||||
|
@ -15,7 +15,6 @@ import Button from '@/components/elements/Button';
|
|||
import Field from '@/components/elements/Field';
|
||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||
import { Field as FormikField, Form, Formik, FormikHelpers } from 'formik';
|
||||
import MountDeleteButton from '@/components/admin/mounts/MountDeleteButton';
|
||||
import Label from '@/components/elements/Label';
|
||||
|
||||
interface ctx {
|
||||
|
@ -41,8 +40,6 @@ interface Values {
|
|||
}
|
||||
|
||||
const EditInformationContainer = () => {
|
||||
const history = useHistory();
|
||||
|
||||
const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||
|
||||
const mount = Context.useStoreState(state => state.mount);
|
||||
|
@ -184,10 +181,10 @@ const EditInformationContainer = () => {
|
|||
|
||||
<div css={tw`w-full flex flex-row items-center mt-6`}>
|
||||
<div css={tw`flex`}>
|
||||
<MountDeleteButton
|
||||
mountId={mount.id}
|
||||
onDeleted={() => history.push('/admin/mounts')}
|
||||
/>
|
||||
{/* <MountDeleteButton */}
|
||||
{/* mountId={mount.id} */}
|
||||
{/* onDeleted={() => history.push('/admin/mounts')} */}
|
||||
{/* /> */}
|
||||
</div>
|
||||
|
||||
<div css={tw`flex ml-auto`}>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { NavLink, useHistory, useRouteMatch } from 'react-router-dom';
|
||||
import { NavLink, useRouteMatch } from 'react-router-dom';
|
||||
import tw from 'twin.macro';
|
||||
import AdminContentBlock from '@/components/admin/AdminContentBlock';
|
||||
import Spinner from '@/components/elements/Spinner';
|
||||
|
@ -20,7 +20,6 @@ import AdminTable, { ContentWrapper, NoItems, TableBody, TableHead, TableHeader,
|
|||
import CopyOnClick from '@/components/elements/CopyOnClick';
|
||||
import Input from '@/components/elements/Input';
|
||||
import Label from '@/components/elements/Label';
|
||||
import NestDeleteButton from '@/components/admin/nests/NestDeleteButton';
|
||||
|
||||
interface ctx {
|
||||
nest: Nest | undefined;
|
||||
|
@ -61,8 +60,6 @@ interface Values {
|
|||
}
|
||||
|
||||
const EditInformationContainer = () => {
|
||||
const history = useHistory();
|
||||
|
||||
const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||
|
||||
const nest = Context.useStoreState(state => state.nest);
|
||||
|
@ -125,10 +122,10 @@ const EditInformationContainer = () => {
|
|||
|
||||
<div css={tw`w-full flex flex-row items-center mt-6`}>
|
||||
<div css={tw`flex`}>
|
||||
<NestDeleteButton
|
||||
nestId={nest.id}
|
||||
onDeleted={() => history.push('/admin/nests')}
|
||||
/>
|
||||
{/* <NestDeleteButton */}
|
||||
{/* nestId={nest.id} */}
|
||||
{/* onDeleted={() => history.push('/admin/nests')} */}
|
||||
{/* /> */}
|
||||
</div>
|
||||
|
||||
<div css={tw`flex ml-auto`}>
|
||||
|
|
|
@ -98,7 +98,7 @@ export default () => {
|
|||
<TableBody>
|
||||
{
|
||||
roles.map(role => (
|
||||
<TableRow key={role.id}>
|
||||
<TableRow key={role.id} css={role.id === roles[roles.length - 1].id ? tw`rounded-b-lg` : undefined}>
|
||||
<td css={tw`pl-6`}>
|
||||
<RowCheckbox id={role.id}/>
|
||||
</td>
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import tw from 'twin.macro';
|
||||
import { useRouteMatch } from 'react-router-dom';
|
||||
import { action, Action, Actions, createContextStore, useStoreActions } from 'easy-peasy';
|
||||
import { Server } from '@/api/admin/servers/getServers';
|
||||
import getServer from '@/api/admin/servers/getServer';
|
||||
import AdminContentBlock from '@/components/admin/AdminContentBlock';
|
||||
import Spinner from '@/components/elements/Spinner';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import { ApplicationStore } from '@/state';
|
||||
|
||||
interface ctx {
|
||||
server: Server | undefined;
|
||||
setServer: Action<ctx, Server | undefined>;
|
||||
}
|
||||
|
||||
export const Context = createContextStore<ctx>({
|
||||
server: undefined,
|
||||
|
||||
setServer: action((state, payload) => {
|
||||
state.server = payload;
|
||||
}),
|
||||
});
|
||||
|
||||
const ServerEditContainer = () => {
|
||||
const match = useRouteMatch<{ id?: string }>();
|
||||
|
||||
const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||
const [ loading, setLoading ] = useState(true);
|
||||
|
||||
const server = Context.useStoreState(state => state.server);
|
||||
const setServer = Context.useStoreActions(actions => actions.setServer);
|
||||
|
||||
useEffect(() => {
|
||||
clearFlashes('server');
|
||||
|
||||
getServer(Number(match.params?.id), [])
|
||||
.then(server => setServer(server))
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
clearAndAddHttpError({ key: 'server', error });
|
||||
})
|
||||
.then(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
if (loading || server === undefined) {
|
||||
return (
|
||||
<AdminContentBlock>
|
||||
<FlashMessageRender byKey={'server'} css={tw`mb-4`}/>
|
||||
|
||||
<div css={tw`w-full flex flex-col items-center justify-center`} style={{ height: '24rem' }}>
|
||||
<Spinner size={'base'}/>
|
||||
</div>
|
||||
</AdminContentBlock>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AdminContentBlock title={'Server - ' + server.name}>
|
||||
<div css={tw`w-full flex flex-row items-center mb-8`}>
|
||||
<div css={tw`flex flex-col flex-shrink`} style={{ minWidth: '0' }}>
|
||||
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>{server.name}</h2>
|
||||
{
|
||||
(server.description || '').length < 1 ?
|
||||
<p css={tw`text-base text-neutral-400`}>
|
||||
<span css={tw`italic`}>No description</span>
|
||||
</p>
|
||||
:
|
||||
<p css={tw`text-base text-neutral-400 whitespace-nowrap overflow-ellipsis overflow-hidden`}>{server.description}</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FlashMessageRender byKey={'server'} css={tw`mb-4`}/>
|
||||
</AdminContentBlock>
|
||||
);
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<Context.Provider>
|
||||
<ServerEditContainer/>
|
||||
</Context.Provider>
|
||||
);
|
||||
};
|
|
@ -1,3 +1,5 @@
|
|||
import ServerRouter
|
||||
from '@/components/admin/servers/ServerRouter';
|
||||
import React, { useState } from 'react';
|
||||
import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
import { State, useStoreState } from 'easy-peasy';
|
||||
|
@ -20,7 +22,6 @@ import LocationsContainer from '@/components/admin/locations/LocationsContainer'
|
|||
import LocationEditContainer from '@/components/admin/locations/LocationEditContainer';
|
||||
import ServersContainer from '@/components/admin/servers/ServersContainer';
|
||||
import NewServerContainer from '@/components/admin/servers/NewServerContainer';
|
||||
import ServerEditContainer from '@/components/admin/servers/ServerEditContainer';
|
||||
import UsersContainer from '@/components/admin/users/UsersContainer';
|
||||
import NewUserContainer from '@/components/admin/users/NewUserContainer';
|
||||
import UserEditContainer from '@/components/admin/users/UserEditContainer';
|
||||
|
@ -233,7 +234,7 @@ const AdminRouter = ({ location, match }: RouteComponentProps) => {
|
|||
<Route path={`${match.path}/servers/new`} component={NewServerContainer} exact/>
|
||||
<Route
|
||||
path={`${match.path}/servers/:id`}
|
||||
component={ServerEditContainer}
|
||||
component={ServerRouter}
|
||||
/>
|
||||
|
||||
<Route path={`${match.path}/users`} component={UsersContainer} exact/>
|
||||
|
|
Loading…
Reference in a new issue