587 lines
20 KiB
TypeScript
587 lines
20 KiB
TypeScript
import * as plugins from './plugins.js';
|
|
import * as domtools from '@design.estate/dees-domtools';
|
|
|
|
const appstate = new plugins.deesDomtools.plugins.smartstate.Smartstate();
|
|
|
|
// Helper: strip class instances (with circular refs) to plain objects
|
|
type IdData<T> = { id: string; data: T };
|
|
const toPlain = <D>(items: Array<{ id: string; data: D }> = []): Array<IdData<D>> =>
|
|
items.map(({ id, data }) => ({ id, data }));
|
|
export interface ILoginState {
|
|
identity: plugins.interfaces.data.IIdentity;
|
|
}
|
|
export const loginStatePart: plugins.smartstate.StatePart<unknown, ILoginState> = await appstate.getStatePart<ILoginState>(
|
|
'login',
|
|
{ identity: null },
|
|
'persistent'
|
|
);
|
|
|
|
export const loginAction = loginStatePart.createAction<{ username: string; password: string }>(
|
|
async (statePartArg, payloadArg) => {
|
|
const currentState = statePartArg.getState();
|
|
let identity: plugins.interfaces.data.IIdentity = null;
|
|
try {
|
|
identity = await apiClient.loginWithUsernameAndPassword(payloadArg.username, payloadArg.password);
|
|
} catch (err) {
|
|
console.log(err);
|
|
}
|
|
const newState = {
|
|
...currentState,
|
|
...(identity ? { identity } : {}),
|
|
};
|
|
try {
|
|
// Keep shared API client in sync and establish WS for modules using sockets
|
|
apiClient.identity = identity || null;
|
|
if (apiClient.identity) {
|
|
if (!apiClient['typedsocketClient']) {
|
|
await apiClient.start();
|
|
}
|
|
try { apiClient.typedsocketClient.addTag('identity', apiClient.identity); } catch {}
|
|
}
|
|
} catch {}
|
|
return newState;
|
|
}
|
|
);
|
|
|
|
export const logoutAction = loginStatePart.createAction(async (statePartArg) => {
|
|
const currentState = statePartArg.getState();
|
|
return {
|
|
...currentState,
|
|
identity: null,
|
|
};
|
|
});
|
|
|
|
export interface IDataState {
|
|
secretGroups?: plugins.interfaces.data.ISecretGroup[];
|
|
secretBundles?: plugins.interfaces.data.ISecretBundle[];
|
|
clusters?: plugins.interfaces.data.ICluster[];
|
|
externalRegistries?: plugins.interfaces.data.IExternalRegistry[];
|
|
images?: any[];
|
|
services?: plugins.interfaces.data.IService[];
|
|
deployments?: plugins.interfaces.data.IDeployment[];
|
|
domains?: plugins.interfaces.data.IDomain[];
|
|
dnsEntries?: plugins.interfaces.data.IDnsEntry[];
|
|
tasks?: any[];
|
|
taskExecutions?: plugins.interfaces.data.ITaskExecution[];
|
|
mails?: any[];
|
|
logs?: any[];
|
|
s3?: any[];
|
|
dbs?: any[];
|
|
backups?: any[];
|
|
}
|
|
export const dataState = await appstate.getStatePart<IDataState>(
|
|
'data',
|
|
{
|
|
secretGroups: [],
|
|
secretBundles: [],
|
|
clusters: [],
|
|
externalRegistries: [],
|
|
images: [],
|
|
services: [],
|
|
deployments: [],
|
|
domains: [],
|
|
dnsEntries: [],
|
|
tasks: [],
|
|
taskExecutions: [],
|
|
mails: [],
|
|
logs: [],
|
|
s3: [],
|
|
dbs: [],
|
|
backups: [],
|
|
},
|
|
'soft'
|
|
);
|
|
|
|
// Shared API client instance (used by UI actions)
|
|
export const apiClient = new plugins.servezoneApi.CloudlyApiClient({
|
|
registerAs: 'api',
|
|
cloudlyUrl: (typeof window !== 'undefined' && window.location?.origin) ? window.location.origin : undefined,
|
|
});
|
|
|
|
// Getting data
|
|
export const getAllDataAction = dataState.createAction(async (statePartArg) => {
|
|
let currentState = statePartArg.getState();
|
|
// SecretsGroups
|
|
try {
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
const secretGroups = await apiClient.secretgroup.getSecretGroups();
|
|
currentState = {
|
|
...currentState,
|
|
secretGroups: toPlain(secretGroups),
|
|
};
|
|
} catch (err) {
|
|
console.error('Failed to fetch secret groups:', err);
|
|
currentState = {
|
|
...currentState,
|
|
secretGroups: [],
|
|
};
|
|
}
|
|
|
|
// SecretBundles
|
|
try {
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
const responseSecretBundles = await apiClient.secretbundle.getSecretBundles();
|
|
currentState = {
|
|
...currentState,
|
|
secretBundles: toPlain(responseSecretBundles),
|
|
};
|
|
} catch (err) {
|
|
console.error('Failed to fetch secret bundles:', err);
|
|
currentState = {
|
|
...currentState,
|
|
secretBundles: [],
|
|
};
|
|
}
|
|
|
|
// images
|
|
try {
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
const images = await apiClient.image.getImages();
|
|
currentState = {
|
|
...currentState,
|
|
images: toPlain(images),
|
|
};
|
|
} catch (err) {
|
|
console.error('Failed to fetch images:', err);
|
|
currentState = {
|
|
...currentState,
|
|
images: [],
|
|
};
|
|
}
|
|
|
|
// Clusters
|
|
try {
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
const clusters = await apiClient.cluster.getClusters();
|
|
currentState = {
|
|
...currentState,
|
|
clusters: toPlain(clusters),
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to fetch clusters:', err);
|
|
currentState = {
|
|
...currentState,
|
|
clusters: [],
|
|
}
|
|
}
|
|
|
|
// External Registries via shared API client
|
|
try {
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
const registries = await apiClient.externalRegistry.getRegistries();
|
|
currentState = {
|
|
...currentState,
|
|
externalRegistries: toPlain(registries),
|
|
};
|
|
} catch (error) {
|
|
console.error('Failed to fetch external registries:', error);
|
|
currentState = {
|
|
...currentState,
|
|
externalRegistries: [],
|
|
};
|
|
}
|
|
|
|
// Services
|
|
try {
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
const services = await apiClient.services.getServices();
|
|
currentState = {
|
|
...currentState,
|
|
services: toPlain(services),
|
|
};
|
|
} catch (error) {
|
|
console.error('Failed to fetch services:', error);
|
|
currentState = {
|
|
...currentState,
|
|
services: [],
|
|
};
|
|
}
|
|
|
|
// Deployments
|
|
try {
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
const responseDeployments = await apiClient.deployments.getDeployments();
|
|
currentState = {
|
|
...currentState,
|
|
deployments: responseDeployments?.deployments || [],
|
|
};
|
|
} catch (error) {
|
|
console.error('Failed to fetch deployments:', error);
|
|
currentState = {
|
|
...currentState,
|
|
deployments: [],
|
|
};
|
|
}
|
|
|
|
// Domains via API client
|
|
try {
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
const responseDomains = await apiClient.domains.getDomains();
|
|
currentState = {
|
|
...currentState,
|
|
domains: responseDomains?.domains || [],
|
|
};
|
|
} catch (error) {
|
|
console.error('Failed to fetch domains:', error);
|
|
currentState = {
|
|
...currentState,
|
|
domains: [],
|
|
};
|
|
}
|
|
|
|
// DNS Entries via API client
|
|
try {
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
const responseDnsEntries = await apiClient.dns.getDnsEntries();
|
|
currentState = {
|
|
...currentState,
|
|
dnsEntries: responseDnsEntries?.dnsEntries || [],
|
|
};
|
|
} catch (error) {
|
|
console.error('Failed to fetch DNS entries:', error);
|
|
currentState = {
|
|
...currentState,
|
|
dnsEntries: [],
|
|
};
|
|
}
|
|
|
|
return currentState;
|
|
});
|
|
|
|
// Service Actions
|
|
export const createServiceAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { serviceData: plugins.interfaces.data.IService['data'] }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.services.createService(payloadArg.serviceData);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const updateServiceAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { serviceId: string; serviceData: plugins.interfaces.data.IService['data'] }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.services.updateService(payloadArg.serviceId, payloadArg.serviceData);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const deleteServiceAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { serviceId: string }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.services.deleteService(payloadArg.serviceId);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
// SecretGroup Actions
|
|
export const createSecretGroupAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: plugins.interfaces.data.ISecretGroup) => {
|
|
let currentState = statePartArg.getState();
|
|
try {
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.secretgroup.createSecretGroup(payloadArg.data);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
} catch (err) {
|
|
console.error('Failed to create secret group:', err);
|
|
}
|
|
return currentState;
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const deleteSecretGroupAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { secretGroupId: string }) => {
|
|
let currentState = statePartArg.getState();
|
|
try {
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.secretgroup.deleteSecretGroupById(payloadArg.secretGroupId);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
} catch (err) {
|
|
console.error('Failed to delete secret group:', err);
|
|
}
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
// SecretBundle Actions
|
|
export const deleteSecretBundleAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { configBundleId: string }) => {
|
|
let currentState = statePartArg.getState();
|
|
try {
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.secretbundle.deleteSecretBundleById(payloadArg.configBundleId);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
} catch (err) {
|
|
console.error('Failed to delete secret bundle:', err);
|
|
}
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
// image actions
|
|
export const createImageAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { imageName: string, description: string }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.image.createImage({ name: payloadArg.imageName, description: payloadArg.description });
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const deleteImageAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { imageId: string }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.image.deleteImage(payloadArg.imageId);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
// Deployment Actions
|
|
export const createDeploymentAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { deploymentData: Partial<plugins.interfaces.data.IDeployment> }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.deployments.createDeployment(payloadArg.deploymentData);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const updateDeploymentAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { deploymentId: string; deploymentData: Partial<plugins.interfaces.data.IDeployment> }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.deployments.updateDeployment(payloadArg.deploymentId, payloadArg.deploymentData);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const deleteDeploymentAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { deploymentId: string }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.deployments.deleteDeployment(payloadArg.deploymentId);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
// DNS Actions
|
|
export const createDnsEntryAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { dnsEntryData: plugins.interfaces.data.IDnsEntry['data'] }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.dns.createDnsEntry(payloadArg.dnsEntryData);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const updateDnsEntryAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { dnsEntryId: string; dnsEntryData: plugins.interfaces.data.IDnsEntry['data'] }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.dns.updateDnsEntry(payloadArg.dnsEntryId, payloadArg.dnsEntryData);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const deleteDnsEntryAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { dnsEntryId: string }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.dns.deleteDnsEntry(payloadArg.dnsEntryId);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
// Domain Actions
|
|
export const createDomainAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { domainData: plugins.interfaces.data.IDomain['data'] }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.domains.createDomain(payloadArg.domainData);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const updateDomainAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { domainId: string; domainData: plugins.interfaces.data.IDomain['data'] }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.domains.updateDomain(payloadArg.domainId, payloadArg.domainData);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const deleteDomainAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { domainId: string }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.domains.deleteDomain(payloadArg.domainId);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const verifyDomainAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { domainId: string; verificationMethod?: 'dns' | 'http' | 'email' | 'manual' }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.domains.verifyDomain(payloadArg.domainId, payloadArg.verificationMethod);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
// External Registry Actions
|
|
export const createExternalRegistryAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { registryData: plugins.interfaces.data.IExternalRegistry['data'] }) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.externalRegistry.createRegistry(payloadArg.registryData);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const updateExternalRegistryAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { registryId: string; registryData: plugins.interfaces.data.IExternalRegistry['data'] }) => {
|
|
let currentState = statePartArg.getState();
|
|
try {
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.externalRegistry.updateRegistry(payloadArg.registryId, payloadArg.registryData);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
} catch (err) {
|
|
console.error('Failed to update external registry:', err);
|
|
}
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const deleteExternalRegistryAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { registryId: string }) => {
|
|
let currentState = statePartArg.getState();
|
|
try {
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.externalRegistry.deleteRegistry(payloadArg.registryId);
|
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
|
} catch (err) {
|
|
console.error('Failed to delete external registry:', err);
|
|
}
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const verifyExternalRegistryAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { registryId: string }) => {
|
|
let currentState = statePartArg.getState();
|
|
try {
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
const result = await apiClient.externalRegistry.verifyRegistry(payloadArg.registryId);
|
|
if (result.success && result.registry) {
|
|
const regs = (currentState.externalRegistries || []).slice();
|
|
const idx = regs.findIndex(r => r.id === payloadArg.registryId);
|
|
if (idx >= 0) {
|
|
// Preserve instance; update its data + shallow props
|
|
const instance: any = regs[idx];
|
|
instance.data = result.registry.data;
|
|
instance.id = result.registry.id;
|
|
regs[idx] = instance;
|
|
}
|
|
currentState = {
|
|
...currentState,
|
|
externalRegistries: regs,
|
|
};
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to verify external registry:', err);
|
|
}
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
// Task Actions
|
|
export const taskActions = {
|
|
getTasks: dataState.createAction(
|
|
async (statePartArg, payloadArg: {}) => {
|
|
const currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
const response = await apiClient.tasks.getTasks();
|
|
return {
|
|
...currentState,
|
|
tasks: response.tasks,
|
|
};
|
|
}
|
|
),
|
|
|
|
getTaskExecutions: dataState.createAction(
|
|
async (statePartArg, payloadArg: { filter?: any }) => {
|
|
const currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
const response = await apiClient.tasks.getTaskExecutions(payloadArg.filter);
|
|
return {
|
|
...currentState,
|
|
taskExecutions: response.executions,
|
|
};
|
|
}
|
|
),
|
|
|
|
getTaskExecutionById: dataState.createAction(
|
|
async (statePartArg, payloadArg: { executionId: string }) => {
|
|
const currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.tasks.getTaskExecutionById(payloadArg.executionId);
|
|
return currentState;
|
|
}
|
|
),
|
|
|
|
triggerTask: dataState.createAction(
|
|
async (statePartArg, payloadArg: { taskName: string; userId?: string }) => {
|
|
const currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.tasks.triggerTask(payloadArg.taskName, payloadArg.userId);
|
|
return currentState;
|
|
}
|
|
),
|
|
|
|
cancelTask: dataState.createAction(
|
|
async (statePartArg, payloadArg: { executionId: string }) => {
|
|
const currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.tasks.cancelTask(payloadArg.executionId);
|
|
return currentState;
|
|
}
|
|
),
|
|
};
|
|
|
|
// cluster
|
|
export const addClusterAction = dataState.createAction(
|
|
async (
|
|
statePartArg,
|
|
payloadArg: {
|
|
clusterName: string;
|
|
setupMode?: 'manual' | 'hetzner' | 'aws' | 'digitalocean';
|
|
}
|
|
) => {
|
|
let currentState = statePartArg.getState();
|
|
apiClient.identity = loginStatePart.getState().identity;
|
|
await apiClient.cluster.createClusterAdvanced(payloadArg.clusterName, payloadArg.setupMode);
|
|
return await dataState.dispatchAction(getAllDataAction, null);
|
|
}
|
|
);
|