Start working towards a consistent model/transformer layout
This commit is contained in:
parent
9f934b5ab8
commit
a00fee5516
17 changed files with 303 additions and 267 deletions
|
@ -150,6 +150,7 @@
|
||||||
"svg-url-loader": "^7.1.1",
|
"svg-url-loader": "^7.1.1",
|
||||||
"tailwindcss": "^3.0.23",
|
"tailwindcss": "^3.0.23",
|
||||||
"terser-webpack-plugin": "^4.2.3",
|
"terser-webpack-plugin": "^4.2.3",
|
||||||
|
"ts-essentials": "^9.1.2",
|
||||||
"twin.macro": "^2.8.2",
|
"twin.macro": "^2.8.2",
|
||||||
"typescript": "^4.4.4",
|
"typescript": "^4.4.4",
|
||||||
"webpack": "^4.46.0",
|
"webpack": "^4.46.0",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Model, UUID, WithRelationships, withRelationships } from '@/api/admin/index';
|
import { Model, UUID, WithRelationships, withRelationships } from '@/api/admin/index';
|
||||||
import { Nest } from '@/api/admin/nest';
|
import { Nest } from '@/api/admin/nest';
|
||||||
import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http';
|
import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http';
|
||||||
import { AdminTransformers } from '@/api/admin/transformers';
|
import Transformers from '@definitions/admin/transformers';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { useRouteMatch } from 'react-router-dom';
|
import { useRouteMatch } from 'react-router-dom';
|
||||||
import useSWR, { SWRResponse } from 'swr';
|
import useSWR, { SWRResponse } from 'swr';
|
||||||
|
@ -64,7 +64,7 @@ export const getEgg = async (id: number | string): Promise<LoadedEgg> => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return withRelationships(AdminTransformers.toEgg(data), 'nest', 'variables');
|
return withRelationships(Transformers.toEgg(data), 'nest', 'variables');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const searchEggs = async (nestId: number, params: QueryBuilderParams<'name'>): Promise<WithRelationships<Egg, 'variables'>[]> => {
|
export const searchEggs = async (nestId: number, params: QueryBuilderParams<'name'>): Promise<WithRelationships<Egg, 'variables'>[]> => {
|
||||||
|
@ -75,7 +75,7 @@ export const searchEggs = async (nestId: number, params: QueryBuilderParams<'nam
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return data.data.map(AdminTransformers.toEgg);
|
return data.data.map(Transformers.toEgg);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const exportEgg = async (eggId: number): Promise<Record<string, any>> => {
|
export const exportEgg = async (eggId: number): Promise<Record<string, any>> => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import http from '@/api/http';
|
import http from '@/api/http';
|
||||||
import { EggVariable } from '@/api/admin/egg';
|
import { EggVariable } from '@/api/admin/egg';
|
||||||
import { AdminTransformers } from '@/api/admin/transformers';
|
import Transformers from '@definitions/admin/transformers';
|
||||||
|
|
||||||
export type CreateEggVariable = Omit<EggVariable, 'id' | 'eggId' | 'createdAt' | 'updatedAt' | 'relationships'>;
|
export type CreateEggVariable = Omit<EggVariable, 'id' | 'eggId' | 'createdAt' | 'updatedAt' | 'relationships'>;
|
||||||
|
|
||||||
|
@ -18,5 +18,5 @@ export default async (eggId: number, variable: CreateEggVariable): Promise<EggVa
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return AdminTransformers.toEggVariable(data);
|
return Transformers.toEggVariable(data);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import http from '@/api/http';
|
import http from '@/api/http';
|
||||||
import { EggVariable } from '@/api/admin/egg';
|
import { EggVariable } from '@/api/admin/egg';
|
||||||
import { AdminTransformers } from '@/api/admin/transformers';
|
import Transformers from '@definitions/admin/transformers';
|
||||||
|
|
||||||
export default async (eggId: number, variables: Omit<EggVariable, 'eggId' | 'createdAt' | 'updatedAt'>[]): Promise<EggVariable[]> => {
|
export default async (eggId: number, variables: Omit<EggVariable, 'eggId' | 'createdAt' | 'updatedAt'>[]): Promise<EggVariable[]> => {
|
||||||
const { data } = await http.patch(
|
const { data } = await http.patch(
|
||||||
|
@ -17,5 +17,5 @@ export default async (eggId: number, variables: Omit<EggVariable, 'eggId' | 'cre
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
return data.data.map(AdminTransformers.toEggVariable);
|
return data.data.map(Transformers.toEggVariable);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Model, UUID } from '@/api/admin/index';
|
import { Model, UUID } from '@/api/admin/index';
|
||||||
import { Egg } from '@/api/admin/egg';
|
import { Egg } from '@/api/admin/egg';
|
||||||
import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http';
|
import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http';
|
||||||
import { AdminTransformers } from '@/api/admin/transformers';
|
import Transformers from '@definitions/admin/transformers';
|
||||||
|
|
||||||
export interface Nest extends Model {
|
export interface Nest extends Model {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -21,5 +21,5 @@ export const searchNests = async (params: QueryBuilderParams<'name'>): Promise<N
|
||||||
params: withQueryBuilderParams(params),
|
params: withQueryBuilderParams(params),
|
||||||
});
|
});
|
||||||
|
|
||||||
return data.data.map(AdminTransformers.toNest);
|
return data.data.map(Transformers.toNest);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Model, UUID, WithRelationships, withRelationships } from '@/api/admin/index';
|
import { Model, UUID, WithRelationships, withRelationships } from '@/api/admin/index';
|
||||||
import { Location } from '@/api/admin/location';
|
import { Location } from '@/api/admin/location';
|
||||||
import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http';
|
import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http';
|
||||||
import { AdminTransformers } from '@/api/admin/transformers';
|
import Transformers from '@definitions/admin/transformers';
|
||||||
import { Server } from '@/api/admin/server';
|
import { Server } from '@/api/admin/server';
|
||||||
|
|
||||||
interface NodePorts {
|
interface NodePorts {
|
||||||
|
@ -64,7 +64,7 @@ export const getNode = async (id: string | number): Promise<WithRelationships<No
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return withRelationships(AdminTransformers.toNode(data.data), 'location');
|
return withRelationships(Transformers.toNode(data.data), 'location');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const searchNodes = async (params: QueryBuilderParams<'name'>): Promise<Node[]> => {
|
export const searchNodes = async (params: QueryBuilderParams<'name'>): Promise<Node[]> => {
|
||||||
|
@ -72,7 +72,7 @@ export const searchNodes = async (params: QueryBuilderParams<'name'>): Promise<N
|
||||||
params: withQueryBuilderParams(params),
|
params: withQueryBuilderParams(params),
|
||||||
});
|
});
|
||||||
|
|
||||||
return data.data.map(AdminTransformers.toNode);
|
return data.data.map(Transformers.toNode);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAllocations = async (id: string | number, params?: QueryBuilderParams<'ip' | 'server_id'>): Promise<Allocation[]> => {
|
export const getAllocations = async (id: string | number, params?: QueryBuilderParams<'ip' | 'server_id'>): Promise<Allocation[]> => {
|
||||||
|
@ -80,5 +80,5 @@ export const getAllocations = async (id: string | number, params?: QueryBuilderP
|
||||||
params: withQueryBuilderParams(params),
|
params: withQueryBuilderParams(params),
|
||||||
});
|
});
|
||||||
|
|
||||||
return data.data.map(AdminTransformers.toAllocation);
|
return data.data.map(Transformers.toAllocation);
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,9 +3,9 @@ import { AxiosError } from 'axios';
|
||||||
import { useRouteMatch } from 'react-router-dom';
|
import { useRouteMatch } from 'react-router-dom';
|
||||||
import http from '@/api/http';
|
import http from '@/api/http';
|
||||||
import { Model, UUID, withRelationships, WithRelationships } from '@/api/admin/index';
|
import { Model, UUID, withRelationships, WithRelationships } from '@/api/admin/index';
|
||||||
import { AdminTransformers } from '@/api/admin/transformers';
|
import Transformers from '@definitions/admin/transformers';
|
||||||
import { Allocation, Node } from '@/api/admin/node';
|
import { Allocation, Node } from '@/api/admin/node';
|
||||||
import { User } from '@/api/admin/user';
|
import { User } from '@definitions/admin/models';
|
||||||
import { Egg, EggVariable } from '@/api/admin/egg';
|
import { Egg, EggVariable } from '@/api/admin/egg';
|
||||||
import { Nest } from '@/api/admin/nest';
|
import { Nest } from '@/api/admin/nest';
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ export const getServer = async (id: number | string): Promise<LoadedServer> => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return withRelationships(AdminTransformers.toServer(data), 'allocations', 'user', 'node', 'variables');
|
return withRelationships(Transformers.toServer(data), 'allocations', 'user', 'node', 'variables');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,212 +0,0 @@
|
||||||
/* eslint-disable camelcase */
|
|
||||||
import { Allocation, Node } from '@/api/admin/node';
|
|
||||||
import { Server, ServerVariable } from '@/api/admin/server';
|
|
||||||
import { FractalResponseData, FractalResponseList } from '@/api/http';
|
|
||||||
import { User, UserRole } from '@/api/admin/user';
|
|
||||||
import { Location } from '@/api/admin/location';
|
|
||||||
import { Egg, EggVariable } from '@/api/admin/egg';
|
|
||||||
import { Nest } from '@/api/admin/nest';
|
|
||||||
|
|
||||||
const isList = (data: FractalResponseList | FractalResponseData): data is FractalResponseList => data.object === 'list';
|
|
||||||
|
|
||||||
function transform<T, M = undefined> (data: undefined, transformer: (callback: FractalResponseData) => T, missing?: M): undefined;
|
|
||||||
function transform<T, M> (data: FractalResponseData | undefined, transformer: (callback: FractalResponseData) => T, missing?: M): T | M | undefined;
|
|
||||||
function transform<T, M> (data: FractalResponseList | undefined, transformer: (callback: FractalResponseData) => T, missing?: M): T[] | undefined;
|
|
||||||
function transform<T> (data: FractalResponseData | FractalResponseList | undefined, transformer: (callback: FractalResponseData) => T, missing = undefined) {
|
|
||||||
if (data === undefined) return undefined;
|
|
||||||
|
|
||||||
if (isList(data)) {
|
|
||||||
return data.data.map(transformer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return !data ? missing : transformer(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AdminTransformers {
|
|
||||||
static toServer = ({ attributes }: FractalResponseData): Server => {
|
|
||||||
const { oom_disabled, ...limits } = attributes.limits;
|
|
||||||
const { allocations, egg, nest, node, user, variables } = attributes.relationships || {};
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: attributes.id,
|
|
||||||
uuid: attributes.uuid,
|
|
||||||
externalId: attributes.external_id,
|
|
||||||
identifier: attributes.identifier,
|
|
||||||
name: attributes.name,
|
|
||||||
description: attributes.description,
|
|
||||||
status: attributes.status,
|
|
||||||
userId: attributes.owner_id,
|
|
||||||
nodeId: attributes.node_id,
|
|
||||||
allocationId: attributes.allocation_id,
|
|
||||||
eggId: attributes.egg_id,
|
|
||||||
nestId: attributes.nest_id,
|
|
||||||
limits: { ...limits, oomDisabled: oom_disabled },
|
|
||||||
featureLimits: attributes.feature_limits,
|
|
||||||
container: attributes.container,
|
|
||||||
createdAt: new Date(attributes.created_at),
|
|
||||||
updatedAt: new Date(attributes.updated_at),
|
|
||||||
relationships: {
|
|
||||||
allocations: transform(allocations as FractalResponseList | undefined, this.toAllocation),
|
|
||||||
nest: transform(nest as FractalResponseData | undefined, this.toNest),
|
|
||||||
egg: transform(egg as FractalResponseData | undefined, this.toEgg),
|
|
||||||
node: transform(node as FractalResponseData | undefined, this.toNode),
|
|
||||||
user: transform(user as FractalResponseData | undefined, this.toUser),
|
|
||||||
variables: transform(variables as FractalResponseList | undefined, this.toServerEggVariable),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
static toNode = ({ attributes }: FractalResponseData): Node => {
|
|
||||||
return {
|
|
||||||
id: attributes.id,
|
|
||||||
uuid: attributes.uuid,
|
|
||||||
isPublic: attributes.public,
|
|
||||||
locationId: attributes.location_id,
|
|
||||||
databaseHostId: attributes.database_host_id,
|
|
||||||
name: attributes.name,
|
|
||||||
description: attributes.description,
|
|
||||||
fqdn: attributes.fqdn,
|
|
||||||
ports: {
|
|
||||||
http: {
|
|
||||||
public: attributes.publicPortHttp,
|
|
||||||
listen: attributes.listenPortHttp,
|
|
||||||
},
|
|
||||||
sftp: {
|
|
||||||
public: attributes.publicPortSftp,
|
|
||||||
listen: attributes.listenPortSftp,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scheme: attributes.scheme,
|
|
||||||
isBehindProxy: attributes.behindProxy,
|
|
||||||
isMaintenanceMode: attributes.maintenance_mode,
|
|
||||||
memory: attributes.memory,
|
|
||||||
memoryOverallocate: attributes.memory_overallocate,
|
|
||||||
disk: attributes.disk,
|
|
||||||
diskOverallocate: attributes.disk_overallocate,
|
|
||||||
uploadSize: attributes.upload_size,
|
|
||||||
daemonBase: attributes.daemonBase,
|
|
||||||
createdAt: new Date(attributes.created_at),
|
|
||||||
updatedAt: new Date(attributes.updated_at),
|
|
||||||
relationships: {
|
|
||||||
location: transform(attributes.relationships?.location as FractalResponseData, this.toLocation),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
static toUserRole = ({ attributes }: FractalResponseData): UserRole => ({
|
|
||||||
id: attributes.id,
|
|
||||||
name: attributes.name,
|
|
||||||
description: attributes.description,
|
|
||||||
relationships: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
static toUser = ({ attributes }: FractalResponseData): User => {
|
|
||||||
return {
|
|
||||||
id: attributes.id,
|
|
||||||
uuid: attributes.uuid,
|
|
||||||
externalId: attributes.external_id,
|
|
||||||
username: attributes.username,
|
|
||||||
email: attributes.email,
|
|
||||||
language: attributes.language,
|
|
||||||
adminRoleId: attributes.adminRoleId || null,
|
|
||||||
roleName: attributes.role_name,
|
|
||||||
isRootAdmin: attributes.root_admin,
|
|
||||||
isUsingTwoFactor: attributes['2fa'] || false,
|
|
||||||
avatarUrl: attributes.avatar_url,
|
|
||||||
createdAt: new Date(attributes.created_at),
|
|
||||||
updatedAt: new Date(attributes.updated_at),
|
|
||||||
relationships: {
|
|
||||||
role: transform(attributes.relationships?.role as FractalResponseData, this.toUserRole) || null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
static toLocation = ({ attributes }: FractalResponseData): Location => ({
|
|
||||||
id: attributes.id,
|
|
||||||
short: attributes.short,
|
|
||||||
long: attributes.long,
|
|
||||||
createdAt: new Date(attributes.created_at),
|
|
||||||
updatedAt: new Date(attributes.updated_at),
|
|
||||||
relationships: {
|
|
||||||
nodes: transform(attributes.relationships?.node as FractalResponseList, this.toNode),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
static toEgg = ({ attributes }: FractalResponseData): Egg => ({
|
|
||||||
id: attributes.id,
|
|
||||||
uuid: attributes.uuid,
|
|
||||||
nestId: attributes.nest_id,
|
|
||||||
author: attributes.author,
|
|
||||||
name: attributes.name,
|
|
||||||
description: attributes.description,
|
|
||||||
features: attributes.features,
|
|
||||||
dockerImages: attributes.docker_images,
|
|
||||||
configFiles: attributes.config?.files,
|
|
||||||
configStartup: attributes.config?.startup,
|
|
||||||
configStop: attributes.config?.stop,
|
|
||||||
configFrom: attributes.config?.extends,
|
|
||||||
startup: attributes.startup,
|
|
||||||
copyScriptFrom: attributes.copy_script_from,
|
|
||||||
scriptContainer: attributes.script?.container,
|
|
||||||
scriptEntry: attributes.script?.entry,
|
|
||||||
scriptIsPrivileged: attributes.script?.privileged,
|
|
||||||
scriptInstall: attributes.script?.install,
|
|
||||||
createdAt: new Date(attributes.created_at),
|
|
||||||
updatedAt: new Date(attributes.updated_at),
|
|
||||||
relationships: {
|
|
||||||
nest: transform(attributes.relationships?.nest as FractalResponseData, this.toNest),
|
|
||||||
variables: transform(attributes.relationships?.variables as FractalResponseList, this.toEggVariable),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
static toEggVariable = ({ attributes }: FractalResponseData): EggVariable => ({
|
|
||||||
id: attributes.id,
|
|
||||||
eggId: attributes.egg_id,
|
|
||||||
name: attributes.name,
|
|
||||||
description: attributes.description,
|
|
||||||
environmentVariable: attributes.env_variable,
|
|
||||||
defaultValue: attributes.default_value,
|
|
||||||
isUserViewable: attributes.user_viewable,
|
|
||||||
isUserEditable: attributes.user_editable,
|
|
||||||
// isRequired: attributes.required,
|
|
||||||
rules: attributes.rules,
|
|
||||||
createdAt: new Date(attributes.created_at),
|
|
||||||
updatedAt: new Date(attributes.updated_at),
|
|
||||||
relationships: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
static toServerEggVariable = (data: FractalResponseData): ServerVariable => ({
|
|
||||||
...this.toEggVariable(data),
|
|
||||||
serverValue: data.attributes.server_value,
|
|
||||||
});
|
|
||||||
|
|
||||||
static toAllocation = ({ attributes }: FractalResponseData): Allocation => ({
|
|
||||||
id: attributes.id,
|
|
||||||
ip: attributes.ip,
|
|
||||||
port: attributes.port,
|
|
||||||
alias: attributes.alias || null,
|
|
||||||
isAssigned: attributes.assigned,
|
|
||||||
relationships: {
|
|
||||||
node: transform(attributes.relationships?.node as FractalResponseData, this.toNode),
|
|
||||||
server: transform(attributes.relationships?.server as FractalResponseData, this.toServer),
|
|
||||||
},
|
|
||||||
getDisplayText (): string {
|
|
||||||
const raw = `${this.ip}:${this.port}`;
|
|
||||||
|
|
||||||
return !this.alias ? raw : `${this.alias} (${raw})`;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
static toNest = ({ attributes }: FractalResponseData): Nest => ({
|
|
||||||
id: attributes.id,
|
|
||||||
uuid: attributes.uuid,
|
|
||||||
author: attributes.author,
|
|
||||||
name: attributes.name,
|
|
||||||
description: attributes.description,
|
|
||||||
createdAt: new Date(attributes.created_at),
|
|
||||||
updatedAt: new Date(attributes.updated_at),
|
|
||||||
relationships: {
|
|
||||||
eggs: transform(attributes.relationships?.eggs as FractalResponseList, this.toEgg),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,38 +1,11 @@
|
||||||
import { Model, UUID } from '@/api/admin/index';
|
|
||||||
import { Server } from '@/api/admin/server';
|
|
||||||
import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http';
|
import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http';
|
||||||
import { AdminTransformers } from '@/api/admin/transformers';
|
import Transformers from '@definitions/admin/transformers';
|
||||||
|
import { User } from '@definitions/admin/models';
|
||||||
export interface User extends Model {
|
|
||||||
id: number;
|
|
||||||
uuid: UUID;
|
|
||||||
externalId: string;
|
|
||||||
username: string;
|
|
||||||
email: string;
|
|
||||||
language: string;
|
|
||||||
adminRoleId: number | null;
|
|
||||||
roleName: string;
|
|
||||||
isRootAdmin: boolean;
|
|
||||||
isUsingTwoFactor: boolean;
|
|
||||||
avatarUrl: string;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
relationships: {
|
|
||||||
role: UserRole | null;
|
|
||||||
servers?: Server[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserRole extends Model {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getUser = async (id: string | number): Promise<User> => {
|
export const getUser = async (id: string | number): Promise<User> => {
|
||||||
const { data } = await http.get(`/api/application/users/${id}`);
|
const { data } = await http.get(`/api/application/users/${id}`);
|
||||||
|
|
||||||
return AdminTransformers.toUser(data.data);
|
return Transformers.toUser(data.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const searchUserAccounts = async (params: QueryBuilderParams<'username' | 'email'>): Promise<User[]> => {
|
export const searchUserAccounts = async (params: QueryBuilderParams<'username' | 'email'>): Promise<User[]> => {
|
||||||
|
@ -40,5 +13,5 @@ export const searchUserAccounts = async (params: QueryBuilderParams<'username' |
|
||||||
params: withQueryBuilderParams(params),
|
params: withQueryBuilderParams(params),
|
||||||
});
|
});
|
||||||
|
|
||||||
return data.data.map(AdminTransformers.toUser);
|
return data.data.map(Transformers.toUser);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { Model as BaseModel, UUID } from '@/api/definitions';
|
||||||
|
import { Server } from '@/api/admin/server';
|
||||||
|
import { MarkRequired } from 'ts-essentials';
|
||||||
|
|
||||||
|
interface Model extends BaseModel {
|
||||||
|
relationships: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows a model to have optional relationships that are marked as being
|
||||||
|
* present in a given pathway. This allows different API calls to specify the
|
||||||
|
* "completeness" of a response object without having to make every API return
|
||||||
|
* the same information, or every piece of logic do explicit null checking.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* >> const user: WithLoadedRelations<User, 'servers'> = {};
|
||||||
|
* >> // "user.servers" is no longer potentially undefined.
|
||||||
|
*/
|
||||||
|
type WithLoadedRelations<M extends Model, R extends keyof M['relationships']> = M & {
|
||||||
|
relationships: MarkRequired<M['relationships'], R>;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface User extends Model {
|
||||||
|
id: number;
|
||||||
|
uuid: UUID;
|
||||||
|
externalId: string;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
language: string;
|
||||||
|
adminRoleId: number | null;
|
||||||
|
roleName: string;
|
||||||
|
isRootAdmin: boolean;
|
||||||
|
isUsingTwoFactor: boolean;
|
||||||
|
avatarUrl: string;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
relationships: {
|
||||||
|
role: UserRole | null;
|
||||||
|
// TODO: just use an API call, this is probably a bad idea for performance.
|
||||||
|
servers?: Server[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserRole extends Model {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
|
@ -0,0 +1,212 @@
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
import { Allocation, Node } from '@/api/admin/node';
|
||||||
|
import { Server, ServerVariable } from '@/api/admin/server';
|
||||||
|
import { FractalResponseData, FractalResponseList } from '@/api/http';
|
||||||
|
import * as Models from '@definitions/admin/models';
|
||||||
|
import { Location } from '@/api/admin/location';
|
||||||
|
import { Egg, EggVariable } from '@/api/admin/egg';
|
||||||
|
import { Nest } from '@/api/admin/nest';
|
||||||
|
|
||||||
|
const isList = (data: FractalResponseList | FractalResponseData): data is FractalResponseList => data.object === 'list';
|
||||||
|
|
||||||
|
function transform<T, M = undefined> (data: undefined, transformer: (callback: FractalResponseData) => T, missing?: M): undefined;
|
||||||
|
function transform<T, M> (data: FractalResponseData | undefined, transformer: (callback: FractalResponseData) => T, missing?: M): T | M | undefined;
|
||||||
|
function transform<T, M> (data: FractalResponseList | undefined, transformer: (callback: FractalResponseData) => T, missing?: M): T[] | undefined;
|
||||||
|
function transform<T> (data: FractalResponseData | FractalResponseList | undefined, transformer: (callback: FractalResponseData) => T, missing = undefined) {
|
||||||
|
if (data === undefined) return undefined;
|
||||||
|
|
||||||
|
if (isList(data)) {
|
||||||
|
return data.data.map(transformer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !data ? missing : transformer(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Transformers {
|
||||||
|
static toServer = ({ attributes }: FractalResponseData): Server => {
|
||||||
|
const { oom_disabled, ...limits } = attributes.limits;
|
||||||
|
const { allocations, egg, nest, node, user, variables } = attributes.relationships || {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: attributes.id,
|
||||||
|
uuid: attributes.uuid,
|
||||||
|
externalId: attributes.external_id,
|
||||||
|
identifier: attributes.identifier,
|
||||||
|
name: attributes.name,
|
||||||
|
description: attributes.description,
|
||||||
|
status: attributes.status,
|
||||||
|
userId: attributes.owner_id,
|
||||||
|
nodeId: attributes.node_id,
|
||||||
|
allocationId: attributes.allocation_id,
|
||||||
|
eggId: attributes.egg_id,
|
||||||
|
nestId: attributes.nest_id,
|
||||||
|
limits: { ...limits, oomDisabled: oom_disabled },
|
||||||
|
featureLimits: attributes.feature_limits,
|
||||||
|
container: attributes.container,
|
||||||
|
createdAt: new Date(attributes.created_at),
|
||||||
|
updatedAt: new Date(attributes.updated_at),
|
||||||
|
relationships: {
|
||||||
|
allocations: transform(allocations as FractalResponseList | undefined, this.toAllocation),
|
||||||
|
nest: transform(nest as FractalResponseData | undefined, this.toNest),
|
||||||
|
egg: transform(egg as FractalResponseData | undefined, this.toEgg),
|
||||||
|
node: transform(node as FractalResponseData | undefined, this.toNode),
|
||||||
|
user: transform(user as FractalResponseData | undefined, this.toUser),
|
||||||
|
variables: transform(variables as FractalResponseList | undefined, this.toServerEggVariable),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static toNode = ({ attributes }: FractalResponseData): Node => {
|
||||||
|
return {
|
||||||
|
id: attributes.id,
|
||||||
|
uuid: attributes.uuid,
|
||||||
|
isPublic: attributes.public,
|
||||||
|
locationId: attributes.location_id,
|
||||||
|
databaseHostId: attributes.database_host_id,
|
||||||
|
name: attributes.name,
|
||||||
|
description: attributes.description,
|
||||||
|
fqdn: attributes.fqdn,
|
||||||
|
ports: {
|
||||||
|
http: {
|
||||||
|
public: attributes.publicPortHttp,
|
||||||
|
listen: attributes.listenPortHttp,
|
||||||
|
},
|
||||||
|
sftp: {
|
||||||
|
public: attributes.publicPortSftp,
|
||||||
|
listen: attributes.listenPortSftp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scheme: attributes.scheme,
|
||||||
|
isBehindProxy: attributes.behindProxy,
|
||||||
|
isMaintenanceMode: attributes.maintenance_mode,
|
||||||
|
memory: attributes.memory,
|
||||||
|
memoryOverallocate: attributes.memory_overallocate,
|
||||||
|
disk: attributes.disk,
|
||||||
|
diskOverallocate: attributes.disk_overallocate,
|
||||||
|
uploadSize: attributes.upload_size,
|
||||||
|
daemonBase: attributes.daemonBase,
|
||||||
|
createdAt: new Date(attributes.created_at),
|
||||||
|
updatedAt: new Date(attributes.updated_at),
|
||||||
|
relationships: {
|
||||||
|
location: transform(attributes.relationships?.location as FractalResponseData, this.toLocation),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static toUserRole = ({ attributes }: FractalResponseData): Models.UserRole => ({
|
||||||
|
id: attributes.id,
|
||||||
|
name: attributes.name,
|
||||||
|
description: attributes.description,
|
||||||
|
relationships: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
static toUser = ({ attributes }: FractalResponseData): Models.User => {
|
||||||
|
return {
|
||||||
|
id: attributes.id,
|
||||||
|
uuid: attributes.uuid,
|
||||||
|
externalId: attributes.external_id,
|
||||||
|
username: attributes.username,
|
||||||
|
email: attributes.email,
|
||||||
|
language: attributes.language,
|
||||||
|
adminRoleId: attributes.adminRoleId || null,
|
||||||
|
roleName: attributes.role_name,
|
||||||
|
isRootAdmin: attributes.root_admin,
|
||||||
|
isUsingTwoFactor: attributes['2fa'] || false,
|
||||||
|
avatarUrl: attributes.avatar_url,
|
||||||
|
createdAt: new Date(attributes.created_at),
|
||||||
|
updatedAt: new Date(attributes.updated_at),
|
||||||
|
relationships: {
|
||||||
|
role: transform(attributes.relationships?.role as FractalResponseData, this.toUserRole) || null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static toLocation = ({ attributes }: FractalResponseData): Location => ({
|
||||||
|
id: attributes.id,
|
||||||
|
short: attributes.short,
|
||||||
|
long: attributes.long,
|
||||||
|
createdAt: new Date(attributes.created_at),
|
||||||
|
updatedAt: new Date(attributes.updated_at),
|
||||||
|
relationships: {
|
||||||
|
nodes: transform(attributes.relationships?.node as FractalResponseList, this.toNode),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
static toEgg = ({ attributes }: FractalResponseData): Egg => ({
|
||||||
|
id: attributes.id,
|
||||||
|
uuid: attributes.uuid,
|
||||||
|
nestId: attributes.nest_id,
|
||||||
|
author: attributes.author,
|
||||||
|
name: attributes.name,
|
||||||
|
description: attributes.description,
|
||||||
|
features: attributes.features,
|
||||||
|
dockerImages: attributes.docker_images,
|
||||||
|
configFiles: attributes.config?.files,
|
||||||
|
configStartup: attributes.config?.startup,
|
||||||
|
configStop: attributes.config?.stop,
|
||||||
|
configFrom: attributes.config?.extends,
|
||||||
|
startup: attributes.startup,
|
||||||
|
copyScriptFrom: attributes.copy_script_from,
|
||||||
|
scriptContainer: attributes.script?.container,
|
||||||
|
scriptEntry: attributes.script?.entry,
|
||||||
|
scriptIsPrivileged: attributes.script?.privileged,
|
||||||
|
scriptInstall: attributes.script?.install,
|
||||||
|
createdAt: new Date(attributes.created_at),
|
||||||
|
updatedAt: new Date(attributes.updated_at),
|
||||||
|
relationships: {
|
||||||
|
nest: transform(attributes.relationships?.nest as FractalResponseData, this.toNest),
|
||||||
|
variables: transform(attributes.relationships?.variables as FractalResponseList, this.toEggVariable),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
static toEggVariable = ({ attributes }: FractalResponseData): EggVariable => ({
|
||||||
|
id: attributes.id,
|
||||||
|
eggId: attributes.egg_id,
|
||||||
|
name: attributes.name,
|
||||||
|
description: attributes.description,
|
||||||
|
environmentVariable: attributes.env_variable,
|
||||||
|
defaultValue: attributes.default_value,
|
||||||
|
isUserViewable: attributes.user_viewable,
|
||||||
|
isUserEditable: attributes.user_editable,
|
||||||
|
// isRequired: attributes.required,
|
||||||
|
rules: attributes.rules,
|
||||||
|
createdAt: new Date(attributes.created_at),
|
||||||
|
updatedAt: new Date(attributes.updated_at),
|
||||||
|
relationships: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
static toServerEggVariable = (data: FractalResponseData): ServerVariable => ({
|
||||||
|
...this.toEggVariable(data),
|
||||||
|
serverValue: data.attributes.server_value,
|
||||||
|
});
|
||||||
|
|
||||||
|
static toAllocation = ({ attributes }: FractalResponseData): Allocation => ({
|
||||||
|
id: attributes.id,
|
||||||
|
ip: attributes.ip,
|
||||||
|
port: attributes.port,
|
||||||
|
alias: attributes.alias || null,
|
||||||
|
isAssigned: attributes.assigned,
|
||||||
|
relationships: {
|
||||||
|
node: transform(attributes.relationships?.node as FractalResponseData, this.toNode),
|
||||||
|
server: transform(attributes.relationships?.server as FractalResponseData, this.toServer),
|
||||||
|
},
|
||||||
|
getDisplayText (): string {
|
||||||
|
const raw = `${this.ip}:${this.port}`;
|
||||||
|
|
||||||
|
return !this.alias ? raw : `${this.alias} (${raw})`;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
static toNest = ({ attributes }: FractalResponseData): Nest => ({
|
||||||
|
id: attributes.id,
|
||||||
|
uuid: attributes.uuid,
|
||||||
|
author: attributes.author,
|
||||||
|
name: attributes.name,
|
||||||
|
description: attributes.description,
|
||||||
|
createdAt: new Date(attributes.created_at),
|
||||||
|
updatedAt: new Date(attributes.updated_at),
|
||||||
|
relationships: {
|
||||||
|
eggs: transform(attributes.relationships?.eggs as FractalResponseList, this.toEgg),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
4
resources/scripts/api/definitions/index.d.ts
vendored
Normal file
4
resources/scripts/api/definitions/index.d.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
|
export interface Model {}
|
||||||
|
|
||||||
|
export type UUID = string;
|
|
@ -1,8 +1,7 @@
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
import { Model, UUID } from '@/api/definitions';
|
||||||
export interface Model {}
|
|
||||||
|
|
||||||
interface SecurityKey extends Model {
|
interface SecurityKey extends Model {
|
||||||
uuid: string;
|
uuid: UUID;
|
||||||
name: string;
|
name: string;
|
||||||
type: 'public-key';
|
type: 'public-key';
|
||||||
publicKeyId: string;
|
publicKeyId: string;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import SearchableSelect, { Option } from '@/components/elements/SearchableSelect';
|
import SearchableSelect, { Option } from '@/components/elements/SearchableSelect';
|
||||||
import { User, searchUserAccounts } from '@/api/admin/user';
|
import { User } from '@definitions/admin/models';
|
||||||
|
import { searchUserAccounts } from '@/api/admin/user';
|
||||||
|
|
||||||
export default ({ selected }: { selected?: User }) => {
|
export default ({ selected }: { selected?: User }) => {
|
||||||
const { setFieldValue } = useFormikContext();
|
const { setFieldValue } = useFormikContext();
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Checkbox } from '@/components/elements/inputs';
|
||||||
import { Dropdown } from '@/components/elements/dropdown';
|
import { Dropdown } from '@/components/elements/dropdown';
|
||||||
import { BanIcon, DotsVerticalIcon, LockOpenIcon, PencilIcon, SupportIcon, TrashIcon } from '@heroicons/react/solid';
|
import { BanIcon, DotsVerticalIcon, LockOpenIcon, PencilIcon, SupportIcon, TrashIcon } from '@heroicons/react/solid';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { User } from '@/api/admin/user';
|
import { User } from '@definitions/admin/models';
|
||||||
import { Dialog } from '@/components/elements/dialog';
|
import { Dialog } from '@/components/elements/dialog';
|
||||||
import { Button } from '@/components/elements/button/index';
|
import { Button } from '@/components/elements/button/index';
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import http from '@/api/http';
|
import http from '@/api/http';
|
||||||
import { User } from '@/api/admin/user';
|
import { User } from '@definitions/admin/models';
|
||||||
import { AdminTransformers } from '@/api/admin/transformers';
|
import Transformers from '@definitions/admin/transformers';
|
||||||
import { LockOpenIcon, PlusIcon, SupportIcon, TrashIcon } from '@heroicons/react/solid';
|
import { LockOpenIcon, PlusIcon, SupportIcon, TrashIcon } from '@heroicons/react/solid';
|
||||||
import { Button } from '@/components/elements/button/index';
|
import { Button } from '@/components/elements/button/index';
|
||||||
import { Checkbox, InputField } from '@/components/elements/inputs';
|
import { Checkbox, InputField } from '@/components/elements/inputs';
|
||||||
|
@ -16,7 +16,7 @@ const UsersContainerV2 = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
http.get('/api/application/users')
|
http.get('/api/application/users')
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
setUsers(data.data.map(AdminTransformers.toUser));
|
setUsers(data.data.map(Transformers.toUser));
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -11036,6 +11036,7 @@ fsevents@^1.2.7:
|
||||||
swr: ^1.0.1
|
swr: ^1.0.1
|
||||||
tailwindcss: ^3.0.23
|
tailwindcss: ^3.0.23
|
||||||
terser-webpack-plugin: ^4.2.3
|
terser-webpack-plugin: ^4.2.3
|
||||||
|
ts-essentials: ^9.1.2
|
||||||
twin.macro: ^2.8.2
|
twin.macro: ^2.8.2
|
||||||
typescript: ^4.4.4
|
typescript: ^4.4.4
|
||||||
uuid: ^3.4.0
|
uuid: ^3.4.0
|
||||||
|
@ -13389,6 +13390,15 @@ resolve@^2.0.0-next.3:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"ts-essentials@npm:^9.1.2":
|
||||||
|
version: 9.1.2
|
||||||
|
resolution: "ts-essentials@npm:9.1.2"
|
||||||
|
peerDependencies:
|
||||||
|
typescript: ">=4.1.0"
|
||||||
|
checksum: 3b14d8511557bd1bdec068505074ee41d3b1cdb052d3c66a5046f00b483bd4722d529b32a99cca424a9d7db9caff7ba108acbf344a5477a741e0616af5649b47
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"ts-toolbelt@npm:^9.6.0":
|
"ts-toolbelt@npm:^9.6.0":
|
||||||
version: 9.6.0
|
version: 9.6.0
|
||||||
resolution: "ts-toolbelt@npm:9.6.0"
|
resolution: "ts-toolbelt@npm:9.6.0"
|
||||||
|
|
Loading…
Reference in a new issue