Files

590 lines
20 KiB
TypeScript
Raw Permalink Normal View History

import * as plugins from './plugins.js';
import * as interfaces from '../ts_interfaces/index.js';
// ============================================================================
// Smartstate instance
// ============================================================================
export const appState = new plugins.domtools.plugins.smartstate.Smartstate();
// ============================================================================
// State Part Interfaces
// ============================================================================
export interface ILoginState {
identity: interfaces.data.IIdentity | null;
isLoggedIn: boolean;
}
export interface IServerState {
status: interfaces.data.IServerStatus | null;
connectionInfo: interfaces.data.IConnectionInfo | null;
}
export interface IBucketsState {
buckets: interfaces.data.IBucketInfo[];
}
export interface IObjectsState {
result: interfaces.data.IObjectListResult | null;
currentBucket: string;
currentPrefix: string;
}
export interface ICredentialsState {
credentials: interfaces.data.IObjstCredential[];
}
export interface IPoliciesState {
policies: interfaces.data.INamedPolicy[];
}
export interface IConfigState {
config: interfaces.data.IServerConfig | null;
}
export interface IUiState {
activeView: string;
autoRefresh: boolean;
refreshInterval: number;
}
// ============================================================================
// State Parts
// ============================================================================
export const loginStatePart = await appState.getStatePart<ILoginState>(
'login',
{
identity: null,
isLoggedIn: false,
},
'persistent',
);
export const serverStatePart = await appState.getStatePart<IServerState>(
'server',
{
status: null,
connectionInfo: null,
},
'soft',
);
export const bucketsStatePart = await appState.getStatePart<IBucketsState>(
'buckets',
{
buckets: [],
},
'soft',
);
export const objectsStatePart = await appState.getStatePart<IObjectsState>(
'objects',
{
result: null,
currentBucket: '',
currentPrefix: '',
},
'soft',
);
export const credentialsStatePart = await appState.getStatePart<ICredentialsState>(
'credentials',
{
credentials: [],
},
'soft',
);
export const policiesStatePart = await appState.getStatePart<IPoliciesState>(
'policies',
{
policies: [],
},
'soft',
);
export const configStatePart = await appState.getStatePart<IConfigState>(
'config',
{
config: null,
},
'soft',
);
export const uiStatePart = await appState.getStatePart<IUiState>(
'ui',
{
activeView: 'overview',
autoRefresh: true,
refreshInterval: 30000,
},
);
// ============================================================================
// Helpers
// ============================================================================
interface IActionContext {
identity: interfaces.data.IIdentity | null;
}
const getActionContext = (): IActionContext => {
return { identity: loginStatePart.getState().identity };
};
// ============================================================================
// Login Actions
// ============================================================================
export const loginAction = loginStatePart.createAction<{
username: string;
password: string;
}>(async (statePartArg, dataArg) => {
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_AdminLoginWithUsernameAndPassword
>('/typedrequest', 'adminLoginWithUsernameAndPassword');
const response = await typedRequest.fire({
username: dataArg.username,
password: dataArg.password,
});
return {
identity: response.identity || null,
isLoggedIn: !!response.identity,
};
} catch (err) {
console.error('Login failed:', err);
return { identity: null, isLoggedIn: false };
}
});
export const logoutAction = loginStatePart.createAction(async (statePartArg) => {
const context = getActionContext();
try {
if (context.identity) {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_AdminLogout
>('/typedrequest', 'adminLogout');
await typedRequest.fire({ identity: context.identity });
}
} catch (err) {
console.error('Logout error:', err);
}
return { identity: null, isLoggedIn: false };
});
// ============================================================================
// Server Status Actions
// ============================================================================
export const fetchServerStatusAction = serverStatePart.createAction(async (statePartArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetServerStatus
>('/typedrequest', 'getServerStatus');
const response = await typedRequest.fire({ identity: context.identity! });
return { status: response.status, connectionInfo: response.connectionInfo };
} catch (err) {
console.error('Failed to fetch server status:', err);
return statePartArg.getState();
}
});
// ============================================================================
// Buckets Actions
// ============================================================================
export const fetchBucketsAction = bucketsStatePart.createAction(async (statePartArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListBuckets
>('/typedrequest', 'listBuckets');
const response = await typedRequest.fire({ identity: context.identity! });
return { buckets: response.buckets };
} catch (err) {
console.error('Failed to fetch buckets:', err);
return statePartArg.getState();
}
});
export const createBucketAction = bucketsStatePart.createAction<{ bucketName: string }>(
async (statePartArg, dataArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_CreateBucket
>('/typedrequest', 'createBucket');
await typedRequest.fire({ identity: context.identity!, bucketName: dataArg.bucketName });
// Re-fetch buckets list
const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListBuckets
>('/typedrequest', 'listBuckets');
const listResp = await listReq.fire({ identity: context.identity! });
return { buckets: listResp.buckets };
} catch (err) {
console.error('Failed to create bucket:', err);
return statePartArg.getState();
}
},
);
export const deleteBucketAction = bucketsStatePart.createAction<{ bucketName: string }>(
async (statePartArg, dataArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_DeleteBucket
>('/typedrequest', 'deleteBucket');
await typedRequest.fire({ identity: context.identity!, bucketName: dataArg.bucketName });
const state = statePartArg.getState();
return {
buckets: state.buckets.filter((b) => b.name !== dataArg.bucketName),
};
} catch (err) {
console.error('Failed to delete bucket:', err);
return statePartArg.getState();
}
},
);
// ============================================================================
// Named Policies Actions
// ============================================================================
export const fetchPoliciesAction = policiesStatePart.createAction(async (statePartArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListNamedPolicies
>('/typedrequest', 'listNamedPolicies');
const response = await typedRequest.fire({ identity: context.identity! });
return { policies: response.policies };
} catch (err) {
console.error('Failed to fetch policies:', err);
return statePartArg.getState();
}
});
export const createPolicyAction = policiesStatePart.createAction<{
name: string;
description: string;
statements: interfaces.data.IObjstStatement[];
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_CreateNamedPolicy
>('/typedrequest', 'createNamedPolicy');
await typedRequest.fire({
identity: context.identity!,
name: dataArg.name,
description: dataArg.description,
statements: dataArg.statements,
});
// Re-fetch
const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListNamedPolicies
>('/typedrequest', 'listNamedPolicies');
const listResp = await listReq.fire({ identity: context.identity! });
return { policies: listResp.policies };
} catch (err) {
console.error('Failed to create policy:', err);
return statePartArg.getState();
}
});
export const updatePolicyAction = policiesStatePart.createAction<{
policyId: string;
name: string;
description: string;
statements: interfaces.data.IObjstStatement[];
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_UpdateNamedPolicy
>('/typedrequest', 'updateNamedPolicy');
await typedRequest.fire({
identity: context.identity!,
policyId: dataArg.policyId,
name: dataArg.name,
description: dataArg.description,
statements: dataArg.statements,
});
// Re-fetch
const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListNamedPolicies
>('/typedrequest', 'listNamedPolicies');
const listResp = await listReq.fire({ identity: context.identity! });
return { policies: listResp.policies };
} catch (err) {
console.error('Failed to update policy:', err);
return statePartArg.getState();
}
});
export const deletePolicyAction = policiesStatePart.createAction<{ policyId: string }>(
async (statePartArg, dataArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_DeleteNamedPolicy
>('/typedrequest', 'deleteNamedPolicy');
await typedRequest.fire({ identity: context.identity!, policyId: dataArg.policyId });
const state = statePartArg.getState();
return { policies: state.policies.filter((p) => p.id !== dataArg.policyId) };
} catch (err) {
console.error('Failed to delete policy:', err);
return statePartArg.getState();
}
},
);
// Standalone async functions for policy-bucket management
export const getBucketNamedPolicies = async (bucketName: string) => {
const context = getActionContext();
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetBucketNamedPolicies
>('/typedrequest', 'getBucketNamedPolicies');
return await typedRequest.fire({ identity: context.identity!, bucketName });
};
export const attachPolicyToBucket = async (policyId: string, bucketName: string) => {
const context = getActionContext();
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_AttachPolicyToBucket
>('/typedrequest', 'attachPolicyToBucket');
return await typedRequest.fire({ identity: context.identity!, policyId, bucketName });
};
export const detachPolicyFromBucket = async (policyId: string, bucketName: string) => {
const context = getActionContext();
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_DetachPolicyFromBucket
>('/typedrequest', 'detachPolicyFromBucket');
return await typedRequest.fire({ identity: context.identity!, policyId, bucketName });
};
export const getPolicyBuckets = async (policyId: string) => {
const context = getActionContext();
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetPolicyBuckets
>('/typedrequest', 'getPolicyBuckets');
return await typedRequest.fire({ identity: context.identity!, policyId });
};
export const setPolicyBuckets = async (policyId: string, bucketNames: string[]) => {
const context = getActionContext();
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_SetPolicyBuckets
>('/typedrequest', 'setPolicyBuckets');
return await typedRequest.fire({ identity: context.identity!, policyId, bucketNames });
};
// ============================================================================
// Objects Actions
// ============================================================================
export const fetchObjectsAction = objectsStatePart.createAction<{
bucketName: string;
prefix?: string;
delimiter?: string;
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListObjects
>('/typedrequest', 'listObjects');
const response = await typedRequest.fire({
identity: context.identity!,
bucketName: dataArg.bucketName,
prefix: dataArg.prefix || '',
delimiter: dataArg.delimiter || '/',
});
return {
result: response.result,
currentBucket: dataArg.bucketName,
currentPrefix: dataArg.prefix || '',
};
} catch (err) {
console.error('Failed to fetch objects:', err);
return statePartArg.getState();
}
});
export const deleteObjectAction = objectsStatePart.createAction<{
bucketName: string;
key: string;
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_DeleteObject
>('/typedrequest', 'deleteObject');
await typedRequest.fire({
identity: context.identity!,
bucketName: dataArg.bucketName,
key: dataArg.key,
});
// Re-fetch objects
const state = statePartArg.getState();
const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListObjects
>('/typedrequest', 'listObjects');
const listResp = await listReq.fire({
identity: context.identity!,
bucketName: state.currentBucket,
prefix: state.currentPrefix,
delimiter: '/',
});
return {
result: listResp.result,
currentBucket: state.currentBucket,
currentPrefix: state.currentPrefix,
};
} catch (err) {
console.error('Failed to delete object:', err);
return statePartArg.getState();
}
});
// ============================================================================
// Credentials Actions
// ============================================================================
export const fetchCredentialsAction = credentialsStatePart.createAction(async (statePartArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetCredentials
>('/typedrequest', 'getCredentials');
const response = await typedRequest.fire({ identity: context.identity! });
return { credentials: response.credentials };
} catch (err) {
console.error('Failed to fetch credentials:', err);
return statePartArg.getState();
}
});
export const addCredentialAction = credentialsStatePart.createAction<{
accessKeyId: string;
secretAccessKey: string;
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_AddCredential
>('/typedrequest', 'addCredential');
await typedRequest.fire({
identity: context.identity!,
accessKeyId: dataArg.accessKeyId,
secretAccessKey: dataArg.secretAccessKey,
});
// Re-fetch credentials
const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetCredentials
>('/typedrequest', 'getCredentials');
const listResp = await listReq.fire({ identity: context.identity! });
return { credentials: listResp.credentials };
} catch (err) {
console.error('Failed to add credential:', err);
return statePartArg.getState();
}
});
export const removeCredentialAction = credentialsStatePart.createAction<{
accessKeyId: string;
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_RemoveCredential
>('/typedrequest', 'removeCredential');
await typedRequest.fire({
identity: context.identity!,
accessKeyId: dataArg.accessKeyId,
});
const state = statePartArg.getState();
return {
credentials: state.credentials.filter((c) => c.accessKeyId !== dataArg.accessKeyId),
};
} catch (err) {
console.error('Failed to remove credential:', err);
return statePartArg.getState();
}
});
// ============================================================================
// Config Actions
// ============================================================================
export const fetchConfigAction = configStatePart.createAction(async (statePartArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetServerConfig
>('/typedrequest', 'getServerConfig');
const response = await typedRequest.fire({ identity: context.identity! });
return { config: response.config };
} catch (err) {
console.error('Failed to fetch config:', err);
return statePartArg.getState();
}
});
// ============================================================================
// UI Actions
// ============================================================================
export const setActiveViewAction = uiStatePart.createAction<{ view: string }>(
async (statePartArg, dataArg) => {
return { ...statePartArg.getState(), activeView: dataArg.view };
},
);
// ============================================================================
// Auto-refresh system
// ============================================================================
let refreshIntervalHandle: ReturnType<typeof setInterval> | null = null;
const dispatchCombinedRefreshAction = async () => {
const loginState = loginStatePart.getState();
if (!loginState.isLoggedIn) return;
try {
await serverStatePart.dispatchAction(fetchServerStatusAction, null);
} catch (_err) {
// Silently fail on auto-refresh
}
};
const startAutoRefresh = () => {
const uiState = uiStatePart.getState();
const loginState = loginStatePart.getState();
if (uiState.autoRefresh && loginState.isLoggedIn) {
if (refreshIntervalHandle) {
clearInterval(refreshIntervalHandle);
}
refreshIntervalHandle = setInterval(() => {
dispatchCombinedRefreshAction();
}, uiState.refreshInterval);
} else {
if (refreshIntervalHandle) {
clearInterval(refreshIntervalHandle);
refreshIntervalHandle = null;
}
}
};
uiStatePart.select((s) => s).subscribe(() => startAutoRefresh());
loginStatePart.select((s) => s).subscribe(() => startAutoRefresh());
startAutoRefresh();