ui(admin): make all tables searchable and sortable
This commit is contained in:
parent
8f8d66584d
commit
c0e9f1adee
|
@ -38,12 +38,13 @@ class EggController extends ApplicationApiController
|
||||||
*/
|
*/
|
||||||
public function index(GetEggsRequest $request, Nest $nest): array
|
public function index(GetEggsRequest $request, Nest $nest): array
|
||||||
{
|
{
|
||||||
$perPage = $request->query('per_page', 0);
|
$perPage = $request->query('per_page', 10);
|
||||||
if ($perPage > 100) {
|
if ($perPage > 100) {
|
||||||
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
$eggs = QueryBuilder::for(Egg::query())
|
$eggs = QueryBuilder::for(Egg::query())
|
||||||
|
->where('nest_id', '=', $nest->id)
|
||||||
->allowedFilters(['id', 'name', 'author'])
|
->allowedFilters(['id', 'name', 'author'])
|
||||||
->allowedSorts(['id', 'name', 'author']);
|
->allowedSorts(['id', 'name', 'author']);
|
||||||
if ($perPage > 0) {
|
if ($perPage > 0) {
|
||||||
|
|
|
@ -9,7 +9,6 @@ use Spatie\QueryBuilder\QueryBuilder;
|
||||||
use Pterodactyl\Services\Locations\LocationUpdateService;
|
use Pterodactyl\Services\Locations\LocationUpdateService;
|
||||||
use Pterodactyl\Services\Locations\LocationCreationService;
|
use Pterodactyl\Services\Locations\LocationCreationService;
|
||||||
use Pterodactyl\Services\Locations\LocationDeletionService;
|
use Pterodactyl\Services\Locations\LocationDeletionService;
|
||||||
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
|
|
||||||
use Pterodactyl\Transformers\Api\Application\LocationTransformer;
|
use Pterodactyl\Transformers\Api\Application\LocationTransformer;
|
||||||
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||||
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||||
|
@ -24,7 +23,6 @@ class LocationController extends ApplicationApiController
|
||||||
private LocationCreationService $creationService;
|
private LocationCreationService $creationService;
|
||||||
private LocationDeletionService $deletionService;
|
private LocationDeletionService $deletionService;
|
||||||
private LocationUpdateService $updateService;
|
private LocationUpdateService $updateService;
|
||||||
private LocationRepositoryInterface $repository;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LocationController constructor.
|
* LocationController constructor.
|
||||||
|
@ -32,15 +30,13 @@ class LocationController extends ApplicationApiController
|
||||||
public function __construct(
|
public function __construct(
|
||||||
LocationCreationService $creationService,
|
LocationCreationService $creationService,
|
||||||
LocationDeletionService $deletionService,
|
LocationDeletionService $deletionService,
|
||||||
LocationUpdateService $updateService,
|
LocationUpdateService $updateService
|
||||||
LocationRepositoryInterface $repository
|
|
||||||
) {
|
) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
$this->creationService = $creationService;
|
$this->creationService = $creationService;
|
||||||
$this->deletionService = $deletionService;
|
$this->deletionService = $deletionService;
|
||||||
$this->updateService = $updateService;
|
$this->updateService = $updateService;
|
||||||
$this->repository = $repository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,7 +53,7 @@ class LocationController extends ApplicationApiController
|
||||||
|
|
||||||
$locations = QueryBuilder::for(Location::query())
|
$locations = QueryBuilder::for(Location::query())
|
||||||
->allowedFilters(['short', 'long'])
|
->allowedFilters(['short', 'long'])
|
||||||
->allowedSorts(['id'])
|
->allowedSorts(['id', 'short', 'long'])
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
|
|
||||||
return $this->fractal->collection($locations)
|
return $this->fractal->collection($locations)
|
||||||
|
|
|
@ -40,8 +40,8 @@ class MountController extends ApplicationApiController
|
||||||
}
|
}
|
||||||
|
|
||||||
$mounts = QueryBuilder::for(Mount::query())
|
$mounts = QueryBuilder::for(Mount::query())
|
||||||
->allowedFilters(['name', 'host'])
|
->allowedFilters(['id', 'name', 'source', 'target'])
|
||||||
->allowedSorts(['id', 'name', 'host'])
|
->allowedSorts(['id', 'name', 'source', 'target'])
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
|
|
||||||
return $this->fractal->collection($mounts)
|
return $this->fractal->collection($mounts)
|
||||||
|
|
|
@ -50,7 +50,7 @@ class NestController extends ApplicationApiController
|
||||||
*/
|
*/
|
||||||
public function index(GetNestsRequest $request): array
|
public function index(GetNestsRequest $request): array
|
||||||
{
|
{
|
||||||
$perPage = $request->query('per_page', 0);
|
$perPage = $request->query('per_page', 10);
|
||||||
if ($perPage > 100) {
|
if ($perPage > 100) {
|
||||||
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,13 @@
|
||||||
namespace Pterodactyl\Http\Controllers\Api\Application\Roles;
|
namespace Pterodactyl\Http\Controllers\Api\Application\Roles;
|
||||||
|
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
|
use Pterodactyl\Models\Location;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Pterodactyl\Models\AdminRole;
|
use Pterodactyl\Models\AdminRole;
|
||||||
|
use Spatie\QueryBuilder\QueryBuilder;
|
||||||
|
use Pterodactyl\Transformers\Api\Application\LocationTransformer;
|
||||||
use Pterodactyl\Transformers\Api\Application\AdminRoleTransformer;
|
use Pterodactyl\Transformers\Api\Application\AdminRoleTransformer;
|
||||||
|
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||||
use Pterodactyl\Http\Requests\Api\Application\Roles\GetRoleRequest;
|
use Pterodactyl\Http\Requests\Api\Application\Roles\GetRoleRequest;
|
||||||
use Pterodactyl\Http\Requests\Api\Application\Roles\GetRolesRequest;
|
use Pterodactyl\Http\Requests\Api\Application\Roles\GetRolesRequest;
|
||||||
use Pterodactyl\Http\Requests\Api\Application\Roles\StoreRoleRequest;
|
use Pterodactyl\Http\Requests\Api\Application\Roles\StoreRoleRequest;
|
||||||
|
@ -30,7 +34,17 @@ class RoleController extends ApplicationApiController
|
||||||
*/
|
*/
|
||||||
public function index(GetRolesRequest $request): array
|
public function index(GetRolesRequest $request): array
|
||||||
{
|
{
|
||||||
return $this->fractal->collection(AdminRole::all())
|
$perPage = $request->query('per_page', 10);
|
||||||
|
if ($perPage < 1 || $perPage > 100) {
|
||||||
|
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
$roles = QueryBuilder::for(AdminRole::query())
|
||||||
|
->allowedFilters(['id', 'name'])
|
||||||
|
->allowedSorts(['id', 'name'])
|
||||||
|
->paginate($perPage);
|
||||||
|
|
||||||
|
return $this->fractal->collection($roles)
|
||||||
->transformWith($this->getTransformer(AdminRoleTransformer::class))
|
->transformWith($this->getTransformer(AdminRoleTransformer::class))
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ use Pterodactyl\Http\Requests\Api\Application\Users\DeleteUserRequest;
|
||||||
use Pterodactyl\Http\Requests\Api\Application\Users\UpdateUserRequest;
|
use Pterodactyl\Http\Requests\Api\Application\Users\UpdateUserRequest;
|
||||||
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||||
|
|
||||||
class UserController extends ApplicationApiController
|
class UserController extends ApplicationApiController
|
||||||
{
|
{
|
||||||
private UserRepositoryInterface $repository;
|
private UserRepositoryInterface $repository;
|
||||||
private UserCreationService $creationService;
|
private UserCreationService $creationService;
|
||||||
|
@ -58,8 +58,8 @@ class UserController extends ApplicationApiController
|
||||||
}
|
}
|
||||||
|
|
||||||
$users = QueryBuilder::for(User::query())
|
$users = QueryBuilder::for(User::query())
|
||||||
->allowedFilters(['email', 'uuid', 'username', 'external_id'])
|
->allowedFilters(['id', 'uuid', 'username', 'email', 'first_name', 'last_name', 'external_id'])
|
||||||
->allowedSorts(['id', 'uuid'])
|
->allowedSorts(['id', 'uuid', 'username', 'email', 'admin_role_id'])
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
|
|
||||||
return $this->fractal->collection($users)
|
return $this->fractal->collection($users)
|
||||||
|
|
|
@ -28,18 +28,58 @@ export const rawDataToDatabase = ({ attributes }: FractalResponseData): Database
|
||||||
getAddress: () => `${attributes.host}:${attributes.port}`,
|
getAddress: () => `${attributes.host}:${attributes.port}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export interface Filters {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
host?: string;
|
||||||
|
}
|
||||||
|
|
||||||
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<Database>>([ 'databases', page ], async () => {
|
const params = {};
|
||||||
const { data } = await http.get('/api/application/databases', { 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<Database>>([ 'databases', page, filters, sort, sortDirection ], async () => {
|
||||||
|
const { data } = await http.get('/api/application/databases', { params: { include: include.join(','), page, ...params } });
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
items: (data.data || []).map(rawDataToDatabase),
|
items: (data.data || []).map(rawDataToDatabase),
|
||||||
|
|
|
@ -18,18 +18,58 @@ export const rawDataToLocation = ({ attributes }: FractalResponseData): Location
|
||||||
updatedAt: new Date(attributes.updated_at),
|
updatedAt: new Date(attributes.updated_at),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export interface Filters {
|
||||||
|
id?: string;
|
||||||
|
short?: string;
|
||||||
|
long?: string;
|
||||||
|
}
|
||||||
|
|
||||||
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<Location>>([ 'locations', page ], async () => {
|
const params = {};
|
||||||
const { data } = await http.get('/api/application/locations', { 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<Location>>([ 'locations', page, filters, sort, sortDirection ], async () => {
|
||||||
|
const { data } = await http.get('/api/application/locations', { params: { include: include.join(','), page, ...params } });
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
items: (data.data || []).map(rawDataToLocation),
|
items: (data.data || []).map(rawDataToLocation),
|
||||||
|
|
|
@ -43,18 +43,59 @@ export const rawDataToMount = ({ attributes }: FractalResponseData): Mount => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export interface Filters {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
source?: string;
|
||||||
|
target?: string;
|
||||||
|
}
|
||||||
|
|
||||||
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<Mount>>([ 'mounts', page ], async () => {
|
const params = {};
|
||||||
const { data } = await http.get('/api/application/mounts', { 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<Mount>>([ 'mounts', page, filters, sort, sortDirection ], async () => {
|
||||||
|
const { data } = await http.get('/api/application/mounts', { params: { include: include.join(','), page, ...params } });
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
items: (data.data || []).map(rawDataToMount),
|
items: (data.data || []).map(rawDataToMount),
|
||||||
|
|
|
@ -1,10 +1,63 @@
|
||||||
import http from '@/api/http';
|
import http, { getPaginationSet, PaginatedResult } from '@/api/http';
|
||||||
|
import { createContext, useContext } from 'react';
|
||||||
|
import useSWR from 'swr';
|
||||||
import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg';
|
import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg';
|
||||||
|
|
||||||
export default (nestId: number): Promise<Egg[]> => {
|
export interface Filters {
|
||||||
return new Promise((resolve, reject) => {
|
id?: string;
|
||||||
http.get(`/api/application/nests/${nestId}/eggs`)
|
name?: string;
|
||||||
.then(({ data }) => resolve((data.data || []).map(rawDataToEgg)))
|
}
|
||||||
.catch(reject);
|
|
||||||
|
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,
|
||||||
|
|
||||||
|
filters: null,
|
||||||
|
setFilters: () => null,
|
||||||
|
|
||||||
|
sort: null,
|
||||||
|
setSort: () => null,
|
||||||
|
|
||||||
|
sortDirection: false,
|
||||||
|
setSortDirection: () => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default (nestId: number, include: string[] = []) => {
|
||||||
|
const { page, filters, sort, sortDirection } = useContext(Context);
|
||||||
|
|
||||||
|
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<Egg>>([ nestId, 'eggs', page, filters, sort, sortDirection ], async () => {
|
||||||
|
const { data } = await http.get(`/api/application/nests/${nestId}/eggs`, { params: { include: include.join(','), page, ...params } });
|
||||||
|
|
||||||
|
return ({
|
||||||
|
items: (data.data || []).map(rawDataToEgg),
|
||||||
|
pagination: getPaginationSet(data.meta.pagination),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,24 +31,57 @@ export const rawDataToNest = ({ attributes }: FractalResponseData): Nest => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export interface Filters {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
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<Nest>>([ 'nests', page ], async () => {
|
const params = {};
|
||||||
const { data } = await http.get('/api/application/nests', {
|
if (filters !== null) {
|
||||||
params: {
|
Object.keys(filters).forEach(key => {
|
||||||
include: include.join(','),
|
// @ts-ignore
|
||||||
per_page: 10,
|
params['filter[' + key + ']'] = filters[key];
|
||||||
page,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sort !== null) {
|
||||||
|
// @ts-ignore
|
||||||
|
params.sort = (sortDirection ? '-' : '') + sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
return useSWR<PaginatedResult<Nest>>([ 'nests', page, filters, sort, sortDirection ], async () => {
|
||||||
|
const { data } = await http.get('/api/application/nests', { params: { include: include.join(','), page, ...params } });
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
items: (data.data || []).map(rawDataToNest),
|
items: (data.data || []).map(rawDataToNest),
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import http from '@/api/http';
|
import http, { FractalResponseData } from '@/api/http';
|
||||||
import { rawDataToServerAllocation } from '@/api/transformers';
|
|
||||||
|
|
||||||
export interface Allocation {
|
export interface Allocation {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -7,17 +6,22 @@ export interface Allocation {
|
||||||
alias: string | null;
|
alias: string | null;
|
||||||
port: number;
|
port: number;
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
isDefault: boolean;
|
assigned: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (uuid: string): Promise<[ Allocation, string[] ]> => {
|
export const rawDataToAllocation = (data: FractalResponseData): Allocation => ({
|
||||||
|
id: data.attributes.id,
|
||||||
|
ip: data.attributes.ip,
|
||||||
|
alias: data.attributes.ip_alias,
|
||||||
|
port: data.attributes.port,
|
||||||
|
notes: data.attributes.notes,
|
||||||
|
assigned: data.attributes.assigned,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default (uuid: string): Promise<Allocation[]> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
http.get(`/api/application/allocations/${uuid}`)
|
http.get(`/api/application/nodes/${uuid}/allocations`)
|
||||||
.then(({ data }) => resolve([
|
.then(({ data }) => resolve((data.data || []).map(rawDataToAllocation)))
|
||||||
rawDataToServerAllocation(data),
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
data.meta?.is_allocation_owner ? [ '*' ] : (data.meta?.user_permissions || []),
|
|
||||||
]))
|
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -68,6 +68,7 @@ export const rawDataToNode = ({ attributes }: FractalResponseData): Node => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface Filters {
|
export interface Filters {
|
||||||
|
id?: string;
|
||||||
uuid?: string;
|
uuid?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import http, { FractalResponseData } from '@/api/http';
|
import http, { FractalResponseData, getPaginationSet, PaginatedResult } from '@/api/http';
|
||||||
|
import { createContext, useContext } from 'react';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
export interface Role {
|
export interface Role {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -12,10 +14,61 @@ export const rawDataToRole = ({ attributes }: FractalResponseData): Role => ({
|
||||||
description: attributes.description,
|
description: attributes.description,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default (include: string[] = []): Promise<Role[]> => {
|
export interface Filters {
|
||||||
return new Promise((resolve, reject) => {
|
id?: string;
|
||||||
http.get('/api/application/roles', { params: { include: include.join(',') } })
|
name?: string;
|
||||||
.then(({ data }) => resolve((data.data || []).map(rawDataToRole)))
|
}
|
||||||
.catch(reject);
|
|
||||||
|
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,
|
||||||
|
|
||||||
|
filters: null,
|
||||||
|
setFilters: () => null,
|
||||||
|
|
||||||
|
sort: null,
|
||||||
|
setSort: () => null,
|
||||||
|
|
||||||
|
sortDirection: false,
|
||||||
|
setSortDirection: () => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default (include: string[] = []) => {
|
||||||
|
const { page, filters, sort, sortDirection } = useContext(Context);
|
||||||
|
|
||||||
|
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<Role>>([ 'roles', page, filters, sort, sortDirection ], async () => {
|
||||||
|
const { data } = await http.get('/api/application/roles', { params: { include: include.join(','), page, ...params } });
|
||||||
|
|
||||||
|
return ({
|
||||||
|
items: (data.data || []).map(rawDataToRole),
|
||||||
|
pagination: getPaginationSet(data.meta.pagination),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -100,6 +100,7 @@ export const rawDataToServer = ({ attributes }: FractalResponseData): Server =>
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface Filters {
|
export interface Filters {
|
||||||
|
id?: string;
|
||||||
uuid?: string;
|
uuid?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
|
|
|
@ -36,18 +36,61 @@ export const rawDataToUser = ({ attributes }: FractalResponseData): User => ({
|
||||||
updatedAt: new Date(attributes.updated_at),
|
updatedAt: new Date(attributes.updated_at),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export interface Filters {
|
||||||
|
id?: string;
|
||||||
|
uuid?: string;
|
||||||
|
username?: string;
|
||||||
|
email?: string;
|
||||||
|
firstName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
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<User>>([ 'users', page ], async () => {
|
const params = {};
|
||||||
const { data } = await http.get('/api/application/users', { 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<User>>([ 'users', page, filters, sort, sortDirection ], async () => {
|
||||||
|
const { data } = await http.get('/api/application/users', { params: { include: include.join(','), page, ...params } });
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
items: (data.data || []).map(rawDataToUser),
|
items: (data.data || []).map(rawDataToUser),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
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, Filters } from '@/api/admin/databases/getDatabases';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
import { AdminContext } from '@/state/admin';
|
import { AdminContext } from '@/state/admin';
|
||||||
|
@ -34,7 +34,7 @@ const RowCheckbox = ({ id }: { id: number}) => {
|
||||||
const DatabasesContainer = () => {
|
const DatabasesContainer = () => {
|
||||||
const match = useRouteMatch();
|
const match = useRouteMatch();
|
||||||
|
|
||||||
const { page, setPage } = useContext(DatabasesContext);
|
const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(DatabasesContext);
|
||||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||||
const { data: databases, error, isValidating } = getDatabases();
|
const { data: databases, error, isValidating } = getDatabases();
|
||||||
|
|
||||||
|
@ -56,6 +56,17 @@ const DatabasesContainer = () => {
|
||||||
setSelectedDatabases(e.currentTarget.checked ? (databases?.items?.map(database => database.id) || []) : []);
|
setSelectedDatabases(e.currentTarget.checked ? (databases?.items?.map(database => database.id) || []) : []);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSearch = (query: string): Promise<void> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (query.length < 2) {
|
||||||
|
setFilters(null);
|
||||||
|
} else {
|
||||||
|
setFilters({ id: query, name: query, host: query });
|
||||||
|
}
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedDatabases([]);
|
setSelectedDatabases([]);
|
||||||
}, [ page ]);
|
}, [ page ]);
|
||||||
|
@ -89,15 +100,16 @@ const DatabasesContainer = () => {
|
||||||
<ContentWrapper
|
<ContentWrapper
|
||||||
checked={selectedDatabasesLength === (length === 0 ? -1 : length)}
|
checked={selectedDatabasesLength === (length === 0 ? -1 : length)}
|
||||||
onSelectAllClick={onSelectAllClick}
|
onSelectAllClick={onSelectAllClick}
|
||||||
|
onSearch={onSearch}
|
||||||
>
|
>
|
||||||
<Pagination data={databases} onPageSelect={setPage}>
|
<Pagination data={databases} onPageSelect={setPage}>
|
||||||
<div css={tw`overflow-x-auto`}>
|
<div css={tw`overflow-x-auto`}>
|
||||||
<table css={tw`w-full table-auto`}>
|
<table css={tw`w-full table-auto`}>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableHeader name={'ID'}/>
|
<TableHeader name={'ID'} direction={sort === 'id' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('id')}/>
|
||||||
<TableHeader name={'Name'}/>
|
<TableHeader name={'Name'} direction={sort === 'name' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('name')}/>
|
||||||
<TableHeader name={'Address'}/>
|
<TableHeader name={'Address'}/>
|
||||||
<TableHeader name={'Username'}/>
|
<TableHeader name={'Username'} direction={sort === 'username' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('username')}/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
@ -143,9 +155,21 @@ const DatabasesContainer = () => {
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [ page, setPage ] = useState<number>(1);
|
const [ page, setPage ] = useState<number>(1);
|
||||||
|
const [ filters, setFilters ] = useState<Filters | null>(null);
|
||||||
|
const [ sort, setSortState ] = useState<string | null>(null);
|
||||||
|
const [ sortDirection, setSortDirection ] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const setSort = (newSort: string | null) => {
|
||||||
|
if (sort === newSort) {
|
||||||
|
setSortDirection(!sortDirection);
|
||||||
|
} else {
|
||||||
|
setSortState(newSort);
|
||||||
|
setSortDirection(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DatabasesContext.Provider value={{ page, setPage }}>
|
<DatabasesContext.Provider value={{ page, setPage, filters, setFilters, sort, setSort, sortDirection, setSortDirection }}>
|
||||||
<DatabasesContainer/>
|
<DatabasesContainer/>
|
||||||
</DatabasesContext.Provider>
|
</DatabasesContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import getLocations, { Context as LocationsContext } from '@/api/admin/locations/getLocations';
|
import getLocations, { Context as LocationsContext, Filters } from '@/api/admin/locations/getLocations';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
import { AdminContext } from '@/state/admin';
|
import { AdminContext } from '@/state/admin';
|
||||||
|
@ -34,7 +34,7 @@ const RowCheckbox = ({ id }: { id: number}) => {
|
||||||
const LocationsContainer = () => {
|
const LocationsContainer = () => {
|
||||||
const match = useRouteMatch();
|
const match = useRouteMatch();
|
||||||
|
|
||||||
const { page, setPage } = useContext(LocationsContext);
|
const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(LocationsContext);
|
||||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||||
const { data: locations, error, isValidating } = getLocations();
|
const { data: locations, error, isValidating } = getLocations();
|
||||||
|
|
||||||
|
@ -56,6 +56,17 @@ const LocationsContainer = () => {
|
||||||
setSelectedLocations(e.currentTarget.checked ? (locations?.items?.map(location => location.id) || []) : []);
|
setSelectedLocations(e.currentTarget.checked ? (locations?.items?.map(location => location.id) || []) : []);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSearch = (query: string): Promise<void> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (query.length < 2) {
|
||||||
|
setFilters(null);
|
||||||
|
} else {
|
||||||
|
setFilters({ short: query, long: query });
|
||||||
|
}
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedLocations([]);
|
setSelectedLocations([]);
|
||||||
}, [ page ]);
|
}, [ page ]);
|
||||||
|
@ -85,14 +96,15 @@ const LocationsContainer = () => {
|
||||||
<ContentWrapper
|
<ContentWrapper
|
||||||
checked={selectedLocationsLength === (length === 0 ? -1 : length)}
|
checked={selectedLocationsLength === (length === 0 ? -1 : length)}
|
||||||
onSelectAllClick={onSelectAllClick}
|
onSelectAllClick={onSelectAllClick}
|
||||||
|
onSearch={onSearch}
|
||||||
>
|
>
|
||||||
<Pagination data={locations} onPageSelect={setPage}>
|
<Pagination data={locations} onPageSelect={setPage}>
|
||||||
<div css={tw`overflow-x-auto`}>
|
<div css={tw`overflow-x-auto`}>
|
||||||
<table css={tw`w-full table-auto`}>
|
<table css={tw`w-full table-auto`}>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableHeader name={'ID'}/>
|
<TableHeader name={'ID'} direction={sort === 'id' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('id')}/>
|
||||||
<TableHeader name={'Short Name'}/>
|
<TableHeader name={'Short Name'} direction={sort === 'short' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('short')}/>
|
||||||
<TableHeader name={'Long Name'}/>
|
<TableHeader name={'Long Name'} direction={sort === 'long' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('long')}/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
@ -132,9 +144,21 @@ const LocationsContainer = () => {
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [ page, setPage ] = useState<number>(1);
|
const [ page, setPage ] = useState<number>(1);
|
||||||
|
const [ filters, setFilters ] = useState<Filters | null>(null);
|
||||||
|
const [ sort, setSortState ] = useState<string | null>(null);
|
||||||
|
const [ sortDirection, setSortDirection ] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const setSort = (newSort: string | null) => {
|
||||||
|
if (sort === newSort) {
|
||||||
|
setSortDirection(!sortDirection);
|
||||||
|
} else {
|
||||||
|
setSortState(newSort);
|
||||||
|
setSortDirection(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocationsContext.Provider value={{ page, setPage }}>
|
<LocationsContext.Provider value={{ page, setPage, filters, setFilters, sort, setSort, sortDirection, setSortDirection }}>
|
||||||
<LocationsContainer/>
|
<LocationsContainer/>
|
||||||
</LocationsContext.Provider>
|
</LocationsContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import getMounts, { Context as MountsContext } from '@/api/admin/mounts/getMounts';
|
import getMounts, { Context as MountsContext, Filters } from '@/api/admin/mounts/getMounts';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
import { AdminContext } from '@/state/admin';
|
import { AdminContext } from '@/state/admin';
|
||||||
|
@ -34,7 +34,7 @@ const RowCheckbox = ({ id }: { id: number}) => {
|
||||||
const MountsContainer = () => {
|
const MountsContainer = () => {
|
||||||
const match = useRouteMatch();
|
const match = useRouteMatch();
|
||||||
|
|
||||||
const { page, setPage } = useContext(MountsContext);
|
const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(MountsContext);
|
||||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||||
const { data: mounts, error, isValidating } = getMounts();
|
const { data: mounts, error, isValidating } = getMounts();
|
||||||
|
|
||||||
|
@ -56,6 +56,17 @@ const MountsContainer = () => {
|
||||||
setSelectedMounts(e.currentTarget.checked ? (mounts?.items?.map(mount => mount.id) || []) : []);
|
setSelectedMounts(e.currentTarget.checked ? (mounts?.items?.map(mount => mount.id) || []) : []);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSearch = (query: string): Promise<void> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (query.length < 2) {
|
||||||
|
setFilters(null);
|
||||||
|
} else {
|
||||||
|
setFilters({ id: query });
|
||||||
|
}
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedMounts([]);
|
setSelectedMounts([]);
|
||||||
}, [ page ]);
|
}, [ page ]);
|
||||||
|
@ -87,15 +98,16 @@ const MountsContainer = () => {
|
||||||
<ContentWrapper
|
<ContentWrapper
|
||||||
checked={selectedMountsLength === (length === 0 ? -1 : length)}
|
checked={selectedMountsLength === (length === 0 ? -1 : length)}
|
||||||
onSelectAllClick={onSelectAllClick}
|
onSelectAllClick={onSelectAllClick}
|
||||||
|
onSearch={onSearch}
|
||||||
>
|
>
|
||||||
<Pagination data={mounts} onPageSelect={setPage}>
|
<Pagination data={mounts} onPageSelect={setPage}>
|
||||||
<div css={tw`overflow-x-auto`}>
|
<div css={tw`overflow-x-auto`}>
|
||||||
<table css={tw`w-full table-auto`}>
|
<table css={tw`w-full table-auto`}>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableHeader name={'ID'}/>
|
<TableHeader name={'ID'} direction={sort === 'id' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('id')}/>
|
||||||
<TableHeader name={'Name'}/>
|
<TableHeader name={'Name'} direction={sort === 'name' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('name')}/>
|
||||||
<TableHeader name={'Source Path'}/>
|
<TableHeader name={'Source Path'} direction={sort === 'source' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('source')}/>
|
||||||
<TableHeader name={'Target Path'}/>
|
<TableHeader name={'Target Path'} direction={sort === 'target' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('target')}/>
|
||||||
<th css={tw`px-6 py-2`}/>
|
<th css={tw`px-6 py-2`}/>
|
||||||
<th css={tw`px-6 py-2`}/>
|
<th css={tw`px-6 py-2`}/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
@ -171,9 +183,21 @@ const MountsContainer = () => {
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [ page, setPage ] = useState<number>(1);
|
const [ page, setPage ] = useState<number>(1);
|
||||||
|
const [ filters, setFilters ] = useState<Filters | null>(null);
|
||||||
|
const [ sort, setSortState ] = useState<string | null>(null);
|
||||||
|
const [ sortDirection, setSortDirection ] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const setSort = (newSort: string | null) => {
|
||||||
|
if (sort === newSort) {
|
||||||
|
setSortDirection(!sortDirection);
|
||||||
|
} else {
|
||||||
|
setSortState(newSort);
|
||||||
|
setSortDirection(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MountsContext.Provider value={{ page, setPage }}>
|
<MountsContext.Provider value={{ page, setPage, filters, setFilters, sort, setSort, sortDirection, setSortDirection }}>
|
||||||
<MountsContainer/>
|
<MountsContainer/>
|
||||||
</MountsContext.Provider>
|
</MountsContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { NavLink, useRouteMatch } from 'react-router-dom';
|
import { 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';
|
||||||
|
@ -16,12 +16,11 @@ import { ApplicationStore } from '@/state';
|
||||||
import { action, Action, Actions, createContextStore, useStoreActions } from 'easy-peasy';
|
import { action, Action, Actions, createContextStore, useStoreActions } from 'easy-peasy';
|
||||||
import { Form, Formik, FormikHelpers } from 'formik';
|
import { Form, Formik, FormikHelpers } from 'formik';
|
||||||
import AdminBox from '@/components/admin/AdminBox';
|
import AdminBox from '@/components/admin/AdminBox';
|
||||||
import AdminCheckbox from '@/components/admin/AdminCheckbox';
|
|
||||||
import AdminTable, { ContentWrapper, NoItems, TableBody, TableHead, TableHeader, TableRow } from '@/components/admin/AdminTable';
|
|
||||||
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';
|
import NestDeleteButton from '@/components/admin/nests/NestDeleteButton';
|
||||||
|
import NestEggTable from '@/components/admin/nests/NestEggTable';
|
||||||
|
|
||||||
interface ctx {
|
interface ctx {
|
||||||
nest: Nest | undefined;
|
nest: Nest | undefined;
|
||||||
|
@ -198,28 +197,8 @@ const ViewDetailsContainer = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RowCheckbox = ({ id }: { id: number }) => {
|
|
||||||
const isChecked = Context.useStoreState(state => state.selectedEggs.indexOf(id) >= 0);
|
|
||||||
const appendSelectedEggs = Context.useStoreActions(actions => actions.appendSelectedEggs);
|
|
||||||
const removeSelectedEggs = Context.useStoreActions(actions => actions.removeSelectedEggs);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AdminCheckbox
|
|
||||||
name={id.toString()}
|
|
||||||
checked={isChecked}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
if (e.currentTarget.checked) {
|
|
||||||
appendSelectedEggs(id);
|
|
||||||
} else {
|
|
||||||
removeSelectedEggs(id);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const NestEditContainer = () => {
|
const NestEditContainer = () => {
|
||||||
const match = useRouteMatch<{ nestId?: string }>();
|
const match = useRouteMatch<{ nestId: string }>();
|
||||||
|
|
||||||
const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||||
const [ loading, setLoading ] = useState(true);
|
const [ loading, setLoading ] = useState(true);
|
||||||
|
@ -227,13 +206,10 @@ const NestEditContainer = () => {
|
||||||
const nest = Context.useStoreState(state => state.nest);
|
const nest = Context.useStoreState(state => state.nest);
|
||||||
const setNest = Context.useStoreActions(actions => actions.setNest);
|
const setNest = Context.useStoreActions(actions => actions.setNest);
|
||||||
|
|
||||||
const setSelectedEggs = Context.useStoreActions(actions => actions.setSelectedEggs);
|
|
||||||
const selectedEggsLength = Context.useStoreState(state => state.selectedEggs.length);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
clearFlashes('nest');
|
clearFlashes('nest');
|
||||||
|
|
||||||
getNest(Number(match.params?.nestId), [ 'eggs' ])
|
getNest(Number(match.params.nestId), [ 'eggs' ])
|
||||||
.then(nest => setNest(nest))
|
.then(nest => setNest(nest))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -254,12 +230,6 @@ const NestEditContainer = () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const length = nest.relations.eggs?.length || 0;
|
|
||||||
|
|
||||||
const onSelectAllClick = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setSelectedEggs(e.currentTarget.checked ? (nest.relations.eggs?.map(egg => egg.id) || []) : []);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminContentBlock title={'Nests - ' + nest.name}>
|
<AdminContentBlock title={'Nests - ' + nest.name}>
|
||||||
<div css={tw`w-full flex flex-row items-center mb-8`}>
|
<div css={tw`w-full flex flex-row items-center mb-8`}>
|
||||||
|
@ -289,52 +259,7 @@ const NestEditContainer = () => {
|
||||||
<ViewDetailsContainer/>
|
<ViewDetailsContainer/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AdminTable>
|
<NestEggTable/>
|
||||||
{ length < 1 ?
|
|
||||||
<NoItems/>
|
|
||||||
:
|
|
||||||
<ContentWrapper
|
|
||||||
checked={selectedEggsLength === (length === 0 ? -1 : length)}
|
|
||||||
onSelectAllClick={onSelectAllClick}
|
|
||||||
>
|
|
||||||
<div css={tw`overflow-x-auto`}>
|
|
||||||
<table css={tw`w-full table-auto`}>
|
|
||||||
<TableHead>
|
|
||||||
<TableHeader name={'ID'}/>
|
|
||||||
<TableHeader name={'Name'}/>
|
|
||||||
<TableHeader name={'Description'}/>
|
|
||||||
</TableHead>
|
|
||||||
|
|
||||||
<TableBody>
|
|
||||||
{
|
|
||||||
nest.relations.eggs?.map(egg => (
|
|
||||||
<TableRow key={egg.id}>
|
|
||||||
<td css={tw`pl-6`}>
|
|
||||||
<RowCheckbox id={egg.id}/>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td css={tw`px-6 text-sm text-neutral-200 text-left whitespace-nowrap`}>
|
|
||||||
<CopyOnClick text={egg.id.toString()}>
|
|
||||||
<code css={tw`font-mono bg-neutral-900 rounded py-1 px-2`}>{egg.id}</code>
|
|
||||||
</CopyOnClick>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td css={tw`px-6 text-sm text-neutral-200 text-left whitespace-nowrap`}>
|
|
||||||
<NavLink to={`${match.url}/eggs/${egg.id}`} css={tw`text-primary-400 hover:text-primary-300`}>
|
|
||||||
{egg.name}
|
|
||||||
</NavLink>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td css={tw`px-6 text-sm text-neutral-200 text-left whitespace-nowrap`}>{egg.description}</td>
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</TableBody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</ContentWrapper>
|
|
||||||
}
|
|
||||||
</AdminTable>
|
|
||||||
</AdminContentBlock>
|
</AdminContentBlock>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
import CopyOnClick from '@/components/elements/CopyOnClick';
|
||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import getEggs, { Context as EggsContext, Filters } from '@/api/admin/nests/getEggs';
|
||||||
|
import useFlash from '@/plugins/useFlash';
|
||||||
|
import { NavLink, useRouteMatch } from 'react-router-dom';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
import AdminCheckbox from '@/components/admin/AdminCheckbox';
|
||||||
|
import AdminTable, { TableBody, TableHead, TableHeader, TableRow, Pagination, Loading, NoItems, ContentWrapper } from '@/components/admin/AdminTable';
|
||||||
|
import { Context } from '@/components/admin/nests/NestEditContainer';
|
||||||
|
|
||||||
|
const RowCheckbox = ({ id }: { id: number}) => {
|
||||||
|
const isChecked = Context.useStoreState(state => state.selectedEggs.indexOf(id) >= 0);
|
||||||
|
const appendSelectedEggs = Context.useStoreActions(actions => actions.appendSelectedEggs);
|
||||||
|
const removeSelectedEggs = Context.useStoreActions(actions => actions.removeSelectedEggs);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AdminCheckbox
|
||||||
|
name={id.toString()}
|
||||||
|
checked={isChecked}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (e.currentTarget.checked) {
|
||||||
|
appendSelectedEggs(id);
|
||||||
|
} else {
|
||||||
|
removeSelectedEggs(id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EggsTable = () => {
|
||||||
|
const match = useRouteMatch<{ nestId: string }>();
|
||||||
|
|
||||||
|
const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(EggsContext);
|
||||||
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||||
|
const { data: eggs, error, isValidating } = getEggs(Number(match.params.nestId));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!error) {
|
||||||
|
clearFlashes('nests');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAndAddHttpError({ key: 'nests', error });
|
||||||
|
}, [ error ]);
|
||||||
|
|
||||||
|
const length = eggs?.items?.length || 0;
|
||||||
|
|
||||||
|
const setSelectedEggs = Context.useStoreActions(actions => actions.setSelectedEggs);
|
||||||
|
const selectedEggsLength = Context.useStoreState(state => state.selectedEggs.length);
|
||||||
|
|
||||||
|
const onSelectAllClick = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setSelectedEggs(e.currentTarget.checked ? (eggs?.items?.map(nest => nest.id) || []) : []);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSearch = (query: string): Promise<void> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (query.length < 2) {
|
||||||
|
setFilters(null);
|
||||||
|
} else {
|
||||||
|
setFilters({ name: query });
|
||||||
|
}
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedEggs([]);
|
||||||
|
}, [ page ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AdminTable>
|
||||||
|
{ eggs === undefined || (error && isValidating) ?
|
||||||
|
<Loading/>
|
||||||
|
:
|
||||||
|
length < 1 ?
|
||||||
|
<NoItems/>
|
||||||
|
:
|
||||||
|
<ContentWrapper
|
||||||
|
checked={selectedEggsLength === (length === 0 ? -1 : length)}
|
||||||
|
onSelectAllClick={onSelectAllClick}
|
||||||
|
onSearch={onSearch}
|
||||||
|
>
|
||||||
|
<Pagination data={eggs} onPageSelect={setPage}>
|
||||||
|
<div css={tw`overflow-x-auto`}>
|
||||||
|
<table css={tw`w-full table-auto`}>
|
||||||
|
<TableHead>
|
||||||
|
<TableHeader name={'ID'} direction={sort === 'id' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('id')}/>
|
||||||
|
<TableHeader name={'Name'} direction={sort === 'name' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('name')}/>
|
||||||
|
<TableHeader name={'Description'}/>
|
||||||
|
</TableHead>
|
||||||
|
|
||||||
|
<TableBody>
|
||||||
|
{
|
||||||
|
eggs.items.map(egg => (
|
||||||
|
<TableRow key={egg.id}>
|
||||||
|
<td css={tw`pl-6`}>
|
||||||
|
<RowCheckbox id={egg.id}/>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td css={tw`px-6 text-sm text-neutral-200 text-left whitespace-nowrap`}>
|
||||||
|
<CopyOnClick text={egg.id.toString()}>
|
||||||
|
<code css={tw`font-mono bg-neutral-900 rounded py-1 px-2`}>{egg.id}</code>
|
||||||
|
</CopyOnClick>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td css={tw`px-6 text-sm text-neutral-200 text-left whitespace-nowrap`}>
|
||||||
|
<NavLink to={`${match.url}/eggs/${egg.id}`} css={tw`text-primary-400 hover:text-primary-300`}>
|
||||||
|
{egg.name}
|
||||||
|
</NavLink>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td css={tw`px-6 text-sm text-neutral-200 text-left whitespace-nowrap`}>{egg.description}</td>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</TableBody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</Pagination>
|
||||||
|
</ContentWrapper>
|
||||||
|
}
|
||||||
|
</AdminTable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const [ page, setPage ] = useState<number>(1);
|
||||||
|
const [ filters, setFilters ] = useState<Filters | null>(null);
|
||||||
|
const [ sort, setSortState ] = useState<string | null>(null);
|
||||||
|
const [ sortDirection, setSortDirection ] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const setSort = (newSort: string | null) => {
|
||||||
|
if (sort === newSort) {
|
||||||
|
setSortDirection(!sortDirection);
|
||||||
|
} else {
|
||||||
|
setSortState(newSort);
|
||||||
|
setSortDirection(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EggsContext.Provider value={{ page, setPage, filters, setFilters, sort, setSort, sortDirection, setSortDirection }}>
|
||||||
|
<EggsTable/>
|
||||||
|
</EggsContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
import CopyOnClick from '@/components/elements/CopyOnClick';
|
import CopyOnClick from '@/components/elements/CopyOnClick';
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import getNests, { Context as NestsContext } from '@/api/admin/nests/getNests';
|
import getNests, { Context as NestsContext, Filters } from '@/api/admin/nests/getNests';
|
||||||
import NewNestButton from '@/components/admin/nests/NewNestButton';
|
import NewNestButton from '@/components/admin/nests/NewNestButton';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
|
@ -34,7 +34,7 @@ const RowCheckbox = ({ id }: { id: number}) => {
|
||||||
const NestsContainer = () => {
|
const NestsContainer = () => {
|
||||||
const match = useRouteMatch();
|
const match = useRouteMatch();
|
||||||
|
|
||||||
const { page, setPage } = useContext(NestsContext);
|
const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(NestsContext);
|
||||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||||
const { data: nests, error, isValidating } = getNests();
|
const { data: nests, error, isValidating } = getNests();
|
||||||
|
|
||||||
|
@ -56,6 +56,17 @@ const NestsContainer = () => {
|
||||||
setSelectedNests(e.currentTarget.checked ? (nests?.items?.map(nest => nest.id) || []) : []);
|
setSelectedNests(e.currentTarget.checked ? (nests?.items?.map(nest => nest.id) || []) : []);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSearch = (query: string): Promise<void> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (query.length < 2) {
|
||||||
|
setFilters(null);
|
||||||
|
} else {
|
||||||
|
setFilters({ id: query });
|
||||||
|
}
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedNests([]);
|
setSelectedNests([]);
|
||||||
}, [ page ]);
|
}, [ page ]);
|
||||||
|
@ -85,13 +96,14 @@ const NestsContainer = () => {
|
||||||
<ContentWrapper
|
<ContentWrapper
|
||||||
checked={selectedNestsLength === (length === 0 ? -1 : length)}
|
checked={selectedNestsLength === (length === 0 ? -1 : length)}
|
||||||
onSelectAllClick={onSelectAllClick}
|
onSelectAllClick={onSelectAllClick}
|
||||||
|
onSearch={onSearch}
|
||||||
>
|
>
|
||||||
<Pagination data={nests} onPageSelect={setPage}>
|
<Pagination data={nests} onPageSelect={setPage}>
|
||||||
<div css={tw`overflow-x-auto`}>
|
<div css={tw`overflow-x-auto`}>
|
||||||
<table css={tw`w-full table-auto`}>
|
<table css={tw`w-full table-auto`}>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableHeader name={'ID'}/>
|
<TableHeader name={'ID'} direction={sort === 'id' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('id')}/>
|
||||||
<TableHeader name={'Name'}/>
|
<TableHeader name={'Name'} direction={sort === 'name' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('name')}/>
|
||||||
<TableHeader name={'Description'}/>
|
<TableHeader name={'Description'}/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
||||||
|
@ -132,9 +144,21 @@ const NestsContainer = () => {
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [ page, setPage ] = useState<number>(1);
|
const [ page, setPage ] = useState<number>(1);
|
||||||
|
const [ filters, setFilters ] = useState<Filters | null>(null);
|
||||||
|
const [ sort, setSortState ] = useState<string | null>(null);
|
||||||
|
const [ sortDirection, setSortDirection ] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const setSort = (newSort: string | null) => {
|
||||||
|
if (sort === newSort) {
|
||||||
|
setSortDirection(!sortDirection);
|
||||||
|
} else {
|
||||||
|
setSortState(newSort);
|
||||||
|
setSortDirection(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NestsContext.Provider value={{ page, setPage }}>
|
<NestsContext.Provider value={{ page, setPage, filters, setFilters, sort, setSort, sortDirection, setSortDirection }}>
|
||||||
<NestsContainer/>
|
<NestsContainer/>
|
||||||
</NestsContext.Provider>
|
</NestsContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
import Label from '@/components/elements/Label';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import AdminBox from '@/components/admin/AdminBox';
|
||||||
|
import Creatable from 'react-select/creatable';
|
||||||
|
import { ActionMeta, GroupTypeBase, InputActionMeta, ValueType } from 'react-select/src/types';
|
||||||
|
import { SelectStyle } from '@/components/elements/Select2';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
import getAllocations from '@/api/admin/nodes/getAllocations';
|
||||||
|
import { useRouteMatch } from 'react-router-dom';
|
||||||
|
|
||||||
|
interface Option {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const distinct = (value: any, index: any, self: any) => {
|
||||||
|
return self.indexOf(value) === index;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const match = useRouteMatch<{ id: string }>();
|
||||||
|
|
||||||
|
const [ ips, setIPs ] = useState<Option[]>([]);
|
||||||
|
const [ ports, setPorts ] = useState<Option[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getAllocations(match.params.id)
|
||||||
|
.then(allocations => {
|
||||||
|
setIPs(allocations.map(a => a.ip).filter(distinct).map(ip => {
|
||||||
|
return { value: ip, label: ip };
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onChange = (value: ValueType<Option, any>, action: ActionMeta<any>) => {
|
||||||
|
console.log({
|
||||||
|
event: 'onChange',
|
||||||
|
value,
|
||||||
|
action,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onInputChange = (newValue: string, actionMeta: InputActionMeta) => {
|
||||||
|
console.log({
|
||||||
|
event: 'onInputChange',
|
||||||
|
newValue,
|
||||||
|
actionMeta,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const isValidNewOption1 = (inputValue: string, selectValue: ValueType<Option, any>, selectOptions: ReadonlyArray<Option | GroupTypeBase<Option>>): boolean => {
|
||||||
|
return inputValue.match(/^([0-9a-f.:/]+)$/) !== null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const isValidNewOption2 = (inputValue: string, selectValue: ValueType<Option, any>, selectOptions: ReadonlyArray<Option | GroupTypeBase<Option>>): boolean => {
|
||||||
|
return inputValue.match(/^([0-9-]+)$/) !== null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AdminBox title={'Allocations'}>
|
||||||
|
<div css={tw`mb-6`}>
|
||||||
|
<Label>IPs and CIDRs</Label>
|
||||||
|
<Creatable
|
||||||
|
options={ips}
|
||||||
|
styles={SelectStyle}
|
||||||
|
onChange={onChange}
|
||||||
|
onInputChange={onInputChange}
|
||||||
|
isValidNewOption={isValidNewOption1}
|
||||||
|
isMulti
|
||||||
|
isSearchable
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div css={tw`mb-6`}>
|
||||||
|
<Label>Ports</Label>
|
||||||
|
<Creatable
|
||||||
|
options={ports}
|
||||||
|
styles={SelectStyle}
|
||||||
|
// onChange={onChange}
|
||||||
|
// onInputChange={onInputChange}
|
||||||
|
isValidNewOption={isValidNewOption2}
|
||||||
|
isMulti
|
||||||
|
isSearchable
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</AdminBox>
|
||||||
|
);
|
||||||
|
};
|
|
@ -13,6 +13,7 @@ import { SubNavigation, SubNavigationLink } from '@/components/admin/SubNavigati
|
||||||
import NodeAboutContainer from '@/components/admin/nodes/NodeAboutContainer';
|
import NodeAboutContainer from '@/components/admin/nodes/NodeAboutContainer';
|
||||||
import NodeSettingsContainer from '@/components/admin/nodes/NodeSettingsContainer';
|
import NodeSettingsContainer from '@/components/admin/nodes/NodeSettingsContainer';
|
||||||
import NodeConfigurationContainer from '@/components/admin/nodes/NodeConfigurationContainer';
|
import NodeConfigurationContainer from '@/components/admin/nodes/NodeConfigurationContainer';
|
||||||
|
import NodeAllocationContainer from '@/components/admin/nodes/NodeAllocationContainer';
|
||||||
|
|
||||||
interface ctx {
|
interface ctx {
|
||||||
node: Node | undefined;
|
node: Node | undefined;
|
||||||
|
@ -118,7 +119,7 @@ const NodeRouter = () => {
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path={`${match.path}/allocations`} exact>
|
<Route path={`${match.path}/allocations`} exact>
|
||||||
<p>Allocations</p>
|
<NodeAllocationContainer/>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path={`${match.path}/servers`} exact>
|
<Route path={`${match.path}/servers`} exact>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { useDeepMemoize } from '@/plugins/useDeepMemoize';
|
import getRoles, { Context as RolesContext, Filters } from '@/api/admin/roles/getRoles';
|
||||||
import { AdminContext } from '@/state/admin';
|
import { AdminContext } from '@/state/admin';
|
||||||
import NewRoleButton from '@/components/admin/roles/NewRoleButton';
|
import NewRoleButton from '@/components/admin/roles/NewRoleButton';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
|
@ -7,9 +7,8 @@ import useFlash from '@/plugins/useFlash';
|
||||||
import { NavLink, 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 getRoles from '@/api/admin/roles/getRoles';
|
|
||||||
import AdminCheckbox from '@/components/admin/AdminCheckbox';
|
import AdminCheckbox from '@/components/admin/AdminCheckbox';
|
||||||
import AdminTable, { ContentWrapper, Loading, NoItems, TableBody, TableHead, TableHeader, TableRow } from '@/components/admin/AdminTable';
|
import AdminTable, { TableBody, TableHead, TableHeader, TableRow, Pagination, Loading, NoItems, ContentWrapper } from '@/components/admin/AdminTable';
|
||||||
import CopyOnClick from '@/components/elements/CopyOnClick';
|
import CopyOnClick from '@/components/elements/CopyOnClick';
|
||||||
|
|
||||||
const RowCheckbox = ({ id }: { id: number }) => {
|
const RowCheckbox = ({ id }: { id: number }) => {
|
||||||
|
@ -32,35 +31,46 @@ const RowCheckbox = ({ id }: { id: number }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default () => {
|
const RolesContainer = () => {
|
||||||
const match = useRouteMatch();
|
const match = useRouteMatch();
|
||||||
|
|
||||||
|
const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(RolesContext);
|
||||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||||
const [ loading, setLoading ] = useState(true);
|
const { data: roles, error, isValidating } = getRoles();
|
||||||
|
|
||||||
const roles = useDeepMemoize(AdminContext.useStoreState(state => state.roles.data));
|
useEffect(() => {
|
||||||
const setRoles = AdminContext.useStoreActions(state => state.roles.setRoles);
|
if (!error) {
|
||||||
|
clearFlashes('roles');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAndAddHttpError({ key: 'roles', error });
|
||||||
|
}, [ error ]);
|
||||||
|
|
||||||
|
const length = roles?.items?.length || 0;
|
||||||
|
|
||||||
const setSelectedRoles = AdminContext.useStoreActions(actions => actions.roles.setSelectedRoles);
|
const setSelectedRoles = AdminContext.useStoreActions(actions => actions.roles.setSelectedRoles);
|
||||||
const selectedRolesLength = AdminContext.useStoreState(state => state.roles.selectedRoles.length);
|
const selectedRolesLength = AdminContext.useStoreState(state => state.roles.selectedRoles.length);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(!roles.length);
|
|
||||||
clearFlashes('roles');
|
|
||||||
|
|
||||||
getRoles()
|
|
||||||
.then(roles => setRoles(roles))
|
|
||||||
.catch(error => {
|
|
||||||
console.error(error);
|
|
||||||
clearAndAddHttpError({ key: 'roles', error });
|
|
||||||
})
|
|
||||||
.then(() => setLoading(false));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onSelectAllClick = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const onSelectAllClick = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setSelectedRoles(e.currentTarget.checked ? (roles.map(role => role.id) || []) : []);
|
setSelectedRoles(e.currentTarget.checked ? (roles?.items?.map(role => role.id) || []) : []);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSearch = (query: string): Promise<void> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (query.length < 2) {
|
||||||
|
setFilters(null);
|
||||||
|
} else {
|
||||||
|
setFilters({ name: query });
|
||||||
|
}
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedRoles([]);
|
||||||
|
}, [ page ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminContentBlock title={'Roles'}>
|
<AdminContentBlock title={'Roles'}>
|
||||||
<div css={tw`w-full flex flex-row items-center mb-8`}>
|
<div css={tw`w-full flex flex-row items-center mb-8`}>
|
||||||
|
@ -77,54 +87,79 @@ export default () => {
|
||||||
<FlashMessageRender byKey={'roles'} css={tw`mb-4`}/>
|
<FlashMessageRender byKey={'roles'} css={tw`mb-4`}/>
|
||||||
|
|
||||||
<AdminTable>
|
<AdminTable>
|
||||||
{ loading ?
|
{ roles === undefined || (error && isValidating) ?
|
||||||
<Loading/>
|
<Loading/>
|
||||||
:
|
:
|
||||||
roles.length < 1 ?
|
length < 1 ?
|
||||||
<NoItems/>
|
<NoItems/>
|
||||||
:
|
:
|
||||||
<ContentWrapper
|
<ContentWrapper
|
||||||
checked={selectedRolesLength === (roles.length === 0 ? -1 : roles.length)}
|
checked={selectedRolesLength === (length === 0 ? -1 : length)}
|
||||||
onSelectAllClick={onSelectAllClick}
|
onSelectAllClick={onSelectAllClick}
|
||||||
|
onSearch={onSearch}
|
||||||
>
|
>
|
||||||
<div css={tw`overflow-x-auto`}>
|
<Pagination data={roles} onPageSelect={setPage}>
|
||||||
<table css={tw`w-full table-auto`}>
|
<div css={tw`overflow-x-auto`}>
|
||||||
<TableHead>
|
<table css={tw`w-full table-auto`}>
|
||||||
<TableHeader name={'ID'}/>
|
<TableHead>
|
||||||
<TableHeader name={'Name'}/>
|
<TableHeader name={'ID'} direction={sort === 'id' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('id')}/>
|
||||||
<TableHeader name={'Description'}/>
|
<TableHeader name={'Name'} direction={sort === 'name' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('name')}/>
|
||||||
</TableHead>
|
<TableHeader name={'Description'}/>
|
||||||
|
</TableHead>
|
||||||
|
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{
|
{
|
||||||
roles.map(role => (
|
roles.items.map(role => (
|
||||||
<TableRow key={role.id} css={role.id === roles[roles.length - 1].id ? tw`rounded-b-lg` : undefined}>
|
<TableRow key={role.id}>
|
||||||
<td css={tw`pl-6`}>
|
<td css={tw`pl-6`}>
|
||||||
<RowCheckbox id={role.id}/>
|
<RowCheckbox id={role.id}/>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td css={tw`px-6 text-sm text-neutral-200 text-left whitespace-nowrap`}>
|
<td css={tw`px-6 text-sm text-neutral-200 text-left whitespace-nowrap`}>
|
||||||
<CopyOnClick text={role.id.toString()}>
|
<CopyOnClick text={role.id.toString()}>
|
||||||
<code css={tw`font-mono bg-neutral-900 rounded py-1 px-2`}>{role.id}</code>
|
<code css={tw`font-mono bg-neutral-900 rounded py-1 px-2`}>{role.id}</code>
|
||||||
</CopyOnClick>
|
</CopyOnClick>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td css={tw`px-6 text-sm text-neutral-200 text-left whitespace-nowrap`}>
|
<td css={tw`px-6 text-sm text-neutral-200 text-left whitespace-nowrap`}>
|
||||||
<NavLink to={`${match.url}/${role.id}`} css={tw`text-primary-400 hover:text-primary-300`}>
|
<NavLink to={`${match.url}/${role.id}`} css={tw`text-primary-400 hover:text-primary-300`}>
|
||||||
{role.name}
|
{role.name}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td css={tw`px-6 text-sm text-neutral-200 text-left whitespace-nowrap`}>{role.description}</td>
|
<td css={tw`px-6 text-sm text-neutral-200 text-left whitespace-nowrap`}>{role.description}</td>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</Pagination>
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
}
|
}
|
||||||
</AdminTable>
|
</AdminTable>
|
||||||
</AdminContentBlock>
|
</AdminContentBlock>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const [ page, setPage ] = useState<number>(1);
|
||||||
|
const [ filters, setFilters ] = useState<Filters | null>(null);
|
||||||
|
const [ sort, setSortState ] = useState<string | null>(null);
|
||||||
|
const [ sortDirection, setSortDirection ] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const setSort = (newSort: string | null) => {
|
||||||
|
if (sort === newSort) {
|
||||||
|
setSortDirection(!sortDirection);
|
||||||
|
} else {
|
||||||
|
setSortState(newSort);
|
||||||
|
setSortDirection(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RolesContext.Provider value={{ page, setPage, filters, setFilters, sort, setSort, sortDirection, setSortDirection }}>
|
||||||
|
<RolesContainer/>
|
||||||
|
</RolesContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import AdminCheckbox from '@/components/admin/AdminCheckbox';
|
import AdminCheckbox from '@/components/admin/AdminCheckbox';
|
||||||
import CopyOnClick from '@/components/elements/CopyOnClick';
|
import CopyOnClick from '@/components/elements/CopyOnClick';
|
||||||
import getUsers, { Context as UsersContext } from '@/api/admin/users/getUsers';
|
import getUsers, { Context as UsersContext, Filters } from '@/api/admin/users/getUsers';
|
||||||
import AdminTable, { ContentWrapper, Loading, NoItems, Pagination, TableBody, TableHead, TableHeader } from '@/components/admin/AdminTable';
|
import AdminTable, { ContentWrapper, Loading, NoItems, Pagination, TableBody, TableHead, TableHeader } from '@/components/admin/AdminTable';
|
||||||
import Button from '@/components/elements/Button';
|
import Button from '@/components/elements/Button';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
|
@ -34,7 +34,7 @@ const RowCheckbox = ({ id }: { id: number }) => {
|
||||||
const UsersContainer = () => {
|
const UsersContainer = () => {
|
||||||
const match = useRouteMatch();
|
const match = useRouteMatch();
|
||||||
|
|
||||||
const { page, setPage } = useContext(UsersContext);
|
const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(UsersContext);
|
||||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||||
const { data: users, error, isValidating } = getUsers();
|
const { data: users, error, isValidating } = getUsers();
|
||||||
|
|
||||||
|
@ -56,6 +56,17 @@ const UsersContainer = () => {
|
||||||
setSelectedUsers(e.currentTarget.checked ? (users?.items?.map(user => user.id) || []) : []);
|
setSelectedUsers(e.currentTarget.checked ? (users?.items?.map(user => user.id) || []) : []);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSearch = (query: string): Promise<void> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (query.length < 2) {
|
||||||
|
setFilters(null);
|
||||||
|
} else {
|
||||||
|
setFilters({ username: query });
|
||||||
|
}
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedUsers([]);
|
setSelectedUsers([]);
|
||||||
}, [ page ]);
|
}, [ page ]);
|
||||||
|
@ -89,16 +100,17 @@ const UsersContainer = () => {
|
||||||
<ContentWrapper
|
<ContentWrapper
|
||||||
checked={selectedUserLength === (length === 0 ? -1 : length)}
|
checked={selectedUserLength === (length === 0 ? -1 : length)}
|
||||||
onSelectAllClick={onSelectAllClick}
|
onSelectAllClick={onSelectAllClick}
|
||||||
|
onSearch={onSearch}
|
||||||
>
|
>
|
||||||
<Pagination data={users} onPageSelect={setPage}>
|
<Pagination data={users} onPageSelect={setPage}>
|
||||||
<div css={tw`overflow-x-auto`}>
|
<div css={tw`overflow-x-auto`}>
|
||||||
<table css={tw`w-full table-auto`}>
|
<table css={tw`w-full table-auto`}>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableHeader name={'ID'}/>
|
<TableHeader name={'ID'} direction={sort === 'id' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('id')}/>
|
||||||
<TableHeader name={'Name'}/>
|
<TableHeader name={'Name'} direction={sort === 'email' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('email')}/>
|
||||||
<TableHeader name={'Username'}/>
|
<TableHeader name={'Username'} direction={sort === 'username' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('username')}/>
|
||||||
<TableHeader name={'Status'}/>
|
<TableHeader name={'Status'}/>
|
||||||
<TableHeader name={'Role'}/>
|
<TableHeader name={'Role'} direction={sort === 'admin_role_id' ? (sortDirection ? 1 : 2) : null} onClick={() => setSort('admin_role_id')}/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
@ -160,9 +172,21 @@ const UsersContainer = () => {
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [ page, setPage ] = useState<number>(1);
|
const [ page, setPage ] = useState<number>(1);
|
||||||
|
const [ filters, setFilters ] = useState<Filters | null>(null);
|
||||||
|
const [ sort, setSortState ] = useState<string | null>(null);
|
||||||
|
const [ sortDirection, setSortDirection ] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const setSort = (newSort: string | null) => {
|
||||||
|
if (sort === newSort) {
|
||||||
|
setSortDirection(!sortDirection);
|
||||||
|
} else {
|
||||||
|
setSortState(newSort);
|
||||||
|
setSortDirection(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UsersContext.Provider value={{ page, setPage }}>
|
<UsersContext.Provider value={{ page, setPage, filters, setFilters, sort, setSort, sortDirection, setSortDirection }}>
|
||||||
<UsersContainer/>
|
<UsersContainer/>
|
||||||
</UsersContext.Provider>
|
</UsersContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
import { CSSObject } from '@emotion/serialize';
|
||||||
|
import { ContainerProps, ControlProps, InputProps, MenuProps, MultiValueProps, OptionProps, PlaceholderProps, SingleValueProps, StylesConfig, ValueContainerProps } from 'react-select';
|
||||||
|
import { theme } from 'twin.macro';
|
||||||
|
|
||||||
|
type T = any;
|
||||||
|
|
||||||
|
export const SelectStyle: StylesConfig<T, any, any> = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
container: (base: CSSObject, props: ContainerProps<T, any, any>): CSSObject => {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
control: (base: CSSObject, props: ControlProps<T, any, any>): CSSObject => {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
height: '2.75rem',
|
||||||
|
/* paddingTop: '0.75rem',
|
||||||
|
paddingBottom: '0.75rem',
|
||||||
|
paddingLeft: '4rem',
|
||||||
|
paddingRight: '4rem', */
|
||||||
|
background: theme`colors.neutral.600`,
|
||||||
|
borderColor: theme`colors.neutral.500`,
|
||||||
|
borderWidth: '2px',
|
||||||
|
color: theme`colors.neutral.200`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
input: (base: CSSObject, props: InputProps): CSSObject => {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
color: theme`colors.neutral.200`,
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
menu: (base: CSSObject, props: MenuProps<T, any, any>): CSSObject => {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
background: theme`colors.neutral.900`,
|
||||||
|
color: theme`colors.neutral.200`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
multiValue: (base: CSSObject, props: MultiValueProps<T, any>): CSSObject => {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
background: theme`colors.neutral.900`,
|
||||||
|
color: theme`colors.neutral.200`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
multiValueLabel: (base: CSSObject, props: MultiValueProps<T, any>): CSSObject => {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
color: theme`colors.neutral.200`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
option: (base: CSSObject, props: OptionProps<T, any, any>): CSSObject => {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
background: theme`colors.neutral.900`,
|
||||||
|
':hover': {
|
||||||
|
background: theme`colors.neutral.700`,
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
placeholder: (base: CSSObject, props: PlaceholderProps<T, any, any>): CSSObject => {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
color: theme`colors.neutral.300`,
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
singleValue: (base: CSSObject, props: SingleValueProps<T, any>): CSSObject => {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
color: '#00000',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
valueContainer: (base: CSSObject, props: ValueContainerProps<T, any>): CSSObject => {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
Loading…
Reference in New Issue