feat(sync): add sync subsystem: SyncManager, OpsServer sync handlers, Sync UI and state, provider groupFilter support, and realtime sync log streaming via TypedSocket

This commit is contained in:
2026-02-28 16:33:53 +00:00
parent 2f050744bc
commit f7e16aa350
30 changed files with 2983 additions and 21 deletions

View File

@@ -1,5 +1,5 @@
export type TActionType = 'create' | 'update' | 'delete' | 'pause' | 'resume' | 'test' | 'scan';
export type TActionEntity = 'connection' | 'secret' | 'pipeline';
export type TActionType = 'create' | 'update' | 'delete' | 'pause' | 'resume' | 'test' | 'scan' | 'sync' | 'obsolete';
export type TActionEntity = 'connection' | 'secret' | 'pipeline' | 'sync';
export interface IActionLogEntry {
id: string;

View File

@@ -8,4 +8,6 @@ export interface IProviderConnection {
token: string;
createdAt: number;
status: 'connected' | 'disconnected' | 'error' | 'paused';
groupFilter?: string; // Restricts which repos this connection can see (e.g. "foss.global")
groupFilterId?: string; // Resolved filter group ID (numeric for GitLab, org name for Gitea)
}

View File

@@ -5,3 +5,4 @@ export * from './group.ts';
export * from './secret.ts';
export * from './pipeline.ts';
export * from './actionlog.ts';
export * from './sync.ts';

View File

@@ -0,0 +1,36 @@
export type TSyncStatus = 'active' | 'paused' | 'error';
export interface ISyncConfig {
id: string;
name: string;
sourceConnectionId: string;
targetConnectionId: string;
targetGroupOffset?: string; // Path prefix for target repos (e.g. "mirror/gitlab")
intervalMinutes: number; // Default 5
status: TSyncStatus;
lastSyncAt: number;
lastSyncError?: string;
lastSyncDurationMs?: number;
reposSynced: number;
enforceDelete: boolean; // When true, stale target repos are moved to obsolete
enforceGroupDelete: boolean; // When true, stale target groups/orgs are moved to obsolete
addMirrorHint?: boolean; // When true, target descriptions get "(This is a mirror of ...)" appended
createdAt: number;
}
export interface ISyncRepoStatus {
id: string;
syncConfigId: string;
sourceFullPath: string; // e.g. "push.rocks/smartstate"
targetFullPath: string; // e.g. "foss.global/push.rocks/smartstate"
lastSyncAt: number;
lastSyncError?: string;
status: 'synced' | 'error' | 'pending';
}
export interface ISyncLogEntry {
timestamp: number;
level: 'info' | 'warn' | 'error' | 'success' | 'debug';
message: string;
source?: string; // e.g. 'preview', 'sync', 'git', 'api'
}

View File

@@ -25,6 +25,7 @@ export interface IReq_CreateConnection extends plugins.typedrequestInterfaces.im
providerType: data.TProviderType;
baseUrl: string;
token: string;
groupFilter?: string;
};
response: {
connection: data.IProviderConnection;
@@ -42,6 +43,7 @@ export interface IReq_UpdateConnection extends plugins.typedrequestInterfaces.im
name?: string;
baseUrl?: string;
token?: string;
groupFilter?: string;
};
response: {
connection: data.IProviderConnection;

View File

@@ -8,3 +8,4 @@ export * from './logs.ts';
export * from './webhook.ts';
export * from './actions.ts';
export * from './actionlog.ts';
export * from './sync.ts';

View File

@@ -0,0 +1,155 @@
import * as plugins from '../plugins.ts';
import * as data from '../data/index.ts';
export interface IReq_GetSyncConfigs extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetSyncConfigs
> {
method: 'getSyncConfigs';
request: {
identity: data.IIdentity;
};
response: {
configs: data.ISyncConfig[];
};
}
export interface IReq_CreateSyncConfig extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_CreateSyncConfig
> {
method: 'createSyncConfig';
request: {
identity: data.IIdentity;
name: string;
sourceConnectionId: string;
targetConnectionId: string;
targetGroupOffset?: string;
intervalMinutes?: number;
enforceDelete?: boolean;
enforceGroupDelete?: boolean;
addMirrorHint?: boolean;
};
response: {
config: data.ISyncConfig;
};
}
export interface IReq_UpdateSyncConfig extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_UpdateSyncConfig
> {
method: 'updateSyncConfig';
request: {
identity: data.IIdentity;
syncConfigId: string;
name?: string;
targetGroupOffset?: string;
intervalMinutes?: number;
enforceDelete?: boolean;
enforceGroupDelete?: boolean;
addMirrorHint?: boolean;
};
response: {
config: data.ISyncConfig;
};
}
export interface IReq_DeleteSyncConfig extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_DeleteSyncConfig
> {
method: 'deleteSyncConfig';
request: {
identity: data.IIdentity;
syncConfigId: string;
};
response: {
ok: boolean;
};
}
export interface IReq_PauseSyncConfig extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_PauseSyncConfig
> {
method: 'pauseSyncConfig';
request: {
identity: data.IIdentity;
syncConfigId: string;
paused: boolean;
};
response: {
config: data.ISyncConfig;
};
}
export interface IReq_TriggerSync extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_TriggerSync
> {
method: 'triggerSync';
request: {
identity: data.IIdentity;
syncConfigId: string;
};
response: {
ok: boolean;
message: string;
};
}
export interface IReq_PreviewSync extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_PreviewSync
> {
method: 'previewSync';
request: {
identity: data.IIdentity;
syncConfigId: string;
};
response: {
mappings: Array<{ sourceFullPath: string; targetFullPath: string }>;
deletions: string[];
groupDeletions: string[];
};
}
export interface IReq_GetSyncRepoStatuses extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetSyncRepoStatuses
> {
method: 'getSyncRepoStatuses';
request: {
identity: data.IIdentity;
syncConfigId: string;
};
response: {
statuses: data.ISyncRepoStatus[];
};
}
export interface IReq_GetSyncLogs extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetSyncLogs
> {
method: 'getSyncLogs';
request: {
identity: data.IIdentity;
limit?: number;
};
response: {
logs: data.ISyncLogEntry[];
};
}
export interface IReq_PushSyncLog extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_PushSyncLog
> {
method: 'pushSyncLog';
request: {
entry: data.ISyncLogEntry;
};
response: {};
}