feat(core): rebrand to @lossless.zone/objectstorage
- Rename from @lossless.zone/s3container to @lossless.zone/objectstorage - Replace @push.rocks/smarts3 with @push.rocks/smartstorage - Change env var prefix from S3_ to OBJST_ - Rename S3Container class to ObjectStorageContainer - Update web component prefix from s3c- to objst- - Update UI labels, CLI flags, documentation, and Docker config
This commit is contained in:
589
ts_web/appstate.ts
Normal file
589
ts_web/appstate.ts
Normal file
@@ -0,0 +1,589 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user