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())
|
$servers = QueryBuilder::for(Server::query())
|
||||||
->allowedFilters(['uuid', 'name', 'image', 'external_id'])
|
->allowedFilters(['uuid', 'name', 'image', 'external_id'])
|
||||||
->allowedSorts(['id', 'uuid'])
|
->allowedSorts(['id', 'uuid', 'owner_id', 'node_id', 'status'])
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
|
|
||||||
return $this->fractal->collection($servers)
|
return $this->fractal->collection($servers)
|
||||||
|
@ -72,7 +72,7 @@ class ServerController extends ApplicationApiController
|
||||||
|
|
||||||
return $this->fractal->item($server)
|
return $this->fractal->item($server)
|
||||||
->transformWith($this->getTransformer(ServerTransformer::class))
|
->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) {
|
$server = $this->connection->transaction(function () use ($server, $transfer) {
|
||||||
$allocations = [$transfer->old_allocation];
|
$allocations = [$transfer->old_allocation];
|
||||||
if (!empty($transfer->old_additional_allocations)) {
|
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
|
// 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];
|
$allocations = [$transfer->new_allocation];
|
||||||
if (!empty($transfer->new_additional_allocations)) {
|
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]);
|
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 {
|
interface ctx {
|
||||||
page: number;
|
page: number;
|
||||||
setPage: (value: number | ((s: number) => number)) => void;
|
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[] = []) => {
|
export default (include: string[] = []) => {
|
||||||
const { page } = useContext(Context);
|
const { page, filters, sort, sortDirection } = useContext(Context);
|
||||||
|
|
||||||
return useSWR<PaginatedResult<Server>>([ 'servers', page ], async () => {
|
const params = {};
|
||||||
const { data } = await http.get('/api/application/servers', { params: { include: include.join(','), page } });
|
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 ({
|
return ({
|
||||||
items: (data.data || []).map(rawDataToServer),
|
items: (data.data || []).map(rawDataToServer),
|
||||||
|
|
|
@ -94,7 +94,7 @@ export interface PaginatedResult<T> {
|
||||||
pagination: PaginationDataSet;
|
pagination: PaginationDataSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PaginationDataSet {
|
export interface PaginationDataSet {
|
||||||
total: number;
|
total: number;
|
||||||
count: number;
|
count: number;
|
||||||
perPage: 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 ReactGA from 'react-ga';
|
||||||
import { hot } from 'react-hot-loader/root';
|
import { hot } from 'react-hot-loader/root';
|
||||||
import { Route, Router, Switch, useLocation } from 'react-router-dom';
|
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 { history } from '@/components/history';
|
||||||
import { setupInterceptors } from '@/api/interceptors';
|
import { setupInterceptors } from '@/api/interceptors';
|
||||||
import TailwindGlobalStyles from '@/components/GlobalStyles';
|
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 {
|
interface ExtendedWindow extends Window {
|
||||||
SiteConfiguration?: SiteSettings;
|
SiteConfiguration?: SiteSettings;
|
||||||
|
@ -94,7 +93,7 @@ const App = () => {
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/server/:id" component={ServerRouter}/>
|
<Route path="/server/:id" component={ServerRouter}/>
|
||||||
<Route path="/auth" component={AuthenticationRouter}/>
|
<Route path="/auth" component={AuthenticationRouter}/>
|
||||||
<Route path="/admin" component={AdminRouter}/>
|
<Route path="/admin" component={ChunkedAdminRouter}/>
|
||||||
<Route path="/" component={DashboardRouter}/>
|
<Route path="/" component={DashboardRouter}/>
|
||||||
<Route path={'*'} component={NotFound}/>
|
<Route path={'*'} component={NotFound}/>
|
||||||
</Switch>
|
</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 { TableCheckbox } from '@/components/admin/AdminCheckbox';
|
||||||
import Spinner from '@/components/elements/Spinner';
|
import Spinner from '@/components/elements/Spinner';
|
||||||
import styled from 'styled-components/macro';
|
import styled from 'styled-components/macro';
|
||||||
import tw from 'twin.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) {
|
if (!name) {
|
||||||
return <th css={tw`px-6 py-2`}/>;
|
return <th css={tw`px-6 py-2`}/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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`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`}>
|
{direction !== undefined ?
|
||||||
<svg fill="none" viewBox="0 0 20 20" css={tw`w-4 h-4 text-neutral-400`}>
|
<div css={tw`ml-1`}>
|
||||||
<path stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" d="M13 7L10 4L7 7"/>
|
<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="M7 13L10 16L13 13"/>
|
{(direction === null || direction === 1) ? <path stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" d="M13 7L10 4L7 7"/> : null}
|
||||||
</svg>
|
{(direction === null || direction === 2) ? <path stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" d="M7 13L10 16L13 13"/> : null}
|
||||||
</div>
|
</svg>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
);
|
);
|
||||||
|
@ -54,7 +61,7 @@ export const TableRow = ({ children }: { children: React.ReactNode }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props<T> {
|
interface Props<T> {
|
||||||
data: PaginatedResult<T>;
|
data?: PaginatedResult<T>;
|
||||||
onPageSelect: (page: number) => void;
|
onPageSelect: (page: number) => void;
|
||||||
|
|
||||||
children: React.ReactNode;
|
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) => {
|
const setPage = (page: number) => {
|
||||||
if (page < 1 || page > pagination.totalPages) {
|
if (page < 1 || page > pagination.totalPages) {
|
||||||
return;
|
return;
|
||||||
|
@ -173,11 +193,27 @@ export const NoItems = () => {
|
||||||
interface Params {
|
interface Params {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
onSelectAllClick: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
onSelectAllClick: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
onSearch?: (query: string) => Promise<void>;
|
||||||
|
|
||||||
children: React.ReactNode;
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div css={tw`flex flex-row items-center h-12 px-6`}>
|
<div css={tw`flex flex-row items-center h-12 px-6`}>
|
||||||
|
@ -194,15 +230,19 @@ export const ContentWrapper = ({ checked, onSelectAllClick, children }: Params)
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* <div css={tw`flex flex-row items-center px-2 py-1 ml-auto rounded cursor-pointer bg-neutral-600`}>
|
<div css={tw`flex flex-row items-center ml-auto`}>
|
||||||
<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`}>
|
<InputSpinner visible={loading}>
|
||||||
<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"/>
|
<Input
|
||||||
</svg>
|
value={inputText}
|
||||||
|
css={tw`h-8`}
|
||||||
<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`}>
|
placeholder="Search..."
|
||||||
<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"/>
|
onChange={e => {
|
||||||
</svg>
|
setInputText(e.currentTarget.value);
|
||||||
</div> */}
|
search(e.currentTarget.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</InputSpinner>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import CopyOnClick from '@/components/elements/CopyOnClick';
|
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import getDatabases, { Context as DatabasesContext } from '@/api/admin/databases/getDatabases';
|
import getDatabases, { Context as DatabasesContext } from '@/api/admin/databases/getDatabases';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
|
@ -10,6 +9,7 @@ import AdminContentBlock from '@/components/admin/AdminContentBlock';
|
||||||
import AdminCheckbox from '@/components/admin/AdminCheckbox';
|
import AdminCheckbox from '@/components/admin/AdminCheckbox';
|
||||||
import AdminTable, { TableBody, TableHead, TableHeader, TableRow, Pagination, Loading, NoItems, ContentWrapper } from '@/components/admin/AdminTable';
|
import AdminTable, { TableBody, TableHead, TableHeader, TableRow, Pagination, Loading, NoItems, ContentWrapper } from '@/components/admin/AdminTable';
|
||||||
import Button from '@/components/elements/Button';
|
import Button from '@/components/elements/Button';
|
||||||
|
import CopyOnClick from '@/components/elements/CopyOnClick';
|
||||||
|
|
||||||
const RowCheckbox = ({ id }: { id: number}) => {
|
const RowCheckbox = ({ id }: { id: number}) => {
|
||||||
const isChecked = AdminContext.useStoreState(state => state.databases.selectedDatabases.indexOf(id) >= 0);
|
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 React, { useEffect, useState } from 'react';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
import tw from 'twin.macro';
|
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 { action, Action, Actions, createContextStore, useStoreActions } from 'easy-peasy';
|
||||||
import { Location } from '@/api/admin/locations/getLocations';
|
import { Location } from '@/api/admin/locations/getLocations';
|
||||||
import getLocation from '@/api/admin/locations/getLocation';
|
import getLocation from '@/api/admin/locations/getLocation';
|
||||||
|
@ -15,7 +17,6 @@ import Field from '@/components/elements/Field';
|
||||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
import { Form, Formik, FormikHelpers } from 'formik';
|
import { Form, Formik, FormikHelpers } from 'formik';
|
||||||
import updateLocation from '@/api/admin/locations/updateLocation';
|
import updateLocation from '@/api/admin/locations/updateLocation';
|
||||||
import LocationDeleteButton from '@/components/admin/locations/LocationDeleteButton';
|
|
||||||
|
|
||||||
interface ctx {
|
interface ctx {
|
||||||
location: Location | undefined;
|
location: Location | undefined;
|
||||||
|
@ -99,12 +100,12 @@ const EditInformationContainer = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div css={tw`w-full flex flex-row items-center mt-6`}>
|
<div css={tw`w-full flex flex-row items-center mt-6`}>
|
||||||
<div css={tw`flex`}>
|
<div css={tw`flex`}>
|
||||||
<LocationDeleteButton
|
<LocationDeleteButton
|
||||||
locationId={location.id}
|
locationId={location.id}
|
||||||
onDeleted={() => history.push('/admin/locations')}
|
onDeleted={() => history.push('/admin/locations')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div css={tw`flex ml-auto`}>
|
<div css={tw`flex ml-auto`}>
|
||||||
<Button type={'submit'} disabled={isSubmitting || !isValid}>
|
<Button type={'submit'} disabled={isSubmitting || !isValid}>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import tw from 'twin.macro';
|
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 { action, Action, Actions, createContextStore, useStoreActions } from 'easy-peasy';
|
||||||
import { Mount } from '@/api/admin/mounts/getMounts';
|
import { Mount } from '@/api/admin/mounts/getMounts';
|
||||||
import getMount from '@/api/admin/mounts/getMount';
|
import getMount from '@/api/admin/mounts/getMount';
|
||||||
|
@ -15,7 +15,6 @@ import Button from '@/components/elements/Button';
|
||||||
import Field from '@/components/elements/Field';
|
import Field from '@/components/elements/Field';
|
||||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
import { Field as FormikField, Form, Formik, FormikHelpers } from 'formik';
|
import { Field as FormikField, Form, Formik, FormikHelpers } from 'formik';
|
||||||
import MountDeleteButton from '@/components/admin/mounts/MountDeleteButton';
|
|
||||||
import Label from '@/components/elements/Label';
|
import Label from '@/components/elements/Label';
|
||||||
|
|
||||||
interface ctx {
|
interface ctx {
|
||||||
|
@ -41,8 +40,6 @@ interface Values {
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditInformationContainer = () => {
|
const EditInformationContainer = () => {
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||||
|
|
||||||
const mount = Context.useStoreState(state => state.mount);
|
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`w-full flex flex-row items-center mt-6`}>
|
||||||
<div css={tw`flex`}>
|
<div css={tw`flex`}>
|
||||||
<MountDeleteButton
|
{/* <MountDeleteButton */}
|
||||||
mountId={mount.id}
|
{/* mountId={mount.id} */}
|
||||||
onDeleted={() => history.push('/admin/mounts')}
|
{/* onDeleted={() => history.push('/admin/mounts')} */}
|
||||||
/>
|
{/* /> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div css={tw`flex ml-auto`}>
|
<div css={tw`flex ml-auto`}>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
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 tw from 'twin.macro';
|
||||||
import AdminContentBlock from '@/components/admin/AdminContentBlock';
|
import AdminContentBlock from '@/components/admin/AdminContentBlock';
|
||||||
import Spinner from '@/components/elements/Spinner';
|
import Spinner from '@/components/elements/Spinner';
|
||||||
|
@ -20,7 +20,6 @@ import AdminTable, { ContentWrapper, NoItems, TableBody, TableHead, TableHeader,
|
||||||
import CopyOnClick from '@/components/elements/CopyOnClick';
|
import CopyOnClick from '@/components/elements/CopyOnClick';
|
||||||
import Input from '@/components/elements/Input';
|
import Input from '@/components/elements/Input';
|
||||||
import Label from '@/components/elements/Label';
|
import Label from '@/components/elements/Label';
|
||||||
import NestDeleteButton from '@/components/admin/nests/NestDeleteButton';
|
|
||||||
|
|
||||||
interface ctx {
|
interface ctx {
|
||||||
nest: Nest | undefined;
|
nest: Nest | undefined;
|
||||||
|
@ -61,8 +60,6 @@ interface Values {
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditInformationContainer = () => {
|
const EditInformationContainer = () => {
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||||
|
|
||||||
const nest = Context.useStoreState(state => state.nest);
|
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`w-full flex flex-row items-center mt-6`}>
|
||||||
<div css={tw`flex`}>
|
<div css={tw`flex`}>
|
||||||
<NestDeleteButton
|
{/* <NestDeleteButton */}
|
||||||
nestId={nest.id}
|
{/* nestId={nest.id} */}
|
||||||
onDeleted={() => history.push('/admin/nests')}
|
{/* onDeleted={() => history.push('/admin/nests')} */}
|
||||||
/>
|
{/* /> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div css={tw`flex ml-auto`}>
|
<div css={tw`flex ml-auto`}>
|
||||||
|
|
|
@ -98,7 +98,7 @@ export default () => {
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{
|
{
|
||||||
roles.map(role => (
|
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`}>
|
<td css={tw`pl-6`}>
|
||||||
<RowCheckbox id={role.id}/>
|
<RowCheckbox id={role.id}/>
|
||||||
</td>
|
</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 React, { useState } from 'react';
|
||||||
import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||||
import { State, useStoreState } from 'easy-peasy';
|
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 LocationEditContainer from '@/components/admin/locations/LocationEditContainer';
|
||||||
import ServersContainer from '@/components/admin/servers/ServersContainer';
|
import ServersContainer from '@/components/admin/servers/ServersContainer';
|
||||||
import NewServerContainer from '@/components/admin/servers/NewServerContainer';
|
import NewServerContainer from '@/components/admin/servers/NewServerContainer';
|
||||||
import ServerEditContainer from '@/components/admin/servers/ServerEditContainer';
|
|
||||||
import UsersContainer from '@/components/admin/users/UsersContainer';
|
import UsersContainer from '@/components/admin/users/UsersContainer';
|
||||||
import NewUserContainer from '@/components/admin/users/NewUserContainer';
|
import NewUserContainer from '@/components/admin/users/NewUserContainer';
|
||||||
import UserEditContainer from '@/components/admin/users/UserEditContainer';
|
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/new`} component={NewServerContainer} exact/>
|
||||||
<Route
|
<Route
|
||||||
path={`${match.path}/servers/:id`}
|
path={`${match.path}/servers/:id`}
|
||||||
component={ServerEditContainer}
|
component={ServerRouter}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route path={`${match.path}/users`} component={UsersContainer} exact/>
|
<Route path={`${match.path}/users`} component={UsersContainer} exact/>
|
||||||
|
|
Loading…
Reference in a new issue