Files
cloudly/ts_web/appstate.ts

587 lines
20 KiB
TypeScript
Raw Normal View History

2024-04-20 12:21:41 +02:00
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 }));
2024-04-20 12:21:41 +02:00
export interface ILoginState {
identity: plugins.interfaces.data.IIdentity;
2024-04-20 12:21:41 +02:00
}
2024-06-13 09:36:02 +02:00
export const loginStatePart: plugins.smartstate.StatePart<unknown, ILoginState> = await appstate.getStatePart<ILoginState>(
2024-04-20 12:21:41 +02:00
'login',
{ identity: null },
2024-04-20 12:21:41 +02:00
'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 = {
2024-04-20 12:21:41 +02:00
...currentState,
...(identity ? { identity } : {}),
2024-04-20 12:21:41 +02:00
};
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;
2024-04-20 12:21:41 +02:00
}
);
export const logoutAction = loginStatePart.createAction(async (statePartArg) => {
const currentState = statePartArg.getState();
return {
...currentState,
identity: null,
2024-04-20 12:21:41 +02:00
};
});
export interface IDataState {
secretGroups?: plugins.interfaces.data.ISecretGroup[];
secretBundles?: plugins.interfaces.data.ISecretBundle[];
clusters?: plugins.interfaces.data.ICluster[];
externalRegistries?: plugins.interfaces.data.IExternalRegistry[];
2024-04-20 12:21:41 +02:00
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[];
2024-04-20 12:21:41 +02:00
mails?: any[];
logs?: any[];
s3?: any[];
dbs?: any[];
backups?: any[];
}
export const dataState = await appstate.getStatePart<IDataState>(
'data',
{
secretGroups: [],
secretBundles: [],
clusters: [],
externalRegistries: [],
2024-04-20 12:21:41 +02:00
images: [],
services: [],
deployments: [],
domains: [],
dnsEntries: [],
tasks: [],
taskExecutions: [],
2024-04-20 12:21:41 +02:00
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,
});
2024-04-20 12:21:41 +02:00
// Getting data
export const getAllDataAction = dataState.createAction(async (statePartArg) => {
2024-04-20 12:21:41 +02:00
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: [],
};
}
2024-04-20 12:21:41 +02:00
// 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: [],
};
}
2024-04-20 12:21:41 +02:00
// 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: [],
}
2024-04-20 12:21:41 +02:00
}
// 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: [],
};
}
2024-04-20 12:21:41 +02:00
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;
}
);
2024-04-20 12:21:41 +02:00
// 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);
}
2024-04-20 12:21:41 +02:00
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);
}
2024-04-20 12:21:41 +02:00
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);
2024-04-20 12:21:41 +02:00
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;
}
),
};
2024-04-20 12:21:41 +02:00
// cluster
export const addClusterAction = dataState.createAction(
async (
statePartArg,
payloadArg: {
clusterName: string;
setupMode?: 'manual' | 'hetzner' | 'aws' | 'digitalocean';
2024-04-20 12:21:41 +02:00
}
) => {
let currentState = statePartArg.getState();
apiClient.identity = loginStatePart.getState().identity;
await apiClient.cluster.createClusterAdvanced(payloadArg.clusterName, payloadArg.setupMode);
return await dataState.dispatchAction(getAllDataAction, null);
2024-04-20 12:21:41 +02:00
}
);