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 IConnectionsState { connections: interfaces.data.IProviderConnection[]; activeConnectionId: string | null; } export interface IDataState { projects: interfaces.data.IProject[]; groups: interfaces.data.IGroup[]; secrets: interfaces.data.ISecret[]; pipelines: interfaces.data.IPipeline[]; pipelineJobs: interfaces.data.IPipelineJob[]; currentJobLog: string; } export interface IActionLogState { entries: interfaces.data.IActionLogEntry[]; total: number; } export interface INavigationContext { connectionId?: string; scope?: 'project' | 'group'; scopeId?: string; projectId?: string; } export interface IUiState { activeView: string; autoRefresh: boolean; refreshInterval: number; navigationContext?: INavigationContext; } // ============================================================================ // State Parts // ============================================================================ export const loginStatePart = await appState.getStatePart( 'login', { identity: null, isLoggedIn: false, }, 'persistent', ); export const connectionsStatePart = await appState.getStatePart( 'connections', { connections: [], activeConnectionId: null, }, 'soft', ); export const dataStatePart = await appState.getStatePart( 'data', { projects: [], groups: [], secrets: [], pipelines: [], pipelineJobs: [], currentJobLog: '', }, 'soft', ); export const actionLogStatePart = await appState.getStatePart( 'actionLog', { entries: [], total: 0, }, '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_AdminLogin >('/typedrequest', 'adminLogin'); 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 }; }); // ============================================================================ // Connections Actions // ============================================================================ export const fetchConnectionsAction = connectionsStatePart.createAction(async (statePartArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetConnections >('/typedrequest', 'getConnections'); const response = await typedRequest.fire({ identity: context.identity! }); return { ...statePartArg.getState(), connections: response.connections }; } catch (err) { console.error('Failed to fetch connections:', err); return statePartArg.getState(); } }); export const createConnectionAction = connectionsStatePart.createAction<{ name: string; providerType: interfaces.data.TProviderType; baseUrl: string; token: string; groupFilter?: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_CreateConnection >('/typedrequest', 'createConnection'); await typedRequest.fire({ identity: context.identity!, ...dataArg, }); // Re-fetch const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetConnections >('/typedrequest', 'getConnections'); const listResp = await listReq.fire({ identity: context.identity! }); return { ...statePartArg.getState(), connections: listResp.connections }; } catch (err) { console.error('Failed to create connection:', err); return statePartArg.getState(); } }); export const testConnectionAction = connectionsStatePart.createAction<{ connectionId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_TestConnection >('/typedrequest', 'testConnection'); const result = await typedRequest.fire({ identity: context.identity!, connectionId: dataArg.connectionId, }); // Re-fetch to get updated status const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetConnections >('/typedrequest', 'getConnections'); const listResp = await listReq.fire({ identity: context.identity! }); return { ...statePartArg.getState(), connections: listResp.connections }; } catch (err) { console.error('Failed to test connection:', err); return statePartArg.getState(); } }); export const deleteConnectionAction = connectionsStatePart.createAction<{ connectionId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_DeleteConnection >('/typedrequest', 'deleteConnection'); await typedRequest.fire({ identity: context.identity!, connectionId: dataArg.connectionId, }); const state = statePartArg.getState(); return { ...state, connections: state.connections.filter((c) => c.id !== dataArg.connectionId), activeConnectionId: state.activeConnectionId === dataArg.connectionId ? null : state.activeConnectionId, }; } catch (err) { console.error('Failed to delete connection:', err); return statePartArg.getState(); } }); export const pauseConnectionAction = connectionsStatePart.createAction<{ connectionId: string; paused: boolean; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_PauseConnection >('/typedrequest', 'pauseConnection'); await typedRequest.fire({ identity: context.identity!, ...dataArg, }); // Re-fetch to get updated status const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetConnections >('/typedrequest', 'getConnections'); const listResp = await listReq.fire({ identity: context.identity! }); return { ...statePartArg.getState(), connections: listResp.connections }; } catch (err) { console.error('Failed to pause/resume connection:', err); return statePartArg.getState(); } }); export const updateConnectionAction = connectionsStatePart.createAction<{ connectionId: string; name?: string; baseUrl?: string; token?: string; groupFilter?: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_UpdateConnection >('/typedrequest', 'updateConnection'); await typedRequest.fire({ identity: context.identity!, ...dataArg, }); // Re-fetch to get updated data const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetConnections >('/typedrequest', 'getConnections'); const listResp = await listReq.fire({ identity: context.identity! }); return { ...statePartArg.getState(), connections: listResp.connections }; } catch (err) { console.error('Failed to update connection:', err); return statePartArg.getState(); } }); // ============================================================================ // Projects Actions // ============================================================================ export const fetchProjectsAction = dataStatePart.createAction<{ connectionId: string; search?: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetProjects >('/typedrequest', 'getProjects'); const response = await typedRequest.fire({ identity: context.identity!, connectionId: dataArg.connectionId, search: dataArg.search, }); return { ...statePartArg.getState(), projects: response.projects }; } catch (err) { console.error('Failed to fetch projects:', err); return statePartArg.getState(); } }); // ============================================================================ // Groups Actions // ============================================================================ export const fetchGroupsAction = dataStatePart.createAction<{ connectionId: string; search?: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetGroups >('/typedrequest', 'getGroups'); const response = await typedRequest.fire({ identity: context.identity!, connectionId: dataArg.connectionId, search: dataArg.search, }); return { ...statePartArg.getState(), groups: response.groups }; } catch (err) { console.error('Failed to fetch groups:', err); return statePartArg.getState(); } }); // ============================================================================ // Secrets Actions // ============================================================================ export const fetchSecretsAction = dataStatePart.createAction<{ connectionId: string; scope: 'project' | 'group'; scopeId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetSecrets >('/typedrequest', 'getSecrets'); const response = await typedRequest.fire({ identity: context.identity!, connectionId: dataArg.connectionId, scope: dataArg.scope, scopeId: dataArg.scopeId, }); return { ...statePartArg.getState(), secrets: response.secrets }; } catch (err) { console.error('Failed to fetch secrets:', err); return statePartArg.getState(); } }); export const fetchAllSecretsAction = dataStatePart.createAction<{ connectionId: string; scope?: 'project' | 'group'; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { // When no scope specified, fetch both project and group secrets in parallel const scopes: Array<'project' | 'group'> = dataArg.scope ? [dataArg.scope] : ['project', 'group']; const results = await Promise.all( scopes.map(async (scope) => { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetAllSecrets >('/typedrequest', 'getAllSecrets'); const response = await typedRequest.fire({ identity: context.identity!, connectionId: dataArg.connectionId, scope, }); return response.secrets; }), ); return { ...statePartArg.getState(), secrets: results.flat() }; } catch (err) { console.error('Failed to fetch all secrets:', err); return statePartArg.getState(); } }); export const createSecretAction = dataStatePart.createAction<{ connectionId: string; scope: 'project' | 'group'; scopeId: string; key: string; value: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_CreateSecret >('/typedrequest', 'createSecret'); await typedRequest.fire({ identity: context.identity!, ...dataArg, }); // Re-fetch only the affected entity's secrets and merge const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetSecrets >('/typedrequest', 'getSecrets'); const listResp = await listReq.fire({ identity: context.identity!, connectionId: dataArg.connectionId, scope: dataArg.scope, scopeId: dataArg.scopeId, }); const state = statePartArg.getState(); const otherSecrets = state.secrets.filter( (s) => !(s.scopeId === dataArg.scopeId && s.scope === dataArg.scope), ); return { ...state, secrets: [...otherSecrets, ...listResp.secrets] }; } catch (err) { console.error('Failed to create secret:', err); return statePartArg.getState(); } }); export const updateSecretAction = dataStatePart.createAction<{ connectionId: string; scope: 'project' | 'group'; scopeId: string; key: string; value: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_UpdateSecret >('/typedrequest', 'updateSecret'); await typedRequest.fire({ identity: context.identity!, ...dataArg, }); // Re-fetch only the affected entity's secrets and merge const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetSecrets >('/typedrequest', 'getSecrets'); const listResp = await listReq.fire({ identity: context.identity!, connectionId: dataArg.connectionId, scope: dataArg.scope, scopeId: dataArg.scopeId, }); const state = statePartArg.getState(); const otherSecrets = state.secrets.filter( (s) => !(s.scopeId === dataArg.scopeId && s.scope === dataArg.scope), ); return { ...state, secrets: [...otherSecrets, ...listResp.secrets] }; } catch (err) { console.error('Failed to update secret:', err); return statePartArg.getState(); } }); export const deleteSecretAction = dataStatePart.createAction<{ connectionId: string; scope: 'project' | 'group'; scopeId: string; key: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_DeleteSecret >('/typedrequest', 'deleteSecret'); await typedRequest.fire({ identity: context.identity!, ...dataArg, }); const state = statePartArg.getState(); return { ...state, secrets: state.secrets.filter( (s) => !(s.key === dataArg.key && s.scopeId === dataArg.scopeId && s.scope === dataArg.scope), ), }; } catch (err) { console.error('Failed to delete secret:', err); return statePartArg.getState(); } }); // ============================================================================ // Pipelines Actions // ============================================================================ export const fetchPipelinesAction = dataStatePart.createAction<{ connectionId: string; projectId?: string; viewMode?: 'current' | 'project' | 'group' | 'error'; groupId?: string; status?: string; sortBy?: 'created' | 'duration' | 'status'; timeRange?: '1h' | '6h' | '1d' | '3d' | '7d' | '30d'; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetPipelines >('/typedrequest', 'getPipelines'); const response = await typedRequest.fire({ identity: context.identity!, connectionId: dataArg.connectionId, projectId: dataArg.projectId, viewMode: dataArg.viewMode, groupId: dataArg.groupId, status: dataArg.status, sortBy: dataArg.sortBy, timeRange: dataArg.timeRange, }); return { ...statePartArg.getState(), pipelines: response.pipelines }; } catch (err) { console.error('Failed to fetch pipelines:', err); return statePartArg.getState(); } }); export const fetchPipelineJobsAction = dataStatePart.createAction<{ connectionId: string; projectId: string; pipelineId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetPipelineJobs >('/typedrequest', 'getPipelineJobs'); const response = await typedRequest.fire({ identity: context.identity!, connectionId: dataArg.connectionId, projectId: dataArg.projectId, pipelineId: dataArg.pipelineId, }); return { ...statePartArg.getState(), pipelineJobs: response.jobs }; } catch (err) { console.error('Failed to fetch pipeline jobs:', err); return statePartArg.getState(); } }); export const retryPipelineAction = dataStatePart.createAction<{ connectionId: string; projectId: string; pipelineId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_RetryPipeline >('/typedrequest', 'retryPipeline'); await typedRequest.fire({ identity: context.identity!, ...dataArg, }); // Re-fetch pipelines const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetPipelines >('/typedrequest', 'getPipelines'); const listResp = await listReq.fire({ identity: context.identity!, connectionId: dataArg.connectionId, projectId: dataArg.projectId, }); return { ...statePartArg.getState(), pipelines: listResp.pipelines }; } catch (err) { console.error('Failed to retry pipeline:', err); return statePartArg.getState(); } }); export const cancelPipelineAction = dataStatePart.createAction<{ connectionId: string; projectId: string; pipelineId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_CancelPipeline >('/typedrequest', 'cancelPipeline'); await typedRequest.fire({ identity: context.identity!, ...dataArg, }); // Re-fetch pipelines const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetPipelines >('/typedrequest', 'getPipelines'); const listResp = await listReq.fire({ identity: context.identity!, connectionId: dataArg.connectionId, projectId: dataArg.projectId, }); return { ...statePartArg.getState(), pipelines: listResp.pipelines }; } catch (err) { console.error('Failed to cancel pipeline:', err); return statePartArg.getState(); } }); // ============================================================================ // Logs Actions // ============================================================================ export const fetchJobLogAction = dataStatePart.createAction<{ connectionId: string; projectId: string; jobId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetJobLog >('/typedrequest', 'getJobLog'); const response = await typedRequest.fire({ identity: context.identity!, ...dataArg, }); return { ...statePartArg.getState(), currentJobLog: response.log }; } catch (err) { console.error('Failed to fetch job log:', err); return statePartArg.getState(); } }); // ============================================================================ // Action Log Actions // ============================================================================ export const fetchActionLogAction = actionLogStatePart.createAction<{ limit?: number; offset?: number; entityType?: interfaces.data.TActionEntity; } | null>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetActionLog >('/typedrequest', 'getActionLog'); const response = await typedRequest.fire({ identity: context.identity!, limit: dataArg?.limit, offset: dataArg?.offset, entityType: dataArg?.entityType, }); return { entries: response.entries, total: response.total }; } catch (err) { console.error('Failed to fetch action log:', err); return statePartArg.getState(); } }); // ============================================================================ // UI Actions // ============================================================================ export const setActiveViewAction = uiStatePart.createAction<{ view: string; navigationContext?: INavigationContext; }>( async (statePartArg, dataArg) => { return { ...statePartArg.getState(), activeView: dataArg.view, navigationContext: dataArg.navigationContext, }; }, ); export const clearNavigationContextAction = uiStatePart.createAction( async (statePartArg) => { return { ...statePartArg.getState(), navigationContext: undefined }; }, ); export const toggleAutoRefreshAction = uiStatePart.createAction(async (statePartArg) => { const state = statePartArg.getState(); return { ...state, autoRefresh: !state.autoRefresh }; }); export const setRefreshIntervalAction = uiStatePart.createAction<{ interval: number }>( async (statePartArg, dataArg) => { return { ...statePartArg.getState(), refreshInterval: dataArg.interval }; }, ); // ============================================================================ // Managed Secrets State // ============================================================================ export interface IManagedSecretsState { managedSecrets: interfaces.data.IManagedSecret[]; } export const managedSecretsStatePart = await appState.getStatePart( 'managedSecrets', { managedSecrets: [] }, 'soft', ); export const fetchManagedSecretsAction = managedSecretsStatePart.createAction( async (statePartArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetManagedSecrets >('/typedrequest', 'getManagedSecrets'); const response = await typedRequest.fire({ identity: context.identity! }); return { managedSecrets: response.managedSecrets }; } catch (err) { console.error('Failed to fetch managed secrets:', err); return statePartArg.getState(); } }, ); export const createManagedSecretAction = managedSecretsStatePart.createAction<{ key: string; value: string; description?: string; targets: interfaces.data.IManagedSecretTarget[]; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_CreateManagedSecret >('/typedrequest', 'createManagedSecret'); await typedRequest.fire({ identity: context.identity!, ...dataArg }); // Re-fetch const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetManagedSecrets >('/typedrequest', 'getManagedSecrets'); const listResp = await listReq.fire({ identity: context.identity! }); return { managedSecrets: listResp.managedSecrets }; } catch (err) { console.error('Failed to create managed secret:', err); return statePartArg.getState(); } }); export const updateManagedSecretAction = managedSecretsStatePart.createAction<{ managedSecretId: string; value?: string; description?: string; targets?: interfaces.data.IManagedSecretTarget[]; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_UpdateManagedSecret >('/typedrequest', 'updateManagedSecret'); await typedRequest.fire({ identity: context.identity!, ...dataArg }); const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetManagedSecrets >('/typedrequest', 'getManagedSecrets'); const listResp = await listReq.fire({ identity: context.identity! }); return { managedSecrets: listResp.managedSecrets }; } catch (err) { console.error('Failed to update managed secret:', err); return statePartArg.getState(); } }); export const deleteManagedSecretAction = managedSecretsStatePart.createAction<{ managedSecretId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_DeleteManagedSecret >('/typedrequest', 'deleteManagedSecret'); await typedRequest.fire({ identity: context.identity!, ...dataArg }); const state = statePartArg.getState(); return { managedSecrets: state.managedSecrets.filter((s) => s.id !== dataArg.managedSecretId), }; } catch (err) { console.error('Failed to delete managed secret:', err); return statePartArg.getState(); } }); export const pushManagedSecretAction = managedSecretsStatePart.createAction<{ managedSecretId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_PushManagedSecret >('/typedrequest', 'pushManagedSecret'); await typedRequest.fire({ identity: context.identity!, ...dataArg }); const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetManagedSecrets >('/typedrequest', 'getManagedSecrets'); const listResp = await listReq.fire({ identity: context.identity! }); return { managedSecrets: listResp.managedSecrets }; } catch (err) { console.error('Failed to push managed secret:', err); return statePartArg.getState(); } }); export const pushAllManagedSecretsAction = managedSecretsStatePart.createAction( async (statePartArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_PushAllManagedSecrets >('/typedrequest', 'pushAllManagedSecrets'); await typedRequest.fire({ identity: context.identity! }); const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetManagedSecrets >('/typedrequest', 'getManagedSecrets'); const listResp = await listReq.fire({ identity: context.identity! }); return { managedSecrets: listResp.managedSecrets }; } catch (err) { console.error('Failed to push all managed secrets:', err); return statePartArg.getState(); } }, ); // ============================================================================ // Sync State // ============================================================================ export interface ISyncState { configs: interfaces.data.ISyncConfig[]; repoStatuses: interfaces.data.ISyncRepoStatus[]; } export const syncStatePart = await appState.getStatePart( 'sync', { configs: [], repoStatuses: [] }, 'soft', ); export const fetchSyncConfigsAction = syncStatePart.createAction(async (statePartArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetSyncConfigs >('/typedrequest', 'getSyncConfigs'); const response = await typedRequest.fire({ identity: context.identity! }); return { ...statePartArg.getState(), configs: response.configs }; } catch (err) { console.error('Failed to fetch sync configs:', err); return statePartArg.getState(); } }); export const createSyncConfigAction = syncStatePart.createAction<{ name: string; sourceConnectionId: string; targetConnectionId: string; targetGroupOffset?: string; intervalMinutes?: number; enforceDelete?: boolean; enforceGroupDelete?: boolean; addMirrorHint?: boolean; useGroupAvatarsForProjects?: boolean; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_CreateSyncConfig >('/typedrequest', 'createSyncConfig'); await typedRequest.fire({ identity: context.identity!, ...dataArg }); // Re-fetch const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetSyncConfigs >('/typedrequest', 'getSyncConfigs'); const listResp = await listReq.fire({ identity: context.identity! }); return { ...statePartArg.getState(), configs: listResp.configs }; } catch (err) { console.error('Failed to create sync config:', err); return statePartArg.getState(); } }); export const updateSyncConfigAction = syncStatePart.createAction<{ syncConfigId: string; name?: string; targetGroupOffset?: string; intervalMinutes?: number; enforceDelete?: boolean; enforceGroupDelete?: boolean; addMirrorHint?: boolean; useGroupAvatarsForProjects?: boolean; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_UpdateSyncConfig >('/typedrequest', 'updateSyncConfig'); await typedRequest.fire({ identity: context.identity!, ...dataArg }); const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetSyncConfigs >('/typedrequest', 'getSyncConfigs'); const listResp = await listReq.fire({ identity: context.identity! }); return { ...statePartArg.getState(), configs: listResp.configs }; } catch (err) { console.error('Failed to update sync config:', err); return statePartArg.getState(); } }); export const deleteSyncConfigAction = syncStatePart.createAction<{ syncConfigId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_DeleteSyncConfig >('/typedrequest', 'deleteSyncConfig'); await typedRequest.fire({ identity: context.identity!, ...dataArg }); const state = statePartArg.getState(); return { ...state, configs: state.configs.filter((c) => c.id !== dataArg.syncConfigId) }; } catch (err) { console.error('Failed to delete sync config:', err); return statePartArg.getState(); } }); export const pauseSyncConfigAction = syncStatePart.createAction<{ syncConfigId: string; paused: boolean; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_PauseSyncConfig >('/typedrequest', 'pauseSyncConfig'); await typedRequest.fire({ identity: context.identity!, ...dataArg }); const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetSyncConfigs >('/typedrequest', 'getSyncConfigs'); const listResp = await listReq.fire({ identity: context.identity! }); return { ...statePartArg.getState(), configs: listResp.configs }; } catch (err) { console.error('Failed to pause/resume sync config:', err); return statePartArg.getState(); } }); export const triggerSyncAction = syncStatePart.createAction<{ syncConfigId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_TriggerSync >('/typedrequest', 'triggerSync'); await typedRequest.fire({ identity: context.identity!, ...dataArg }); return statePartArg.getState(); } catch (err) { console.error('Failed to trigger sync:', err); return statePartArg.getState(); } }); export const fetchSyncRepoStatusesAction = syncStatePart.createAction<{ syncConfigId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetSyncRepoStatuses >('/typedrequest', 'getSyncRepoStatuses'); const response = await typedRequest.fire({ identity: context.identity!, ...dataArg }); return { ...statePartArg.getState(), repoStatuses: response.statuses }; } catch (err) { console.error('Failed to fetch sync repo statuses:', err); return statePartArg.getState(); } }); // ============================================================================ // Sync Log — TypedSocket client for server-push entries // ============================================================================ export async function fetchSyncLogs(limit = 200): Promise { const identity = loginStatePart.getState().identity; if (!identity) throw new Error('Not logged in'); const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetSyncLogs >('/typedrequest', 'getSyncLogs'); const response = await typedRequest.fire({ identity, limit }); return response.logs; } let syncLogSocketInitialized = false; /** * Create a TypedSocket client that handles server-push sync log entries. * Dispatches 'gitops-sync-log-entry' custom events on document. * Call once after login. */ export async function initSyncLogSocket(): Promise { if (syncLogSocketInitialized) return; syncLogSocketInitialized = true; try { const typedrouter = new plugins.domtools.plugins.typedrequest.TypedRouter(); typedrouter.addTypedHandler( new plugins.domtools.plugins.typedrequest.TypedHandler( 'pushSyncLog', async (dataArg) => { document.dispatchEvent( new CustomEvent('gitops-sync-log-entry', { detail: dataArg.entry }), ); return {}; }, ), ); const typedsocketClient = await plugins.typedsocket.TypedSocket.createClient( typedrouter, plugins.typedsocket.TypedSocket.useWindowLocationOriginUrl(), { autoReconnect: true }, ); await typedsocketClient.setTag('syncLogClient', {}); } catch (err) { console.error('Failed to init sync log TypedSocket client:', err); syncLogSocketInitialized = false; } } // ============================================================================ // Preview Helper // ============================================================================ export async function previewSync(syncConfigId: string): Promise<{ mappings: Array<{ sourceFullPath: string; targetFullPath: string }>; deletions: string[]; groupDeletions: string[]; }> { const identity = loginStatePart.getState().identity; if (!identity) throw new Error('Not logged in'); const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_PreviewSync >('/typedrequest', 'previewSync'); const response = await typedRequest.fire({ identity, syncConfigId }); return { mappings: response.mappings, deletions: response.deletions, groupDeletions: response.groupDeletions }; }