f40ef6b7c0
Align Cloudly with the current typedserver, smartconfig, smartstate, and Docker tooling releases so builds and Docker output stay compatible with the upgraded stack.
586 lines
20 KiB
TypeScript
586 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();
|
|
export interface ILoginState {
|
|
identity: plugins.interfaces.data.IIdentity | null;
|
|
}
|
|
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() || { identity: null };
|
|
let identity: plugins.interfaces.data.IIdentity | null = 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 { await apiClient.typedsocketClient.setTag('identity', apiClient.identity); } catch {}
|
|
}
|
|
} catch {}
|
|
return newState;
|
|
}
|
|
);
|
|
|
|
export const logoutAction = loginStatePart.createAction(async (statePartArg) => {
|
|
const currentState = statePartArg.getState() || { identity: null };
|
|
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)
|
|
type TCloudlyApiClientWithNullableIdentity = Omit<plugins.servezoneApi.CloudlyApiClient, 'identity'> & {
|
|
identity: plugins.interfaces.data.IIdentity | null;
|
|
};
|
|
|
|
export const apiClient = new plugins.servezoneApi.CloudlyApiClient({
|
|
registerAs: 'api',
|
|
cloudlyUrl: (typeof window !== 'undefined' && window.location?.origin) ? window.location.origin : undefined,
|
|
}) as TCloudlyApiClientWithNullableIdentity;
|
|
|
|
// Getting data
|
|
export const getAllDataAction = dataState.createAction(async (statePartArg) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
// SecretsGroups
|
|
try {
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
const secretGroups = await apiClient.secretgroup.getSecretGroups();
|
|
currentState = {
|
|
...currentState,
|
|
secretGroups: secretGroups,
|
|
};
|
|
} catch (err) {
|
|
console.error('Failed to fetch secret groups:', err);
|
|
currentState = {
|
|
...currentState,
|
|
secretGroups: [],
|
|
};
|
|
}
|
|
|
|
// SecretBundles
|
|
try {
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
const responseSecretBundles = await apiClient.secretbundle.getSecretBundles();
|
|
currentState = {
|
|
...currentState,
|
|
secretBundles: responseSecretBundles,
|
|
};
|
|
} catch (err) {
|
|
console.error('Failed to fetch secret bundles:', err);
|
|
currentState = {
|
|
...currentState,
|
|
secretBundles: [],
|
|
};
|
|
}
|
|
|
|
// images
|
|
try {
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
const images = await apiClient.image.getImages();
|
|
currentState = {
|
|
...currentState,
|
|
images: images,
|
|
};
|
|
} catch (err) {
|
|
console.error('Failed to fetch images:', err);
|
|
currentState = {
|
|
...currentState,
|
|
images: [],
|
|
};
|
|
}
|
|
|
|
// Clusters
|
|
try {
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
const clusters = await apiClient.cluster.getClusters();
|
|
currentState = {
|
|
...currentState,
|
|
clusters: 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 ?? null;
|
|
const registries = await apiClient.externalRegistry.getRegistries();
|
|
currentState = {
|
|
...currentState,
|
|
externalRegistries: registries,
|
|
};
|
|
} catch (error) {
|
|
console.error('Failed to fetch external registries:', error);
|
|
currentState = {
|
|
...currentState,
|
|
externalRegistries: [],
|
|
};
|
|
}
|
|
|
|
// Services
|
|
try {
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
const services = await apiClient.services.getServices();
|
|
currentState = {
|
|
...currentState,
|
|
services: services,
|
|
};
|
|
} catch (error) {
|
|
console.error('Failed to fetch services:', error);
|
|
currentState = {
|
|
...currentState,
|
|
services: [],
|
|
};
|
|
}
|
|
|
|
// Deployments
|
|
try {
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
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 ?? null;
|
|
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 ?? null;
|
|
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'] }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.services.createService(payloadArg.serviceData);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const updateServiceAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { serviceId: string; serviceData: plugins.interfaces.data.IService['data'] }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.services.updateService(payloadArg.serviceId, payloadArg.serviceData);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const deleteServiceAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { serviceId: string }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.services.deleteService(payloadArg.serviceId);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
// SecretGroup Actions
|
|
export const createSecretGroupAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { data: plugins.interfaces.data.ISecretGroup['data'] }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
try {
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.secretgroup.createSecretGroup(payloadArg.data);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
} catch (err) {
|
|
console.error('Failed to create secret group:', err);
|
|
}
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const deleteSecretGroupAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { secretGroupId: string }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
try {
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.secretgroup.deleteSecretGroupById(payloadArg.secretGroupId);
|
|
currentState = await context.dispatch(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 }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
try {
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.secretbundle.deleteSecretBundleById(payloadArg.configBundleId);
|
|
currentState = await context.dispatch(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 }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.image.createImage({ name: payloadArg.imageName, description: payloadArg.description });
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const deleteImageAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { imageId: string }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.image.deleteImage(payloadArg.imageId);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
// Deployment Actions
|
|
export const createDeploymentAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { deploymentData: Partial<plugins.interfaces.data.IDeployment> }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.deployments.createDeployment(payloadArg.deploymentData);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const updateDeploymentAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { deploymentId: string; deploymentData: Partial<plugins.interfaces.data.IDeployment> }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.deployments.updateDeployment(payloadArg.deploymentId, payloadArg.deploymentData);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const deleteDeploymentAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { deploymentId: string }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.deployments.deleteDeployment(payloadArg.deploymentId);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
// DNS Actions
|
|
export const createDnsEntryAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { dnsEntryData: plugins.interfaces.data.IDnsEntry['data'] }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.dns.createDnsEntry(payloadArg.dnsEntryData);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const updateDnsEntryAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { dnsEntryId: string; dnsEntryData: plugins.interfaces.data.IDnsEntry['data'] }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.dns.updateDnsEntry(payloadArg.dnsEntryId, payloadArg.dnsEntryData);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const deleteDnsEntryAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { dnsEntryId: string }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.dns.deleteDnsEntry(payloadArg.dnsEntryId);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
// Domain Actions
|
|
export const createDomainAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { domainData: plugins.interfaces.data.IDomain['data'] }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.domains.createDomain(payloadArg.domainData);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const updateDomainAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { domainId: string; domainData: plugins.interfaces.data.IDomain['data'] }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.domains.updateDomain(payloadArg.domainId, payloadArg.domainData);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const deleteDomainAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { domainId: string }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.domains.deleteDomain(payloadArg.domainId);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const verifyDomainAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { domainId: string; verificationMethod?: 'dns' | 'http' | 'email' | 'manual' }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.domains.verifyDomain(payloadArg.domainId, payloadArg.verificationMethod);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
// External Registry Actions
|
|
export const createExternalRegistryAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { registryData: plugins.interfaces.data.IExternalRegistry['data'] }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.externalRegistry.createRegistry(payloadArg.registryData);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const updateExternalRegistryAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { registryId: string; registryData: plugins.interfaces.data.IExternalRegistry['data'] }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
try {
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.externalRegistry.updateRegistry(payloadArg.registryId, payloadArg.registryData);
|
|
currentState = await context.dispatch(getAllDataAction, null);
|
|
} catch (err) {
|
|
console.error('Failed to update external registry:', err);
|
|
}
|
|
return currentState;
|
|
}
|
|
);
|
|
|
|
export const deleteExternalRegistryAction = dataState.createAction(
|
|
async (statePartArg, payloadArg: { registryId: string }, context) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
try {
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.externalRegistry.deleteRegistry(payloadArg.registryId);
|
|
currentState = await context.dispatch(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 ?? null;
|
|
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 ?? null;
|
|
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 ?? null;
|
|
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 ?? null;
|
|
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 ?? null;
|
|
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 ?? null;
|
|
await apiClient.tasks.cancelTask(payloadArg.executionId);
|
|
return currentState;
|
|
}
|
|
),
|
|
};
|
|
|
|
// cluster
|
|
export const addClusterAction = dataState.createAction(
|
|
async (
|
|
statePartArg,
|
|
payloadArg: {
|
|
clusterName: string;
|
|
setupMode?: 'manual' | 'hetzner' | 'aws' | 'digitalocean';
|
|
},
|
|
context
|
|
) => {
|
|
let currentState = statePartArg.getState() || {};
|
|
apiClient.identity = loginStatePart.getState()?.identity ?? null;
|
|
await apiClient.cluster.createClusterAdvanced(payloadArg.clusterName, payloadArg.setupMode);
|
|
return await context.dispatch(getAllDataAction, null);
|
|
}
|
|
);
|