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( 'login', { identity: null, isLoggedIn: false, }, 'persistent', ); export const serverStatePart = await appState.getStatePart( 'server', { status: null, connectionInfo: null, }, 'soft', ); export const bucketsStatePart = await appState.getStatePart( 'buckets', { buckets: [], }, 'soft', ); export const objectsStatePart = await appState.getStatePart( 'objects', { result: null, currentBucket: '', currentPrefix: '', }, 'soft', ); export const credentialsStatePart = await appState.getStatePart( 'credentials', { credentials: [], }, 'soft', ); export const policiesStatePart = await appState.getStatePart( 'policies', { policies: [], }, 'soft', ); export const configStatePart = await appState.getStatePart( 'config', { config: null, }, 'soft', ); export const uiStatePart = await appState.getStatePart( '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 | 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();