@serve.zone/gitops 2.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.smartconfig.json +114 -0
- package/binary/gitops.ts +4 -0
- package/changelog.md +185 -0
- package/cli.child.js +4 -0
- package/cli.js +4 -0
- package/cli.ts.js +5 -0
- package/deno.json +10 -0
- package/dist_serve/bundle.js +36362 -0
- package/dist_serve/index.html +33 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/cache/classes.cache.cleaner.d.ts +23 -0
- package/dist_ts/cache/classes.cache.cleaner.js +61 -0
- package/dist_ts/cache/classes.cached.document.d.ts +30 -0
- package/dist_ts/cache/classes.cached.document.js +101 -0
- package/dist_ts/cache/classes.cachedb.d.ts +22 -0
- package/dist_ts/cache/classes.cachedb.js +58 -0
- package/dist_ts/cache/classes.secrets.scan.service.d.ts +51 -0
- package/dist_ts/cache/classes.secrets.scan.service.js +237 -0
- package/dist_ts/cache/documents/classes.cached.project.d.ts +13 -0
- package/dist_ts/cache/documents/classes.cached.project.js +101 -0
- package/dist_ts/cache/documents/classes.cached.secret.d.ts +24 -0
- package/dist_ts/cache/documents/classes.cached.secret.js +158 -0
- package/dist_ts/cache/documents/index.d.ts +2 -0
- package/dist_ts/cache/documents/index.js +3 -0
- package/dist_ts/cache/index.d.ts +7 -0
- package/dist_ts/cache/index.js +6 -0
- package/dist_ts/classes/actionlog.d.ts +19 -0
- package/dist_ts/classes/actionlog.js +44 -0
- package/dist_ts/classes/connectionmanager.d.ts +57 -0
- package/dist_ts/classes/connectionmanager.js +247 -0
- package/dist_ts/classes/gitopsapp.d.ts +30 -0
- package/dist_ts/classes/gitopsapp.js +101 -0
- package/dist_ts/classes/jobmanager.d.ts +47 -0
- package/dist_ts/classes/jobmanager.js +301 -0
- package/dist_ts/classes/jobrunners/autobookstackdocs.runner.d.ts +29 -0
- package/dist_ts/classes/jobrunners/autobookstackdocs.runner.js +361 -0
- package/dist_ts/classes/jobrunners/base.jobrunner.d.ts +14 -0
- package/dist_ts/classes/jobrunners/base.jobrunner.js +3 -0
- package/dist_ts/classes/jobrunners/index.d.ts +5 -0
- package/dist_ts/classes/jobrunners/index.js +14 -0
- package/dist_ts/classes/managedsecrets.manager.d.ts +47 -0
- package/dist_ts/classes/managedsecrets.manager.js +247 -0
- package/dist_ts/classes/syncmanager.d.ts +189 -0
- package/dist_ts/classes/syncmanager.js +1787 -0
- package/dist_ts/index.d.ts +6 -0
- package/dist_ts/index.js +32 -0
- package/dist_ts/logging.d.ts +49 -0
- package/dist_ts/logging.js +134 -0
- package/dist_ts/opsserver/classes.opsserver.d.ts +25 -0
- package/dist_ts/opsserver/classes.opsserver.js +70 -0
- package/dist_ts/opsserver/handlers/actionlog.handler.d.ts +9 -0
- package/dist_ts/opsserver/handlers/actionlog.handler.js +24 -0
- package/dist_ts/opsserver/handlers/actions.handler.d.ts +9 -0
- package/dist_ts/opsserver/handlers/actions.handler.js +38 -0
- package/dist_ts/opsserver/handlers/admin.handler.d.ts +19 -0
- package/dist_ts/opsserver/handlers/admin.handler.js +96 -0
- package/dist_ts/opsserver/handlers/connections.handler.d.ts +10 -0
- package/dist_ts/opsserver/handlers/connections.handler.js +109 -0
- package/dist_ts/opsserver/handlers/groups.handler.d.ts +9 -0
- package/dist_ts/opsserver/handlers/groups.handler.js +24 -0
- package/dist_ts/opsserver/handlers/index.d.ts +13 -0
- package/dist_ts/opsserver/handlers/index.js +14 -0
- package/dist_ts/opsserver/handlers/jobs.handler.d.ts +16 -0
- package/dist_ts/opsserver/handlers/jobs.handler.js +146 -0
- package/dist_ts/opsserver/handlers/logs.handler.d.ts +9 -0
- package/dist_ts/opsserver/handlers/logs.handler.js +21 -0
- package/dist_ts/opsserver/handlers/managedsecrets.handler.d.ts +11 -0
- package/dist_ts/opsserver/handlers/managedsecrets.handler.js +110 -0
- package/dist_ts/opsserver/handlers/pipelines.handler.d.ts +31 -0
- package/dist_ts/opsserver/handlers/pipelines.handler.js +204 -0
- package/dist_ts/opsserver/handlers/projects.handler.d.ts +9 -0
- package/dist_ts/opsserver/handlers/projects.handler.js +24 -0
- package/dist_ts/opsserver/handlers/secrets.handler.d.ts +10 -0
- package/dist_ts/opsserver/handlers/secrets.handler.js +171 -0
- package/dist_ts/opsserver/handlers/sync.handler.d.ts +16 -0
- package/dist_ts/opsserver/handlers/sync.handler.js +166 -0
- package/dist_ts/opsserver/handlers/webhook.handler.d.ts +7 -0
- package/dist_ts/opsserver/handlers/webhook.handler.js +55 -0
- package/dist_ts/opsserver/helpers/guards.d.ts +5 -0
- package/dist_ts/opsserver/helpers/guards.js +12 -0
- package/dist_ts/opsserver/index.d.ts +1 -0
- package/dist_ts/opsserver/index.js +2 -0
- package/dist_ts/paths.d.ts +9 -0
- package/dist_ts/paths.js +13 -0
- package/dist_ts/plugins.d.ts +25 -0
- package/dist_ts/plugins.js +32 -0
- package/dist_ts/providers/classes.baseprovider.d.ts +51 -0
- package/dist_ts/providers/classes.baseprovider.js +17 -0
- package/dist_ts/providers/classes.giteaprovider.d.ts +40 -0
- package/dist_ts/providers/classes.giteaprovider.js +224 -0
- package/dist_ts/providers/classes.gitlabprovider.d.ts +39 -0
- package/dist_ts/providers/classes.gitlabprovider.js +207 -0
- package/dist_ts/providers/index.d.ts +3 -0
- package/dist_ts/providers/index.js +4 -0
- package/dist_ts/storage/classes.storagemanager.d.ts +33 -0
- package/dist_ts/storage/classes.storagemanager.js +135 -0
- package/dist_ts/storage/index.d.ts +2 -0
- package/dist_ts/storage/index.js +2 -0
- package/dist_ts/timers.d.ts +4 -0
- package/dist_ts/timers.js +24 -0
- package/dist_ts_bundled/bundle.d.ts +4 -0
- package/dist_ts_bundled/bundle.js +12 -0
- package/dist_ts_interfaces/data/actionlog.d.ts +12 -0
- package/dist_ts_interfaces/data/actionlog.js +2 -0
- package/dist_ts_interfaces/data/branch.d.ts +8 -0
- package/dist_ts_interfaces/data/branch.js +2 -0
- package/dist_ts_interfaces/data/connection.d.ts +12 -0
- package/dist_ts_interfaces/data/connection.js +2 -0
- package/dist_ts_interfaces/data/group.d.ts +10 -0
- package/dist_ts_interfaces/data/group.js +2 -0
- package/dist_ts_interfaces/data/identity.d.ts +7 -0
- package/dist_ts_interfaces/data/identity.js +2 -0
- package/dist_ts_interfaces/data/index.d.ts +11 -0
- package/dist_ts_interfaces/data/index.js +12 -0
- package/dist_ts_interfaces/data/job.d.ts +37 -0
- package/dist_ts_interfaces/data/job.js +2 -0
- package/dist_ts_interfaces/data/managedsecret.d.ts +37 -0
- package/dist_ts_interfaces/data/managedsecret.js +2 -0
- package/dist_ts_interfaces/data/pipeline.d.ts +22 -0
- package/dist_ts_interfaces/data/pipeline.js +2 -0
- package/dist_ts_interfaces/data/project.d.ts +12 -0
- package/dist_ts_interfaces/data/project.js +2 -0
- package/dist_ts_interfaces/data/secret.d.ts +11 -0
- package/dist_ts_interfaces/data/secret.js +2 -0
- package/dist_ts_interfaces/data/sync.d.ts +34 -0
- package/dist_ts_interfaces/data/sync.js +2 -0
- package/dist_ts_interfaces/index.d.ts +5 -0
- package/dist_ts_interfaces/index.js +8 -0
- package/dist_ts_interfaces/plugins.d.ts +2 -0
- package/dist_ts_interfaces/plugins.js +4 -0
- package/dist_ts_interfaces/requests/actionlog.d.ts +15 -0
- package/dist_ts_interfaces/requests/actionlog.js +3 -0
- package/dist_ts_interfaces/requests/actions.d.ts +31 -0
- package/dist_ts_interfaces/requests/actions.js +3 -0
- package/dist_ts_interfaces/requests/admin.d.ts +31 -0
- package/dist_ts_interfaces/requests/admin.js +3 -0
- package/dist_ts_interfaces/requests/connections.d.ts +71 -0
- package/dist_ts_interfaces/requests/connections.js +3 -0
- package/dist_ts_interfaces/requests/groups.d.ts +14 -0
- package/dist_ts_interfaces/requests/groups.js +3 -0
- package/dist_ts_interfaces/requests/index.d.ts +13 -0
- package/dist_ts_interfaces/requests/index.js +14 -0
- package/dist_ts_interfaces/requests/jobs.d.ts +86 -0
- package/dist_ts_interfaces/requests/jobs.js +3 -0
- package/dist_ts_interfaces/requests/logs.d.ts +14 -0
- package/dist_ts_interfaces/requests/logs.js +3 -0
- package/dist_ts_interfaces/requests/managedsecrets.d.ts +84 -0
- package/dist_ts_interfaces/requests/managedsecrets.js +3 -0
- package/dist_ts_interfaces/requests/pipelines.d.ts +55 -0
- package/dist_ts_interfaces/requests/pipelines.js +3 -0
- package/dist_ts_interfaces/requests/projects.d.ts +14 -0
- package/dist_ts_interfaces/requests/projects.js +3 -0
- package/dist_ts_interfaces/requests/secrets.d.ts +72 -0
- package/dist_ts_interfaces/requests/secrets.js +3 -0
- package/dist_ts_interfaces/requests/sync.d.ts +120 -0
- package/dist_ts_interfaces/requests/sync.js +3 -0
- package/dist_ts_interfaces/requests/webhook.d.ts +13 -0
- package/dist_ts_interfaces/requests/webhook.js +3 -0
- package/license +21 -0
- package/package.json +81 -0
- package/readme.md +177 -0
- package/readme.todo.md +3 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/cache/classes.cache.cleaner.ts +69 -0
- package/ts/cache/classes.cached.document.ts +57 -0
- package/ts/cache/classes.cachedb.ts +72 -0
- package/ts/cache/classes.secrets.scan.service.ts +267 -0
- package/ts/cache/documents/classes.cached.project.ts +32 -0
- package/ts/cache/documents/classes.cached.secret.ts +81 -0
- package/ts/cache/documents/index.ts +2 -0
- package/ts/cache/index.ts +7 -0
- package/ts/classes/actionlog.ts +57 -0
- package/ts/classes/connectionmanager.ts +263 -0
- package/ts/classes/gitopsapp.ts +128 -0
- package/ts/classes/jobmanager.ts +337 -0
- package/ts/classes/jobrunners/autobookstackdocs.runner.ts +435 -0
- package/ts/classes/jobrunners/base.jobrunner.ts +16 -0
- package/ts/classes/jobrunners/index.ts +17 -0
- package/ts/classes/managedsecrets.manager.ts +322 -0
- package/ts/classes/syncmanager.ts +2117 -0
- package/ts/index.ts +37 -0
- package/ts/logging.ts +162 -0
- package/ts/opsserver/classes.opsserver.ts +86 -0
- package/ts/opsserver/handlers/actionlog.handler.ts +30 -0
- package/ts/opsserver/handlers/actions.handler.ts +50 -0
- package/ts/opsserver/handlers/admin.handler.ts +122 -0
- package/ts/opsserver/handlers/connections.handler.ts +162 -0
- package/ts/opsserver/handlers/groups.handler.ts +32 -0
- package/ts/opsserver/handlers/index.ts +13 -0
- package/ts/opsserver/handlers/jobs.handler.ts +189 -0
- package/ts/opsserver/handlers/logs.handler.ts +29 -0
- package/ts/opsserver/handlers/managedsecrets.handler.ts +158 -0
- package/ts/opsserver/handlers/pipelines.handler.ts +281 -0
- package/ts/opsserver/handlers/projects.handler.ts +32 -0
- package/ts/opsserver/handlers/secrets.handler.ts +224 -0
- package/ts/opsserver/handlers/sync.handler.ts +224 -0
- package/ts/opsserver/handlers/webhook.handler.ts +62 -0
- package/ts/opsserver/helpers/guards.ts +16 -0
- package/ts/opsserver/index.ts +1 -0
- package/ts/paths.ts +19 -0
- package/ts/plugins.ts +38 -0
- package/ts/providers/classes.baseprovider.ts +99 -0
- package/ts/providers/classes.giteaprovider.ts +279 -0
- package/ts/providers/classes.gitlabprovider.ts +265 -0
- package/ts/providers/index.ts +3 -0
- package/ts/storage/classes.storagemanager.ts +144 -0
- package/ts/storage/index.ts +2 -0
- package/ts/timers.ts +34 -0
- package/ts_interfaces/data/actionlog.ts +13 -0
- package/ts_interfaces/data/branch.ts +9 -0
- package/ts_interfaces/data/connection.ts +13 -0
- package/ts_interfaces/data/group.ts +10 -0
- package/ts_interfaces/data/identity.ts +7 -0
- package/ts_interfaces/data/index.ts +11 -0
- package/ts_interfaces/data/job.ts +42 -0
- package/ts_interfaces/data/managedsecret.ts +41 -0
- package/ts_interfaces/data/pipeline.ts +32 -0
- package/ts_interfaces/data/project.ts +12 -0
- package/ts_interfaces/data/secret.ts +11 -0
- package/ts_interfaces/data/sync.ts +37 -0
- package/ts_interfaces/index.ts +9 -0
- package/ts_interfaces/plugins.ts +6 -0
- package/ts_interfaces/requests/actionlog.ts +19 -0
- package/ts_interfaces/requests/actions.ts +39 -0
- package/ts_interfaces/requests/admin.ts +43 -0
- package/ts_interfaces/requests/connections.ts +95 -0
- package/ts_interfaces/requests/groups.ts +18 -0
- package/ts_interfaces/requests/index.ts +13 -0
- package/ts_interfaces/requests/jobs.ts +118 -0
- package/ts_interfaces/requests/logs.ts +18 -0
- package/ts_interfaces/requests/managedsecrets.ts +112 -0
- package/ts_interfaces/requests/pipelines.ts +71 -0
- package/ts_interfaces/requests/projects.ts +18 -0
- package/ts_interfaces/requests/secrets.ts +92 -0
- package/ts_interfaces/requests/sync.ts +157 -0
- package/ts_interfaces/requests/webhook.ts +18 -0
- package/ts_web/00_commitinfo_data.ts +8 -0
- package/ts_web/appstate.ts +1251 -0
- package/ts_web/elements/gitops-dashboard.ts +350 -0
- package/ts_web/elements/index.ts +10 -0
- package/ts_web/elements/shared/css.ts +29 -0
- package/ts_web/elements/shared/index.ts +1 -0
- package/ts_web/elements/views/actionlog/index.ts +101 -0
- package/ts_web/elements/views/actions/index.ts +209 -0
- package/ts_web/elements/views/buildlog/index.ts +196 -0
- package/ts_web/elements/views/connections/index.ts +260 -0
- package/ts_web/elements/views/groups/index.ts +134 -0
- package/ts_web/elements/views/jobs/index.ts +424 -0
- package/ts_web/elements/views/managedsecrets/index.ts +502 -0
- package/ts_web/elements/views/overview/index.ts +86 -0
- package/ts_web/elements/views/pipelines/index.ts +561 -0
- package/ts_web/elements/views/projects/index.ts +149 -0
- package/ts_web/elements/views/secrets/index.ts +310 -0
- package/ts_web/elements/views/sync/index.ts +512 -0
- package/ts_web/index.ts +7 -0
- package/ts_web/plugins.ts +15 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { CacheDb } from '../classes.cachedb.js';
|
|
3
|
+
import { CachedDocument, TTL } from '../classes.cached.document.js';
|
|
4
|
+
import type { ISecret } from '../../../ts_interfaces/data/secret.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Cached secret data from git providers. TTL: 24 hours.
|
|
8
|
+
*/
|
|
9
|
+
@plugins.smartdata.Collection(() => CacheDb.getInstance().getDb())
|
|
10
|
+
export class CachedSecret extends CachedDocument<CachedSecret> {
|
|
11
|
+
@plugins.smartdata.unI()
|
|
12
|
+
public id: string = '';
|
|
13
|
+
|
|
14
|
+
@plugins.smartdata.svDb()
|
|
15
|
+
public connectionId: string = '';
|
|
16
|
+
|
|
17
|
+
@plugins.smartdata.svDb()
|
|
18
|
+
public scope: 'project' | 'group' = 'project';
|
|
19
|
+
|
|
20
|
+
@plugins.smartdata.svDb()
|
|
21
|
+
public scopeId: string = '';
|
|
22
|
+
|
|
23
|
+
@plugins.smartdata.svDb()
|
|
24
|
+
public scopeName: string = '';
|
|
25
|
+
|
|
26
|
+
@plugins.smartdata.svDb()
|
|
27
|
+
public key: string = '';
|
|
28
|
+
|
|
29
|
+
@plugins.smartdata.svDb()
|
|
30
|
+
public value: string = '';
|
|
31
|
+
|
|
32
|
+
@plugins.smartdata.svDb()
|
|
33
|
+
public protected: boolean = false;
|
|
34
|
+
|
|
35
|
+
@plugins.smartdata.svDb()
|
|
36
|
+
public masked: boolean = false;
|
|
37
|
+
|
|
38
|
+
@plugins.smartdata.svDb()
|
|
39
|
+
public environment: string = '';
|
|
40
|
+
|
|
41
|
+
constructor() {
|
|
42
|
+
super();
|
|
43
|
+
this.setTTL(TTL.HOURS_24);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Build the composite unique ID */
|
|
47
|
+
static buildId(connectionId: string, scope: string, scopeId: string, key: string): string {
|
|
48
|
+
return `${connectionId}:${scope}:${scopeId}:${key}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Create a CachedSecret from an ISecret */
|
|
52
|
+
static fromISecret(secret: ISecret): CachedSecret {
|
|
53
|
+
const doc = new CachedSecret();
|
|
54
|
+
doc.id = CachedSecret.buildId(secret.connectionId, secret.scope, secret.scopeId, secret.key);
|
|
55
|
+
doc.connectionId = secret.connectionId;
|
|
56
|
+
doc.scope = secret.scope;
|
|
57
|
+
doc.scopeId = secret.scopeId;
|
|
58
|
+
doc.scopeName = secret.scopeName;
|
|
59
|
+
doc.key = secret.key;
|
|
60
|
+
doc.value = secret.value;
|
|
61
|
+
doc.protected = secret.protected;
|
|
62
|
+
doc.masked = secret.masked;
|
|
63
|
+
doc.environment = secret.environment;
|
|
64
|
+
return doc;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Convert back to ISecret */
|
|
68
|
+
toISecret(): ISecret {
|
|
69
|
+
return {
|
|
70
|
+
connectionId: this.connectionId,
|
|
71
|
+
scope: this.scope,
|
|
72
|
+
scopeId: this.scopeId,
|
|
73
|
+
scopeName: this.scopeName,
|
|
74
|
+
key: this.key,
|
|
75
|
+
value: this.value,
|
|
76
|
+
protected: this.protected,
|
|
77
|
+
masked: this.masked,
|
|
78
|
+
environment: this.environment,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { CacheDb } from './classes.cachedb.js';
|
|
2
|
+
export type { ICacheDbOptions } from './classes.cachedb.js';
|
|
3
|
+
export { CachedDocument, TTL } from './classes.cached.document.js';
|
|
4
|
+
export { CacheCleaner } from './classes.cache.cleaner.js';
|
|
5
|
+
export { SecretsScanService } from './classes.secrets.scan.service.js';
|
|
6
|
+
export type { IScanResult } from './classes.secrets.scan.service.js';
|
|
7
|
+
export * from './documents/index.js';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { logger } from '../logging.js';
|
|
2
|
+
import type * as interfaces from '../../ts_interfaces/index.js';
|
|
3
|
+
import type { StorageManager } from '../storage/index.js';
|
|
4
|
+
|
|
5
|
+
const ACTIONLOG_PREFIX = '/actionlog/';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Persists and queries action log entries via StorageManager.
|
|
9
|
+
* Entries are stored as individual JSON files keyed by timestamp-id.
|
|
10
|
+
*/
|
|
11
|
+
export class ActionLog {
|
|
12
|
+
private storageManager: StorageManager;
|
|
13
|
+
|
|
14
|
+
constructor(storageManager: StorageManager) {
|
|
15
|
+
this.storageManager = storageManager;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async append(entry: Omit<interfaces.data.IActionLogEntry, 'id' | 'timestamp'>): Promise<interfaces.data.IActionLogEntry> {
|
|
19
|
+
const full: interfaces.data.IActionLogEntry = {
|
|
20
|
+
id: crypto.randomUUID(),
|
|
21
|
+
timestamp: Date.now(),
|
|
22
|
+
...entry,
|
|
23
|
+
};
|
|
24
|
+
const key = `${ACTIONLOG_PREFIX}${String(full.timestamp).padStart(16, '0')}-${full.id}.json`;
|
|
25
|
+
await this.storageManager.setJSON(key, full);
|
|
26
|
+
logger.debug(`Action logged: ${full.actionType} ${full.entityType} "${full.entityName}"`);
|
|
27
|
+
return full;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async query(opts: {
|
|
31
|
+
limit?: number;
|
|
32
|
+
offset?: number;
|
|
33
|
+
entityType?: interfaces.data.TActionEntity;
|
|
34
|
+
} = {}): Promise<{ entries: interfaces.data.IActionLogEntry[]; total: number }> {
|
|
35
|
+
const limit = opts.limit ?? 50;
|
|
36
|
+
const offset = opts.offset ?? 0;
|
|
37
|
+
|
|
38
|
+
const keys = await this.storageManager.list(ACTIONLOG_PREFIX);
|
|
39
|
+
// Sort by key descending (newest first — keys are timestamp-prefixed)
|
|
40
|
+
keys.sort((a, b) => b.localeCompare(a));
|
|
41
|
+
|
|
42
|
+
// Load all entries (or filter by entityType)
|
|
43
|
+
let entries: interfaces.data.IActionLogEntry[] = [];
|
|
44
|
+
for (const key of keys) {
|
|
45
|
+
const entry = await this.storageManager.getJSON<interfaces.data.IActionLogEntry>(key);
|
|
46
|
+
if (entry) {
|
|
47
|
+
if (opts.entityType && entry.entityType !== opts.entityType) continue;
|
|
48
|
+
entries.push(entry);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const total = entries.length;
|
|
53
|
+
entries = entries.slice(offset, offset + limit);
|
|
54
|
+
|
|
55
|
+
return { entries, total };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { logger } from '../logging.js';
|
|
3
|
+
import type * as interfaces from '../../ts_interfaces/index.js';
|
|
4
|
+
import { BaseProvider, GiteaProvider, GitLabProvider } from '../providers/index.js';
|
|
5
|
+
import type { StorageManager } from '../storage/index.js';
|
|
6
|
+
|
|
7
|
+
const LEGACY_CONNECTIONS_FILE = './.nogit/connections.json';
|
|
8
|
+
const CONNECTIONS_PREFIX = '/connections/';
|
|
9
|
+
const KEYCHAIN_PREFIX = 'keychain:';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Manages provider connections — persists each connection as an
|
|
13
|
+
* individual JSON file via StorageManager. Tokens are stored in
|
|
14
|
+
* the OS keychain (or encrypted file fallback) via SmartSecret.
|
|
15
|
+
*/
|
|
16
|
+
export class ConnectionManager {
|
|
17
|
+
private connections: interfaces.data.IProviderConnection[] = [];
|
|
18
|
+
private storageManager: StorageManager;
|
|
19
|
+
private smartSecret: plugins.smartsecret.SmartSecret;
|
|
20
|
+
/** Resolves when background connection health checks complete */
|
|
21
|
+
public healthCheckDone: Promise<void> = Promise.resolve();
|
|
22
|
+
|
|
23
|
+
constructor(storageManager: StorageManager, smartSecret: plugins.smartsecret.SmartSecret) {
|
|
24
|
+
this.storageManager = storageManager;
|
|
25
|
+
this.smartSecret = smartSecret;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async init(): Promise<void> {
|
|
29
|
+
await this.migrateLegacyFile();
|
|
30
|
+
await this.loadConnections();
|
|
31
|
+
// Auto-test all connections in the background
|
|
32
|
+
this.healthCheckDone = this.testAllConnections();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Tests all loaded connections in the background and updates their status.
|
|
37
|
+
* Fire-and-forget — does not block startup.
|
|
38
|
+
*/
|
|
39
|
+
private async testAllConnections(): Promise<void> {
|
|
40
|
+
for (const conn of this.connections) {
|
|
41
|
+
if (conn.status === 'paused') continue;
|
|
42
|
+
try {
|
|
43
|
+
const provider = this.getProvider(conn.id);
|
|
44
|
+
const result = await provider.testConnection();
|
|
45
|
+
conn.status = result.ok ? 'connected' : 'error';
|
|
46
|
+
await this.persistConnection(conn);
|
|
47
|
+
} catch {
|
|
48
|
+
conn.status = 'error';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* One-time migration from the legacy .nogit/connections.json file.
|
|
55
|
+
*/
|
|
56
|
+
private async migrateLegacyFile(): Promise<void> {
|
|
57
|
+
try {
|
|
58
|
+
const text = await plugins.fs.readFile(LEGACY_CONNECTIONS_FILE, 'utf8');
|
|
59
|
+
const legacy: interfaces.data.IProviderConnection[] = JSON.parse(text);
|
|
60
|
+
if (legacy.length > 0) {
|
|
61
|
+
logger.info(`Migrating ${legacy.length} connection(s) from legacy file...`);
|
|
62
|
+
for (const conn of legacy) {
|
|
63
|
+
await this.storageManager.setJSON(`${CONNECTIONS_PREFIX}${conn.id}.json`, conn);
|
|
64
|
+
}
|
|
65
|
+
// Rename legacy file so migration doesn't repeat
|
|
66
|
+
await plugins.fs.rename(LEGACY_CONNECTIONS_FILE, LEGACY_CONNECTIONS_FILE + '.migrated');
|
|
67
|
+
logger.success('Legacy connections migrated successfully');
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// No legacy file or already migrated — nothing to do
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private async loadConnections(): Promise<void> {
|
|
75
|
+
const keys = await this.storageManager.list(CONNECTIONS_PREFIX);
|
|
76
|
+
this.connections = [];
|
|
77
|
+
for (const key of keys) {
|
|
78
|
+
const conn = await this.storageManager.getJSON<interfaces.data.IProviderConnection>(key);
|
|
79
|
+
if (conn) {
|
|
80
|
+
// Migrate legacy baseGroup/baseGroupId property names
|
|
81
|
+
if ((conn as any).baseGroup !== undefined && conn.groupFilter === undefined) {
|
|
82
|
+
conn.groupFilter = (conn as any).baseGroup;
|
|
83
|
+
delete (conn as any).baseGroup;
|
|
84
|
+
}
|
|
85
|
+
if ((conn as any).baseGroupId !== undefined && conn.groupFilterId === undefined) {
|
|
86
|
+
conn.groupFilterId = (conn as any).baseGroupId;
|
|
87
|
+
delete (conn as any).baseGroupId;
|
|
88
|
+
}
|
|
89
|
+
if (conn.token.startsWith(KEYCHAIN_PREFIX)) {
|
|
90
|
+
// Token is in keychain — retrieve it
|
|
91
|
+
const realToken = await this.smartSecret.getSecret(conn.id);
|
|
92
|
+
if (realToken) {
|
|
93
|
+
conn.token = realToken;
|
|
94
|
+
} else {
|
|
95
|
+
logger.warn(`Could not retrieve token for connection ${conn.id} from keychain`);
|
|
96
|
+
}
|
|
97
|
+
} else if (conn.token && conn.token !== '***') {
|
|
98
|
+
// Plaintext token found — auto-migrate to keychain
|
|
99
|
+
await this.migrateTokenToKeychain(conn);
|
|
100
|
+
}
|
|
101
|
+
this.connections.push(conn);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (this.connections.length > 0) {
|
|
105
|
+
logger.info(`Loaded ${this.connections.length} connection(s)`);
|
|
106
|
+
} else {
|
|
107
|
+
logger.debug('No existing connections found, starting fresh');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Migrates a plaintext token to keychain storage.
|
|
113
|
+
*/
|
|
114
|
+
private async migrateTokenToKeychain(
|
|
115
|
+
conn: interfaces.data.IProviderConnection,
|
|
116
|
+
): Promise<void> {
|
|
117
|
+
try {
|
|
118
|
+
await this.smartSecret.setSecret(conn.id, conn.token);
|
|
119
|
+
// Save sentinel to JSON file
|
|
120
|
+
const jsonConn = { ...conn, token: `${KEYCHAIN_PREFIX}${conn.id}` };
|
|
121
|
+
await this.storageManager.setJSON(`${CONNECTIONS_PREFIX}${conn.id}.json`, jsonConn);
|
|
122
|
+
logger.info(`Migrated token for connection "${conn.name}" to keychain`);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
logger.warn(`Failed to migrate token for ${conn.id} to keychain: ${err}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private async persistConnection(conn: interfaces.data.IProviderConnection): Promise<void> {
|
|
129
|
+
// Store real token in keychain
|
|
130
|
+
await this.smartSecret.setSecret(conn.id, conn.token);
|
|
131
|
+
// Save JSON with sentinel value
|
|
132
|
+
const jsonConn = { ...conn, token: `${KEYCHAIN_PREFIX}${conn.id}` };
|
|
133
|
+
await this.storageManager.setJSON(`${CONNECTIONS_PREFIX}${conn.id}.json`, jsonConn);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private async removeConnection(id: string): Promise<void> {
|
|
137
|
+
await this.smartSecret.deleteSecret(id);
|
|
138
|
+
await this.storageManager.delete(`${CONNECTIONS_PREFIX}${id}.json`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
getConnections(): interfaces.data.IProviderConnection[] {
|
|
142
|
+
return this.connections.map((c) => ({ ...c, token: '***' }));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
getConnection(id: string): interfaces.data.IProviderConnection | undefined {
|
|
146
|
+
return this.connections.find((c) => c.id === id);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async createConnection(
|
|
150
|
+
name: string,
|
|
151
|
+
providerType: interfaces.data.TProviderType,
|
|
152
|
+
baseUrl: string,
|
|
153
|
+
token: string,
|
|
154
|
+
groupFilter?: string,
|
|
155
|
+
): Promise<interfaces.data.IProviderConnection> {
|
|
156
|
+
const connection: interfaces.data.IProviderConnection = {
|
|
157
|
+
id: crypto.randomUUID(),
|
|
158
|
+
name,
|
|
159
|
+
providerType,
|
|
160
|
+
baseUrl: baseUrl.replace(/\/+$/, ''),
|
|
161
|
+
token,
|
|
162
|
+
createdAt: Date.now(),
|
|
163
|
+
status: 'disconnected',
|
|
164
|
+
groupFilter: groupFilter || undefined,
|
|
165
|
+
};
|
|
166
|
+
this.connections.push(connection);
|
|
167
|
+
await this.persistConnection(connection);
|
|
168
|
+
logger.success(`Connection created: ${name} (${providerType})`);
|
|
169
|
+
return { ...connection, token: '***' };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async updateConnection(
|
|
173
|
+
id: string,
|
|
174
|
+
updates: { name?: string; baseUrl?: string; token?: string; groupFilter?: string },
|
|
175
|
+
): Promise<interfaces.data.IProviderConnection> {
|
|
176
|
+
const conn = this.connections.find((c) => c.id === id);
|
|
177
|
+
if (!conn) throw new Error(`Connection not found: ${id}`);
|
|
178
|
+
if (updates.name) conn.name = updates.name;
|
|
179
|
+
if (updates.baseUrl) conn.baseUrl = updates.baseUrl.replace(/\/+$/, '');
|
|
180
|
+
if (updates.token) conn.token = updates.token;
|
|
181
|
+
if (updates.groupFilter !== undefined) {
|
|
182
|
+
conn.groupFilter = updates.groupFilter || undefined;
|
|
183
|
+
conn.groupFilterId = undefined; // Will be re-resolved on next test
|
|
184
|
+
}
|
|
185
|
+
await this.persistConnection(conn);
|
|
186
|
+
return { ...conn, token: '***' };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async deleteConnection(id: string): Promise<void> {
|
|
190
|
+
const idx = this.connections.findIndex((c) => c.id === id);
|
|
191
|
+
if (idx === -1) throw new Error(`Connection not found: ${id}`);
|
|
192
|
+
this.connections.splice(idx, 1);
|
|
193
|
+
await this.removeConnection(id);
|
|
194
|
+
logger.info(`Connection deleted: ${id}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async pauseConnection(id: string, paused: boolean): Promise<interfaces.data.IProviderConnection> {
|
|
198
|
+
const conn = this.connections.find((c) => c.id === id);
|
|
199
|
+
if (!conn) throw new Error(`Connection not found: ${id}`);
|
|
200
|
+
conn.status = paused ? 'paused' : 'disconnected';
|
|
201
|
+
await this.persistConnection(conn);
|
|
202
|
+
logger.info(`Connection ${paused ? 'paused' : 'resumed'}: ${conn.name}`);
|
|
203
|
+
return { ...conn, token: '***' };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async testConnection(id: string): Promise<{ ok: boolean; error?: string }> {
|
|
207
|
+
const conn = this.connections.find((c) => c.id === id)!;
|
|
208
|
+
if (conn.status === 'paused') {
|
|
209
|
+
return { ok: false, error: 'Connection is paused' };
|
|
210
|
+
}
|
|
211
|
+
const provider = this.getProvider(id);
|
|
212
|
+
const result = await provider.testConnection();
|
|
213
|
+
conn.status = result.ok ? 'connected' : 'error';
|
|
214
|
+
// Resolve group filter ID if connection has a groupFilter
|
|
215
|
+
if (result.ok && conn.groupFilter) {
|
|
216
|
+
await this.resolveGroupFilterId(conn);
|
|
217
|
+
}
|
|
218
|
+
await this.persistConnection(conn);
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Resolves a human-readable groupFilter to the provider-specific group ID.
|
|
224
|
+
*/
|
|
225
|
+
private async resolveGroupFilterId(conn: interfaces.data.IProviderConnection): Promise<void> {
|
|
226
|
+
if (!conn.groupFilter) {
|
|
227
|
+
conn.groupFilterId = undefined;
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
if (conn.providerType === 'gitlab') {
|
|
232
|
+
const gitlabClient = new plugins.gitlabClient.GitLabClient(conn.baseUrl, conn.token);
|
|
233
|
+
const group = await gitlabClient.getGroup(conn.groupFilter);
|
|
234
|
+
conn.groupFilterId = String(group.id);
|
|
235
|
+
logger.info(`Resolved group filter "${conn.groupFilter}" to ID ${conn.groupFilterId}`);
|
|
236
|
+
} else {
|
|
237
|
+
// For Gitea, the org name IS the ID
|
|
238
|
+
conn.groupFilterId = conn.groupFilter;
|
|
239
|
+
logger.info(`Group filter for Gitea connection set to org "${conn.groupFilterId}"`);
|
|
240
|
+
}
|
|
241
|
+
} catch (err) {
|
|
242
|
+
logger.warn(`Failed to resolve group filter "${conn.groupFilter}": ${err}`);
|
|
243
|
+
conn.groupFilterId = undefined;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Factory: returns the correct provider instance for a connection ID
|
|
249
|
+
*/
|
|
250
|
+
getProvider(connectionId: string): BaseProvider {
|
|
251
|
+
const conn = this.connections.find((c) => c.id === connectionId);
|
|
252
|
+
if (!conn) throw new Error(`Connection not found: ${connectionId}`);
|
|
253
|
+
|
|
254
|
+
switch (conn.providerType) {
|
|
255
|
+
case 'gitea':
|
|
256
|
+
return new GiteaProvider(conn.id, conn.baseUrl, conn.token, conn.groupFilterId);
|
|
257
|
+
case 'gitlab':
|
|
258
|
+
return new GitLabProvider(conn.id, conn.baseUrl, conn.token, conn.groupFilterId);
|
|
259
|
+
default:
|
|
260
|
+
throw new Error(`Unknown provider type: ${conn.providerType}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { logger } from '../logging.js';
|
|
3
|
+
import { ConnectionManager } from './connectionmanager.js';
|
|
4
|
+
import { ActionLog } from './actionlog.js';
|
|
5
|
+
import { SyncManager } from './syncmanager.js';
|
|
6
|
+
import { ManagedSecretsManager } from './managedsecrets.manager.js';
|
|
7
|
+
import { JobManager } from './jobmanager.js';
|
|
8
|
+
import { OpsServer } from '../opsserver/index.js';
|
|
9
|
+
import { StorageManager } from '../storage/index.js';
|
|
10
|
+
import { CacheDb, CacheCleaner, CachedProject, CachedSecret, SecretsScanService } from '../cache/index.js';
|
|
11
|
+
import { resolvePaths } from '../paths.js';
|
|
12
|
+
import { unrefTimer } from '../timers.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Main GitOps application orchestrator
|
|
16
|
+
*/
|
|
17
|
+
export class GitopsApp {
|
|
18
|
+
public storageManager: StorageManager;
|
|
19
|
+
public smartSecret: plugins.smartsecret.SmartSecret;
|
|
20
|
+
public connectionManager: ConnectionManager;
|
|
21
|
+
public actionLog: ActionLog;
|
|
22
|
+
public opsServer: OpsServer;
|
|
23
|
+
public cacheDb: CacheDb;
|
|
24
|
+
public cacheCleaner: CacheCleaner;
|
|
25
|
+
public syncManager!: SyncManager;
|
|
26
|
+
public managedSecretsManager!: ManagedSecretsManager;
|
|
27
|
+
public jobManager!: JobManager;
|
|
28
|
+
public secretsScanService!: SecretsScanService;
|
|
29
|
+
private scanIntervalId: ReturnType<typeof setInterval> | null = null;
|
|
30
|
+
private paths: ReturnType<typeof resolvePaths>;
|
|
31
|
+
|
|
32
|
+
constructor() {
|
|
33
|
+
const paths = resolvePaths();
|
|
34
|
+
this.paths = paths;
|
|
35
|
+
this.storageManager = new StorageManager({
|
|
36
|
+
backend: 'filesystem',
|
|
37
|
+
fsPath: paths.defaultStoragePath,
|
|
38
|
+
});
|
|
39
|
+
this.smartSecret = new plugins.smartsecret.SmartSecret({ service: 'gitops' });
|
|
40
|
+
this.connectionManager = new ConnectionManager(this.storageManager, this.smartSecret);
|
|
41
|
+
this.actionLog = new ActionLog(this.storageManager);
|
|
42
|
+
|
|
43
|
+
this.cacheDb = CacheDb.getInstance({
|
|
44
|
+
storagePath: paths.defaultTsmDbPath,
|
|
45
|
+
dbName: 'gitops_cache',
|
|
46
|
+
});
|
|
47
|
+
this.cacheCleaner = new CacheCleaner(this.cacheDb);
|
|
48
|
+
this.cacheCleaner.registerClass(CachedProject);
|
|
49
|
+
this.cacheCleaner.registerClass(CachedSecret);
|
|
50
|
+
|
|
51
|
+
this.opsServer = new OpsServer(this);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async start(port = 3000): Promise<void> {
|
|
55
|
+
logger.info('Initializing GitOps...');
|
|
56
|
+
|
|
57
|
+
// Start CacheDb
|
|
58
|
+
await this.cacheDb.start();
|
|
59
|
+
|
|
60
|
+
// Initialize connection manager (loads saved connections)
|
|
61
|
+
await this.connectionManager.init();
|
|
62
|
+
|
|
63
|
+
// Initialize managed secrets manager
|
|
64
|
+
this.managedSecretsManager = new ManagedSecretsManager(
|
|
65
|
+
this.storageManager,
|
|
66
|
+
this.smartSecret,
|
|
67
|
+
this.connectionManager,
|
|
68
|
+
);
|
|
69
|
+
await this.managedSecretsManager.init();
|
|
70
|
+
|
|
71
|
+
// Initialize sync manager
|
|
72
|
+
this.syncManager = new SyncManager(
|
|
73
|
+
this.storageManager,
|
|
74
|
+
this.connectionManager,
|
|
75
|
+
this.actionLog,
|
|
76
|
+
);
|
|
77
|
+
await this.syncManager.init();
|
|
78
|
+
|
|
79
|
+
// Initialize job manager
|
|
80
|
+
this.jobManager = new JobManager(
|
|
81
|
+
this.storageManager,
|
|
82
|
+
this.connectionManager,
|
|
83
|
+
this.actionLog,
|
|
84
|
+
this.smartSecret,
|
|
85
|
+
);
|
|
86
|
+
await this.jobManager.init();
|
|
87
|
+
|
|
88
|
+
// Initialize secrets scan service with 24h auto-scan
|
|
89
|
+
this.secretsScanService = new SecretsScanService(this.connectionManager);
|
|
90
|
+
const SCAN_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
91
|
+
this.scanIntervalId = setInterval(() => {
|
|
92
|
+
this.secretsScanService.fullScan().catch((err) => {
|
|
93
|
+
logger.error(`Scheduled secrets scan failed: ${err}`);
|
|
94
|
+
});
|
|
95
|
+
}, SCAN_INTERVAL_MS);
|
|
96
|
+
unrefTimer(this.scanIntervalId);
|
|
97
|
+
// Fire-and-forget initial scan (doesn't block startup)
|
|
98
|
+
this.secretsScanService.fullScan().catch((err) => {
|
|
99
|
+
logger.error(`Initial secrets scan failed: ${err}`);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Start CacheCleaner
|
|
103
|
+
this.cacheCleaner.start();
|
|
104
|
+
|
|
105
|
+
// Start OpsServer
|
|
106
|
+
await this.opsServer.start(port);
|
|
107
|
+
|
|
108
|
+
logger.success('GitOps initialized successfully');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async stop(): Promise<void> {
|
|
112
|
+
logger.info('Shutting down GitOps...');
|
|
113
|
+
if (this.scanIntervalId !== null) {
|
|
114
|
+
clearInterval(this.scanIntervalId);
|
|
115
|
+
this.scanIntervalId = null;
|
|
116
|
+
}
|
|
117
|
+
await this.opsServer.stop();
|
|
118
|
+
if (this.jobManager) {
|
|
119
|
+
await this.jobManager.stop();
|
|
120
|
+
}
|
|
121
|
+
if (this.syncManager) {
|
|
122
|
+
await this.syncManager.stop();
|
|
123
|
+
}
|
|
124
|
+
this.cacheCleaner.stop();
|
|
125
|
+
await this.cacheDb.stop();
|
|
126
|
+
logger.success('GitOps shutdown complete');
|
|
127
|
+
}
|
|
128
|
+
}
|