@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.
Files changed (258) hide show
  1. package/.smartconfig.json +114 -0
  2. package/binary/gitops.ts +4 -0
  3. package/changelog.md +185 -0
  4. package/cli.child.js +4 -0
  5. package/cli.js +4 -0
  6. package/cli.ts.js +5 -0
  7. package/deno.json +10 -0
  8. package/dist_serve/bundle.js +36362 -0
  9. package/dist_serve/index.html +33 -0
  10. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  11. package/dist_ts/00_commitinfo_data.js +9 -0
  12. package/dist_ts/cache/classes.cache.cleaner.d.ts +23 -0
  13. package/dist_ts/cache/classes.cache.cleaner.js +61 -0
  14. package/dist_ts/cache/classes.cached.document.d.ts +30 -0
  15. package/dist_ts/cache/classes.cached.document.js +101 -0
  16. package/dist_ts/cache/classes.cachedb.d.ts +22 -0
  17. package/dist_ts/cache/classes.cachedb.js +58 -0
  18. package/dist_ts/cache/classes.secrets.scan.service.d.ts +51 -0
  19. package/dist_ts/cache/classes.secrets.scan.service.js +237 -0
  20. package/dist_ts/cache/documents/classes.cached.project.d.ts +13 -0
  21. package/dist_ts/cache/documents/classes.cached.project.js +101 -0
  22. package/dist_ts/cache/documents/classes.cached.secret.d.ts +24 -0
  23. package/dist_ts/cache/documents/classes.cached.secret.js +158 -0
  24. package/dist_ts/cache/documents/index.d.ts +2 -0
  25. package/dist_ts/cache/documents/index.js +3 -0
  26. package/dist_ts/cache/index.d.ts +7 -0
  27. package/dist_ts/cache/index.js +6 -0
  28. package/dist_ts/classes/actionlog.d.ts +19 -0
  29. package/dist_ts/classes/actionlog.js +44 -0
  30. package/dist_ts/classes/connectionmanager.d.ts +57 -0
  31. package/dist_ts/classes/connectionmanager.js +247 -0
  32. package/dist_ts/classes/gitopsapp.d.ts +30 -0
  33. package/dist_ts/classes/gitopsapp.js +101 -0
  34. package/dist_ts/classes/jobmanager.d.ts +47 -0
  35. package/dist_ts/classes/jobmanager.js +301 -0
  36. package/dist_ts/classes/jobrunners/autobookstackdocs.runner.d.ts +29 -0
  37. package/dist_ts/classes/jobrunners/autobookstackdocs.runner.js +361 -0
  38. package/dist_ts/classes/jobrunners/base.jobrunner.d.ts +14 -0
  39. package/dist_ts/classes/jobrunners/base.jobrunner.js +3 -0
  40. package/dist_ts/classes/jobrunners/index.d.ts +5 -0
  41. package/dist_ts/classes/jobrunners/index.js +14 -0
  42. package/dist_ts/classes/managedsecrets.manager.d.ts +47 -0
  43. package/dist_ts/classes/managedsecrets.manager.js +247 -0
  44. package/dist_ts/classes/syncmanager.d.ts +189 -0
  45. package/dist_ts/classes/syncmanager.js +1787 -0
  46. package/dist_ts/index.d.ts +6 -0
  47. package/dist_ts/index.js +32 -0
  48. package/dist_ts/logging.d.ts +49 -0
  49. package/dist_ts/logging.js +134 -0
  50. package/dist_ts/opsserver/classes.opsserver.d.ts +25 -0
  51. package/dist_ts/opsserver/classes.opsserver.js +70 -0
  52. package/dist_ts/opsserver/handlers/actionlog.handler.d.ts +9 -0
  53. package/dist_ts/opsserver/handlers/actionlog.handler.js +24 -0
  54. package/dist_ts/opsserver/handlers/actions.handler.d.ts +9 -0
  55. package/dist_ts/opsserver/handlers/actions.handler.js +38 -0
  56. package/dist_ts/opsserver/handlers/admin.handler.d.ts +19 -0
  57. package/dist_ts/opsserver/handlers/admin.handler.js +96 -0
  58. package/dist_ts/opsserver/handlers/connections.handler.d.ts +10 -0
  59. package/dist_ts/opsserver/handlers/connections.handler.js +109 -0
  60. package/dist_ts/opsserver/handlers/groups.handler.d.ts +9 -0
  61. package/dist_ts/opsserver/handlers/groups.handler.js +24 -0
  62. package/dist_ts/opsserver/handlers/index.d.ts +13 -0
  63. package/dist_ts/opsserver/handlers/index.js +14 -0
  64. package/dist_ts/opsserver/handlers/jobs.handler.d.ts +16 -0
  65. package/dist_ts/opsserver/handlers/jobs.handler.js +146 -0
  66. package/dist_ts/opsserver/handlers/logs.handler.d.ts +9 -0
  67. package/dist_ts/opsserver/handlers/logs.handler.js +21 -0
  68. package/dist_ts/opsserver/handlers/managedsecrets.handler.d.ts +11 -0
  69. package/dist_ts/opsserver/handlers/managedsecrets.handler.js +110 -0
  70. package/dist_ts/opsserver/handlers/pipelines.handler.d.ts +31 -0
  71. package/dist_ts/opsserver/handlers/pipelines.handler.js +204 -0
  72. package/dist_ts/opsserver/handlers/projects.handler.d.ts +9 -0
  73. package/dist_ts/opsserver/handlers/projects.handler.js +24 -0
  74. package/dist_ts/opsserver/handlers/secrets.handler.d.ts +10 -0
  75. package/dist_ts/opsserver/handlers/secrets.handler.js +171 -0
  76. package/dist_ts/opsserver/handlers/sync.handler.d.ts +16 -0
  77. package/dist_ts/opsserver/handlers/sync.handler.js +166 -0
  78. package/dist_ts/opsserver/handlers/webhook.handler.d.ts +7 -0
  79. package/dist_ts/opsserver/handlers/webhook.handler.js +55 -0
  80. package/dist_ts/opsserver/helpers/guards.d.ts +5 -0
  81. package/dist_ts/opsserver/helpers/guards.js +12 -0
  82. package/dist_ts/opsserver/index.d.ts +1 -0
  83. package/dist_ts/opsserver/index.js +2 -0
  84. package/dist_ts/paths.d.ts +9 -0
  85. package/dist_ts/paths.js +13 -0
  86. package/dist_ts/plugins.d.ts +25 -0
  87. package/dist_ts/plugins.js +32 -0
  88. package/dist_ts/providers/classes.baseprovider.d.ts +51 -0
  89. package/dist_ts/providers/classes.baseprovider.js +17 -0
  90. package/dist_ts/providers/classes.giteaprovider.d.ts +40 -0
  91. package/dist_ts/providers/classes.giteaprovider.js +224 -0
  92. package/dist_ts/providers/classes.gitlabprovider.d.ts +39 -0
  93. package/dist_ts/providers/classes.gitlabprovider.js +207 -0
  94. package/dist_ts/providers/index.d.ts +3 -0
  95. package/dist_ts/providers/index.js +4 -0
  96. package/dist_ts/storage/classes.storagemanager.d.ts +33 -0
  97. package/dist_ts/storage/classes.storagemanager.js +135 -0
  98. package/dist_ts/storage/index.d.ts +2 -0
  99. package/dist_ts/storage/index.js +2 -0
  100. package/dist_ts/timers.d.ts +4 -0
  101. package/dist_ts/timers.js +24 -0
  102. package/dist_ts_bundled/bundle.d.ts +4 -0
  103. package/dist_ts_bundled/bundle.js +12 -0
  104. package/dist_ts_interfaces/data/actionlog.d.ts +12 -0
  105. package/dist_ts_interfaces/data/actionlog.js +2 -0
  106. package/dist_ts_interfaces/data/branch.d.ts +8 -0
  107. package/dist_ts_interfaces/data/branch.js +2 -0
  108. package/dist_ts_interfaces/data/connection.d.ts +12 -0
  109. package/dist_ts_interfaces/data/connection.js +2 -0
  110. package/dist_ts_interfaces/data/group.d.ts +10 -0
  111. package/dist_ts_interfaces/data/group.js +2 -0
  112. package/dist_ts_interfaces/data/identity.d.ts +7 -0
  113. package/dist_ts_interfaces/data/identity.js +2 -0
  114. package/dist_ts_interfaces/data/index.d.ts +11 -0
  115. package/dist_ts_interfaces/data/index.js +12 -0
  116. package/dist_ts_interfaces/data/job.d.ts +37 -0
  117. package/dist_ts_interfaces/data/job.js +2 -0
  118. package/dist_ts_interfaces/data/managedsecret.d.ts +37 -0
  119. package/dist_ts_interfaces/data/managedsecret.js +2 -0
  120. package/dist_ts_interfaces/data/pipeline.d.ts +22 -0
  121. package/dist_ts_interfaces/data/pipeline.js +2 -0
  122. package/dist_ts_interfaces/data/project.d.ts +12 -0
  123. package/dist_ts_interfaces/data/project.js +2 -0
  124. package/dist_ts_interfaces/data/secret.d.ts +11 -0
  125. package/dist_ts_interfaces/data/secret.js +2 -0
  126. package/dist_ts_interfaces/data/sync.d.ts +34 -0
  127. package/dist_ts_interfaces/data/sync.js +2 -0
  128. package/dist_ts_interfaces/index.d.ts +5 -0
  129. package/dist_ts_interfaces/index.js +8 -0
  130. package/dist_ts_interfaces/plugins.d.ts +2 -0
  131. package/dist_ts_interfaces/plugins.js +4 -0
  132. package/dist_ts_interfaces/requests/actionlog.d.ts +15 -0
  133. package/dist_ts_interfaces/requests/actionlog.js +3 -0
  134. package/dist_ts_interfaces/requests/actions.d.ts +31 -0
  135. package/dist_ts_interfaces/requests/actions.js +3 -0
  136. package/dist_ts_interfaces/requests/admin.d.ts +31 -0
  137. package/dist_ts_interfaces/requests/admin.js +3 -0
  138. package/dist_ts_interfaces/requests/connections.d.ts +71 -0
  139. package/dist_ts_interfaces/requests/connections.js +3 -0
  140. package/dist_ts_interfaces/requests/groups.d.ts +14 -0
  141. package/dist_ts_interfaces/requests/groups.js +3 -0
  142. package/dist_ts_interfaces/requests/index.d.ts +13 -0
  143. package/dist_ts_interfaces/requests/index.js +14 -0
  144. package/dist_ts_interfaces/requests/jobs.d.ts +86 -0
  145. package/dist_ts_interfaces/requests/jobs.js +3 -0
  146. package/dist_ts_interfaces/requests/logs.d.ts +14 -0
  147. package/dist_ts_interfaces/requests/logs.js +3 -0
  148. package/dist_ts_interfaces/requests/managedsecrets.d.ts +84 -0
  149. package/dist_ts_interfaces/requests/managedsecrets.js +3 -0
  150. package/dist_ts_interfaces/requests/pipelines.d.ts +55 -0
  151. package/dist_ts_interfaces/requests/pipelines.js +3 -0
  152. package/dist_ts_interfaces/requests/projects.d.ts +14 -0
  153. package/dist_ts_interfaces/requests/projects.js +3 -0
  154. package/dist_ts_interfaces/requests/secrets.d.ts +72 -0
  155. package/dist_ts_interfaces/requests/secrets.js +3 -0
  156. package/dist_ts_interfaces/requests/sync.d.ts +120 -0
  157. package/dist_ts_interfaces/requests/sync.js +3 -0
  158. package/dist_ts_interfaces/requests/webhook.d.ts +13 -0
  159. package/dist_ts_interfaces/requests/webhook.js +3 -0
  160. package/license +21 -0
  161. package/package.json +81 -0
  162. package/readme.md +177 -0
  163. package/readme.todo.md +3 -0
  164. package/ts/00_commitinfo_data.ts +8 -0
  165. package/ts/cache/classes.cache.cleaner.ts +69 -0
  166. package/ts/cache/classes.cached.document.ts +57 -0
  167. package/ts/cache/classes.cachedb.ts +72 -0
  168. package/ts/cache/classes.secrets.scan.service.ts +267 -0
  169. package/ts/cache/documents/classes.cached.project.ts +32 -0
  170. package/ts/cache/documents/classes.cached.secret.ts +81 -0
  171. package/ts/cache/documents/index.ts +2 -0
  172. package/ts/cache/index.ts +7 -0
  173. package/ts/classes/actionlog.ts +57 -0
  174. package/ts/classes/connectionmanager.ts +263 -0
  175. package/ts/classes/gitopsapp.ts +128 -0
  176. package/ts/classes/jobmanager.ts +337 -0
  177. package/ts/classes/jobrunners/autobookstackdocs.runner.ts +435 -0
  178. package/ts/classes/jobrunners/base.jobrunner.ts +16 -0
  179. package/ts/classes/jobrunners/index.ts +17 -0
  180. package/ts/classes/managedsecrets.manager.ts +322 -0
  181. package/ts/classes/syncmanager.ts +2117 -0
  182. package/ts/index.ts +37 -0
  183. package/ts/logging.ts +162 -0
  184. package/ts/opsserver/classes.opsserver.ts +86 -0
  185. package/ts/opsserver/handlers/actionlog.handler.ts +30 -0
  186. package/ts/opsserver/handlers/actions.handler.ts +50 -0
  187. package/ts/opsserver/handlers/admin.handler.ts +122 -0
  188. package/ts/opsserver/handlers/connections.handler.ts +162 -0
  189. package/ts/opsserver/handlers/groups.handler.ts +32 -0
  190. package/ts/opsserver/handlers/index.ts +13 -0
  191. package/ts/opsserver/handlers/jobs.handler.ts +189 -0
  192. package/ts/opsserver/handlers/logs.handler.ts +29 -0
  193. package/ts/opsserver/handlers/managedsecrets.handler.ts +158 -0
  194. package/ts/opsserver/handlers/pipelines.handler.ts +281 -0
  195. package/ts/opsserver/handlers/projects.handler.ts +32 -0
  196. package/ts/opsserver/handlers/secrets.handler.ts +224 -0
  197. package/ts/opsserver/handlers/sync.handler.ts +224 -0
  198. package/ts/opsserver/handlers/webhook.handler.ts +62 -0
  199. package/ts/opsserver/helpers/guards.ts +16 -0
  200. package/ts/opsserver/index.ts +1 -0
  201. package/ts/paths.ts +19 -0
  202. package/ts/plugins.ts +38 -0
  203. package/ts/providers/classes.baseprovider.ts +99 -0
  204. package/ts/providers/classes.giteaprovider.ts +279 -0
  205. package/ts/providers/classes.gitlabprovider.ts +265 -0
  206. package/ts/providers/index.ts +3 -0
  207. package/ts/storage/classes.storagemanager.ts +144 -0
  208. package/ts/storage/index.ts +2 -0
  209. package/ts/timers.ts +34 -0
  210. package/ts_interfaces/data/actionlog.ts +13 -0
  211. package/ts_interfaces/data/branch.ts +9 -0
  212. package/ts_interfaces/data/connection.ts +13 -0
  213. package/ts_interfaces/data/group.ts +10 -0
  214. package/ts_interfaces/data/identity.ts +7 -0
  215. package/ts_interfaces/data/index.ts +11 -0
  216. package/ts_interfaces/data/job.ts +42 -0
  217. package/ts_interfaces/data/managedsecret.ts +41 -0
  218. package/ts_interfaces/data/pipeline.ts +32 -0
  219. package/ts_interfaces/data/project.ts +12 -0
  220. package/ts_interfaces/data/secret.ts +11 -0
  221. package/ts_interfaces/data/sync.ts +37 -0
  222. package/ts_interfaces/index.ts +9 -0
  223. package/ts_interfaces/plugins.ts +6 -0
  224. package/ts_interfaces/requests/actionlog.ts +19 -0
  225. package/ts_interfaces/requests/actions.ts +39 -0
  226. package/ts_interfaces/requests/admin.ts +43 -0
  227. package/ts_interfaces/requests/connections.ts +95 -0
  228. package/ts_interfaces/requests/groups.ts +18 -0
  229. package/ts_interfaces/requests/index.ts +13 -0
  230. package/ts_interfaces/requests/jobs.ts +118 -0
  231. package/ts_interfaces/requests/logs.ts +18 -0
  232. package/ts_interfaces/requests/managedsecrets.ts +112 -0
  233. package/ts_interfaces/requests/pipelines.ts +71 -0
  234. package/ts_interfaces/requests/projects.ts +18 -0
  235. package/ts_interfaces/requests/secrets.ts +92 -0
  236. package/ts_interfaces/requests/sync.ts +157 -0
  237. package/ts_interfaces/requests/webhook.ts +18 -0
  238. package/ts_web/00_commitinfo_data.ts +8 -0
  239. package/ts_web/appstate.ts +1251 -0
  240. package/ts_web/elements/gitops-dashboard.ts +350 -0
  241. package/ts_web/elements/index.ts +10 -0
  242. package/ts_web/elements/shared/css.ts +29 -0
  243. package/ts_web/elements/shared/index.ts +1 -0
  244. package/ts_web/elements/views/actionlog/index.ts +101 -0
  245. package/ts_web/elements/views/actions/index.ts +209 -0
  246. package/ts_web/elements/views/buildlog/index.ts +196 -0
  247. package/ts_web/elements/views/connections/index.ts +260 -0
  248. package/ts_web/elements/views/groups/index.ts +134 -0
  249. package/ts_web/elements/views/jobs/index.ts +424 -0
  250. package/ts_web/elements/views/managedsecrets/index.ts +502 -0
  251. package/ts_web/elements/views/overview/index.ts +86 -0
  252. package/ts_web/elements/views/pipelines/index.ts +561 -0
  253. package/ts_web/elements/views/projects/index.ts +149 -0
  254. package/ts_web/elements/views/secrets/index.ts +310 -0
  255. package/ts_web/elements/views/sync/index.ts +512 -0
  256. package/ts_web/index.ts +7 -0
  257. package/ts_web/plugins.ts +15 -0
  258. package/tsconfig.json +15 -0
@@ -0,0 +1,512 @@
1
+ import * as plugins from '../../../plugins.js';
2
+ import * as appstate from '../../../appstate.js';
3
+ import { viewHostCss } from '../../shared/index.js';
4
+ import {
5
+ DeesElement,
6
+ customElement,
7
+ html,
8
+ state,
9
+ css,
10
+ cssManager,
11
+ type TemplateResult,
12
+ } from '@design.estate/dees-element';
13
+
14
+ @customElement('gitops-view-sync')
15
+ export class GitopsViewSync extends DeesElement {
16
+ @state()
17
+ accessor syncState: appstate.ISyncState = { configs: [], repoStatuses: [] };
18
+
19
+ @state()
20
+ accessor connectionsState: appstate.IConnectionsState = {
21
+ connections: [],
22
+ activeConnectionId: null,
23
+ };
24
+
25
+ private _autoRefreshHandler: () => void;
26
+ private _syncLogHandler: (e: Event) => void;
27
+
28
+ constructor() {
29
+ super();
30
+ const syncSub = appstate.syncStatePart
31
+ .select((s) => s)
32
+ .subscribe((s) => { this.syncState = s; });
33
+ this.rxSubscriptions.push(syncSub);
34
+
35
+ const connSub = appstate.connectionsStatePart
36
+ .select((s) => s)
37
+ .subscribe((s) => { this.connectionsState = s; });
38
+ this.rxSubscriptions.push(connSub);
39
+
40
+ this._autoRefreshHandler = () => this.refresh();
41
+ document.addEventListener('gitops-auto-refresh', this._autoRefreshHandler);
42
+
43
+ // Listen for server-push sync log entries via TypedSocket
44
+ this._syncLogHandler = (e: Event) => {
45
+ const entry = (e as CustomEvent).detail;
46
+ if (!entry) return;
47
+ const chartLog = this.shadowRoot?.querySelector('dees-chart-log') as any;
48
+ if (chartLog?.addLog) {
49
+ chartLog.addLog(entry.level, entry.message, entry.source);
50
+ }
51
+ };
52
+ document.addEventListener('gitops-sync-log-entry', this._syncLogHandler);
53
+ }
54
+
55
+ public override disconnectedCallback() {
56
+ super.disconnectedCallback();
57
+ document.removeEventListener('gitops-auto-refresh', this._autoRefreshHandler);
58
+ document.removeEventListener('gitops-sync-log-entry', this._syncLogHandler);
59
+ }
60
+
61
+ public static styles = [
62
+ cssManager.defaultStyles,
63
+ viewHostCss,
64
+ css`
65
+ .status-badge {
66
+ display: inline-block;
67
+ padding: 2px 8px;
68
+ border-radius: 4px;
69
+ font-size: 12px;
70
+ font-weight: 600;
71
+ text-transform: uppercase;
72
+ }
73
+ .status-active { background: #1a3a1a; color: #00ff88; }
74
+ .status-paused { background: #3a3a1a; color: #ffaa00; }
75
+ .status-error { background: #3a1a1a; color: #ff4444; }
76
+
77
+ dees-chart-log {
78
+ margin-top: 24px;
79
+ }
80
+ `,
81
+ ];
82
+
83
+ public render(): TemplateResult {
84
+ return html`
85
+ <div class="view-title">Sync</div>
86
+ <div class="view-description">Mirror repositories between Gitea and GitLab instances</div>
87
+ <div class="toolbar">
88
+ <dees-button @click=${() => this.addSyncConfig()}>Add Sync</dees-button>
89
+ <dees-button @click=${() => this.refresh()}>Refresh</dees-button>
90
+ </div>
91
+ <dees-table
92
+ .heading1=${'Sync Configurations'}
93
+ .heading2=${'Automatic repository mirroring between instances'}
94
+ .data=${this.syncState.configs}
95
+ .displayFunction=${(item: any) => {
96
+ const sourceConn = this.connectionsState.connections.find((c) => c.id === item.sourceConnectionId);
97
+ const targetConn = this.connectionsState.connections.find((c) => c.id === item.targetConnectionId);
98
+ return {
99
+ Name: item.name,
100
+ Source: sourceConn?.name || item.sourceConnectionId,
101
+ 'Target': `${targetConn?.name || item.targetConnectionId}${item.targetGroupOffset ? ` → ${item.targetGroupOffset}/` : ''}`,
102
+ Interval: `${item.intervalMinutes}m`,
103
+ Status: item.status,
104
+ 'Enforce Delete': item.enforceDelete ? 'Yes' : 'No',
105
+ 'Enforce Group Delete': item.enforceGroupDelete ? 'Yes' : 'No',
106
+ 'Mirror Hint': item.addMirrorHint ? 'Yes' : 'No',
107
+ 'Group Avatars': item.useGroupAvatarsForProjects ? 'Yes' : 'No',
108
+ 'Last Sync': item.lastSyncAt ? new Date(item.lastSyncAt).toLocaleString() : 'Never',
109
+ Repos: String(item.reposSynced),
110
+ };
111
+ }}
112
+ .dataActions=${[
113
+ {
114
+ name: 'Preview',
115
+ iconName: 'lucide:eye',
116
+ type: ['inRow', 'contextmenu'],
117
+ actionFunc: async ({ item }: any) => { await this.previewSync(item); },
118
+ },
119
+ {
120
+ name: 'Trigger Now',
121
+ iconName: 'lucide:play',
122
+ type: ['inRow', 'contextmenu'],
123
+ actionFunc: async ({ item }: any) => {
124
+ const statusNote = item.status === 'paused' ? ' (config is paused — this is a one-off run)' : '';
125
+ await plugins.deesCatalog.DeesModal.createAndShow({
126
+ heading: 'Trigger Sync',
127
+ content: html`<p style="color: #fff;">Run sync "${item.name}" now?${statusNote}</p>`,
128
+ menuOptions: [
129
+ { name: 'Cancel', action: async (modal: any) => { modal.destroy(); } },
130
+ {
131
+ name: 'Trigger',
132
+ action: async (modal: any) => {
133
+ await appstate.syncStatePart.dispatchAction(appstate.triggerSyncAction, {
134
+ syncConfigId: item.id,
135
+ });
136
+ modal.destroy();
137
+ },
138
+ },
139
+ ],
140
+ });
141
+ },
142
+ },
143
+ {
144
+ name: 'View Repos',
145
+ iconName: 'lucide:list',
146
+ type: ['inRow', 'contextmenu'],
147
+ actionFunc: async ({ item }: any) => { await this.viewRepoStatuses(item); },
148
+ },
149
+ {
150
+ name: 'Edit',
151
+ iconName: 'lucide:edit',
152
+ type: ['inRow', 'contextmenu'],
153
+ actionFunc: async ({ item }: any) => { await this.editSyncConfig(item); },
154
+ },
155
+ {
156
+ name: 'Pause/Resume',
157
+ iconName: 'lucide:pauseCircle',
158
+ type: ['inRow', 'contextmenu'],
159
+ actionFunc: async ({ item }: any) => {
160
+ const isPaused = item.status === 'paused';
161
+ const actionLabel = isPaused ? 'Resume' : 'Pause';
162
+ await plugins.deesCatalog.DeesModal.createAndShow({
163
+ heading: `${actionLabel} Sync`,
164
+ content: html`<p style="color: #fff;">Are you sure you want to ${actionLabel.toLowerCase()} sync "${item.name}"?</p>`,
165
+ menuOptions: [
166
+ { name: 'Cancel', action: async (modal: any) => { modal.destroy(); } },
167
+ {
168
+ name: actionLabel,
169
+ action: async (modal: any) => {
170
+ await appstate.syncStatePart.dispatchAction(appstate.pauseSyncConfigAction, {
171
+ syncConfigId: item.id,
172
+ paused: !isPaused,
173
+ });
174
+ modal.destroy();
175
+ },
176
+ },
177
+ ],
178
+ });
179
+ },
180
+ },
181
+ {
182
+ name: 'Delete',
183
+ iconName: 'lucide:trash2',
184
+ type: ['inRow', 'contextmenu'],
185
+ actionFunc: async ({ item }: any) => {
186
+ await plugins.deesCatalog.DeesModal.createAndShow({
187
+ heading: 'Delete Sync Config',
188
+ content: html`<p style="color: #fff;">Are you sure you want to delete sync config "${item.name}"? This will also remove all local mirror data.</p>`,
189
+ menuOptions: [
190
+ { name: 'Cancel', action: async (modal: any) => { modal.destroy(); } },
191
+ {
192
+ name: 'Delete',
193
+ action: async (modal: any) => {
194
+ await appstate.syncStatePart.dispatchAction(appstate.deleteSyncConfigAction, {
195
+ syncConfigId: item.id,
196
+ });
197
+ modal.destroy();
198
+ },
199
+ },
200
+ ],
201
+ });
202
+ },
203
+ },
204
+ ]}
205
+ ></dees-table>
206
+ <dees-chart-log
207
+ .label=${'Sync Activity Log'}
208
+ .autoScroll=${true}
209
+ .maxEntries=${500}
210
+ ></dees-chart-log>
211
+ `;
212
+ }
213
+
214
+ async firstUpdated() {
215
+ await appstate.connectionsStatePart.dispatchAction(appstate.fetchConnectionsAction, null);
216
+ await this.refresh();
217
+ // Initialize TypedSocket for server-push sync log entries
218
+ await appstate.initSyncLogSocket();
219
+ // Load existing log entries
220
+ await this.loadExistingLogs();
221
+ }
222
+
223
+ private async loadExistingLogs() {
224
+ try {
225
+ const logs = await appstate.fetchSyncLogs(200);
226
+ const chartLog = this.shadowRoot?.querySelector('dees-chart-log') as any;
227
+ if (chartLog?.updateLog && logs.length > 0) {
228
+ chartLog.updateLog(
229
+ logs.map((entry) => ({
230
+ timestamp: new Date(entry.timestamp).toISOString(),
231
+ level: entry.level,
232
+ message: entry.message,
233
+ source: entry.source,
234
+ })),
235
+ );
236
+ }
237
+ } catch (err) {
238
+ console.error('Failed to load sync logs:', err);
239
+ }
240
+ }
241
+
242
+ private async refresh() {
243
+ await appstate.syncStatePart.dispatchAction(appstate.fetchSyncConfigsAction, null);
244
+ }
245
+
246
+ private async addSyncConfig() {
247
+ const connectionOptions = this.connectionsState.connections.map((c) => ({
248
+ option: `${c.name} (${c.providerType})`,
249
+ key: c.id,
250
+ }));
251
+
252
+ await plugins.deesCatalog.DeesModal.createAndShow({
253
+ heading: 'Add Sync Configuration',
254
+ content: html`
255
+ <style>.form-row { margin-bottom: 16px; }</style>
256
+ <div class="form-row">
257
+ <dees-input-text .label=${'Name'} .key=${'name'} .description=${'A human-readable name for this sync configuration'}></dees-input-text>
258
+ </div>
259
+ <div class="form-row">
260
+ <dees-input-dropdown
261
+ .label=${'Source Connection'}
262
+ .key=${'sourceConnectionId'}
263
+ .description=${'The connection to read repositories from (filtered by its group filter)'}
264
+ .options=${connectionOptions}
265
+ .selectedOption=${connectionOptions[0]}
266
+ ></dees-input-dropdown>
267
+ </div>
268
+ <div class="form-row">
269
+ <dees-input-dropdown
270
+ .label=${'Target Connection'}
271
+ .key=${'targetConnectionId'}
272
+ .description=${'The connection to push repositories to'}
273
+ .options=${connectionOptions}
274
+ .selectedOption=${connectionOptions[1] || connectionOptions[0]}
275
+ ></dees-input-dropdown>
276
+ </div>
277
+ <div class="form-row">
278
+ <dees-input-text .label=${'Target Group Offset'} .key=${'targetGroupOffset'} .description=${'Path prefix for target repos (e.g. "mirror/gitlab"). Leave empty for no prefix — repos land at their relative path.'}></dees-input-text>
279
+ </div>
280
+ <div class="form-row">
281
+ <dees-input-text .label=${'Interval (minutes)'} .key=${'intervalMinutes'} .value=${'5'} .description=${'How often to run this sync automatically'}></dees-input-text>
282
+ </div>
283
+ <div class="form-row">
284
+ <dees-input-checkbox .label=${'Enforce Deletion'} .key=${'enforceDelete'} .value=${false} .description=${'When enabled, repos on the target not present on the source will be moved to an obsolete group (private).'}></dees-input-checkbox>
285
+ </div>
286
+ <div class="form-row">
287
+ <dees-input-checkbox .label=${'Enforce Group Deletion'} .key=${'enforceGroupDelete'} .value=${false} .description=${'When enabled, groups/orgs on the target not present on the source will be moved to obsolete.'}></dees-input-checkbox>
288
+ </div>
289
+ <div class="form-row">
290
+ <dees-input-checkbox .label=${'Add Mirror Hint'} .key=${'addMirrorHint'} .value=${false} .description=${'When enabled, target descriptions get "(This is a mirror of ...)" appended.'}></dees-input-checkbox>
291
+ </div>
292
+ <div class="form-row">
293
+ <dees-input-checkbox .label=${'Group Avatars for Projects'} .key=${'useGroupAvatarsForProjects'} .value=${false} .description=${'When enabled, projects without their own avatar inherit the group avatar.'}></dees-input-checkbox>
294
+ </div>
295
+ `,
296
+ menuOptions: [
297
+ { name: 'Cancel', action: async (modal: any) => { modal.destroy(); } },
298
+ {
299
+ name: 'Create',
300
+ action: async (modal: any) => {
301
+ const inputs = modal.shadowRoot.querySelectorAll('dees-input-text, dees-input-dropdown, dees-input-checkbox');
302
+ const data: any = {};
303
+ for (const input of inputs) {
304
+ if (input.key === 'sourceConnectionId' || input.key === 'targetConnectionId') {
305
+ data[input.key] = input.selectedOption?.key || '';
306
+ } else if (input.key === 'enforceDelete' || input.key === 'enforceGroupDelete' || input.key === 'addMirrorHint' || input.key === 'useGroupAvatarsForProjects') {
307
+ data[input.key] = input.getValue();
308
+ } else {
309
+ data[input.key] = input.value || '';
310
+ }
311
+ }
312
+ await appstate.syncStatePart.dispatchAction(appstate.createSyncConfigAction, {
313
+ name: data.name,
314
+ sourceConnectionId: data.sourceConnectionId,
315
+ targetConnectionId: data.targetConnectionId,
316
+ targetGroupOffset: data.targetGroupOffset || undefined,
317
+ intervalMinutes: parseInt(data.intervalMinutes) || 5,
318
+ enforceDelete: !!data.enforceDelete,
319
+ enforceGroupDelete: !!data.enforceGroupDelete,
320
+ addMirrorHint: !!data.addMirrorHint,
321
+ useGroupAvatarsForProjects: !!data.useGroupAvatarsForProjects,
322
+ });
323
+ modal.destroy();
324
+ },
325
+ },
326
+ ],
327
+ });
328
+ }
329
+
330
+ private async editSyncConfig(item: any) {
331
+ await plugins.deesCatalog.DeesModal.createAndShow({
332
+ heading: `Edit Sync: ${item.name}`,
333
+ content: html`
334
+ <style>.form-row { margin-bottom: 16px; }</style>
335
+ <div class="form-row">
336
+ <dees-input-text .label=${'Name'} .key=${'name'} .value=${item.name} .description=${'A human-readable name for this sync configuration'}></dees-input-text>
337
+ </div>
338
+ <div class="form-row">
339
+ <dees-input-text .label=${'Target Group Offset'} .key=${'targetGroupOffset'} .value=${item.targetGroupOffset || ''} .description=${'Path prefix for target repos (e.g. "mirror/gitlab"). Leave empty for no prefix — repos land at their relative path.'}></dees-input-text>
340
+ </div>
341
+ <div class="form-row">
342
+ <dees-input-text .label=${'Interval (minutes)'} .key=${'intervalMinutes'} .value=${String(item.intervalMinutes)} .description=${'How often to run this sync automatically'}></dees-input-text>
343
+ </div>
344
+ <div class="form-row">
345
+ <dees-input-checkbox .label=${'Enforce Deletion'} .key=${'enforceDelete'} .value=${!!item.enforceDelete} .description=${'When enabled, repos on the target not present on the source will be moved to an obsolete group (private).'}></dees-input-checkbox>
346
+ </div>
347
+ <div class="form-row">
348
+ <dees-input-checkbox .label=${'Enforce Group Deletion'} .key=${'enforceGroupDelete'} .value=${!!item.enforceGroupDelete} .description=${'When enabled, groups/orgs on the target not present on the source will be moved to obsolete.'}></dees-input-checkbox>
349
+ </div>
350
+ <div class="form-row">
351
+ <dees-input-checkbox .label=${'Add Mirror Hint'} .key=${'addMirrorHint'} .value=${!!item.addMirrorHint} .description=${'When enabled, target descriptions get "(This is a mirror of ...)" appended.'}></dees-input-checkbox>
352
+ </div>
353
+ <div class="form-row">
354
+ <dees-input-checkbox .label=${'Group Avatars for Projects'} .key=${'useGroupAvatarsForProjects'} .value=${!!item.useGroupAvatarsForProjects} .description=${'When enabled, projects without their own avatar inherit the group avatar.'}></dees-input-checkbox>
355
+ </div>
356
+ `,
357
+ menuOptions: [
358
+ { name: 'Cancel', action: async (modal: any) => { modal.destroy(); } },
359
+ {
360
+ name: 'Save',
361
+ action: async (modal: any) => {
362
+ const inputs = modal.shadowRoot.querySelectorAll('dees-input-text, dees-input-checkbox');
363
+ const data: any = {};
364
+ for (const input of inputs) {
365
+ if (input.key === 'enforceDelete' || input.key === 'enforceGroupDelete' || input.key === 'addMirrorHint' || input.key === 'useGroupAvatarsForProjects') {
366
+ data[input.key] = input.getValue();
367
+ } else {
368
+ data[input.key] = input.value || '';
369
+ }
370
+ }
371
+ await appstate.syncStatePart.dispatchAction(appstate.updateSyncConfigAction, {
372
+ syncConfigId: item.id,
373
+ name: data.name,
374
+ targetGroupOffset: data.targetGroupOffset || undefined,
375
+ intervalMinutes: parseInt(data.intervalMinutes) || 5,
376
+ enforceDelete: !!data.enforceDelete,
377
+ enforceGroupDelete: !!data.enforceGroupDelete,
378
+ addMirrorHint: !!data.addMirrorHint,
379
+ useGroupAvatarsForProjects: !!data.useGroupAvatarsForProjects,
380
+ });
381
+ modal.destroy();
382
+ },
383
+ },
384
+ ],
385
+ });
386
+ }
387
+
388
+ private async previewSync(item: any) {
389
+ try {
390
+ const { mappings, deletions, groupDeletions } = await appstate.previewSync(item.id);
391
+
392
+ // Compute the full obsolete group path for display
393
+ const targetConn = this.connectionsState.connections.find((c: any) => c.id === item.targetConnectionId);
394
+ let obsoletePath: string;
395
+ if (targetConn?.providerType === 'gitea') {
396
+ const segments = item.targetGroupOffset ? item.targetGroupOffset.split('/') : [];
397
+ const orgName = segments[0] || targetConn?.groupFilter || 'default';
398
+ obsoletePath = `${orgName}-obsolete`;
399
+ } else {
400
+ obsoletePath = item.targetGroupOffset ? `${item.targetGroupOffset}/obsolete` : 'obsolete';
401
+ }
402
+
403
+ await plugins.deesCatalog.DeesModal.createAndShow({
404
+ heading: `Preview Sync: "${item.name}"`,
405
+ content: html`
406
+ <style>
407
+ .preview-list { color: #fff; max-height: 400px; overflow-y: auto; }
408
+ .preview-item { display: flex; align-items: center; gap: 12px; padding: 6px 0; border-bottom: 1px solid #333; font-size: 13px; }
409
+ .preview-source { color: #aaa; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
410
+ .preview-arrow { color: #666; flex-shrink: 0; }
411
+ .preview-target { color: #00ff88; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
412
+ .preview-count { color: #888; font-size: 12px; margin-bottom: 12px; }
413
+ .preview-delete { color: #ff4444; padding: 6px 0; border-bottom: 1px solid #333; font-size: 13px; display: flex; align-items: center; gap: 8px; }
414
+ .preview-delete-marker { flex-shrink: 0; }
415
+ .preview-delete-path { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
416
+ .preview-section { margin-top: 16px; }
417
+ .preview-section-header { color: #ff4444; font-size: 12px; font-weight: 600; margin-bottom: 8px; }
418
+ </style>
419
+ <div class="preview-count">${mappings.length} repositories will be synced</div>
420
+ <div class="preview-list">
421
+ ${mappings.map((m: any) => html`
422
+ <div class="preview-item">
423
+ <span class="preview-source">${m.sourceFullPath}</span>
424
+ <span class="preview-arrow">&rarr;</span>
425
+ <span class="preview-target">${m.targetFullPath}</span>
426
+ </div>
427
+ `)}
428
+ ${mappings.length === 0 ? html`<p style="color: #888;">No repositories found on source.</p>` : ''}
429
+ </div>
430
+ ${deletions.length > 0 ? html`
431
+ <div class="preview-section">
432
+ <div class="preview-section-header">${deletions.length} target repositor${deletions.length === 1 ? 'y' : 'ies'} will be moved to ${obsoletePath}</div>
433
+ <div class="preview-list">
434
+ ${deletions.map((d: string) => html`
435
+ <div class="preview-delete">
436
+ <span class="preview-delete-marker">→</span>
437
+ <span class="preview-delete-path">${d}</span>
438
+ </div>
439
+ `)}
440
+ </div>
441
+ </div>
442
+ ` : ''}
443
+ ${groupDeletions.length > 0 ? html`
444
+ <div class="preview-section">
445
+ <div class="preview-section-header">${groupDeletions.length} target group${groupDeletions.length === 1 ? '' : 's'} will be moved to ${obsoletePath}</div>
446
+ <div class="preview-list">
447
+ ${groupDeletions.map((g: string) => html`
448
+ <div class="preview-delete">
449
+ <span class="preview-delete-marker">→</span>
450
+ <span class="preview-delete-path">${g}</span>
451
+ </div>
452
+ `)}
453
+ </div>
454
+ </div>
455
+ ` : ''}
456
+ `,
457
+ menuOptions: [
458
+ { name: 'Close', action: async (modal: any) => { modal.destroy(); } },
459
+ ],
460
+ });
461
+ } catch (err: any) {
462
+ await plugins.deesCatalog.DeesModal.createAndShow({
463
+ heading: 'Preview Failed',
464
+ content: html`<p style="color: #ff4444;">${err.message || String(err)}</p>`,
465
+ menuOptions: [
466
+ { name: 'Close', action: async (modal: any) => { modal.destroy(); } },
467
+ ],
468
+ });
469
+ }
470
+ }
471
+
472
+ private async viewRepoStatuses(item: any) {
473
+ await appstate.syncStatePart.dispatchAction(appstate.fetchSyncRepoStatusesAction, {
474
+ syncConfigId: item.id,
475
+ });
476
+
477
+ const statuses = appstate.syncStatePart.getState().repoStatuses;
478
+ await plugins.deesCatalog.DeesModal.createAndShow({
479
+ heading: `Sync "${item.name}" - Repo Statuses`,
480
+ content: html`
481
+ <style>
482
+ .repo-list { color: #fff; max-height: 400px; overflow-y: auto; }
483
+ .repo-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #333; }
484
+ .repo-path { font-weight: 600; font-size: 13px; }
485
+ .repo-status { font-size: 12px; text-transform: uppercase; }
486
+ .repo-status.synced { color: #00ff88; }
487
+ .repo-status.error { color: #ff4444; }
488
+ .repo-status.pending { color: #ffaa00; }
489
+ .repo-error { font-size: 11px; color: #ff6666; margin-top: 4px; }
490
+ </style>
491
+ <div class="repo-list">
492
+ ${statuses.map((s: any) => html`
493
+ <div class="repo-item">
494
+ <div>
495
+ <div class="repo-path">${s.sourceFullPath}</div>
496
+ ${s.lastSyncError ? html`<div class="repo-error">${s.lastSyncError}</div>` : ''}
497
+ </div>
498
+ <div>
499
+ <span class="repo-status ${s.status}">${s.status}</span>
500
+ <div style="font-size: 11px; color: #888;">${s.lastSyncAt ? new Date(s.lastSyncAt).toLocaleString() : ''}</div>
501
+ </div>
502
+ </div>
503
+ `)}
504
+ ${statuses.length === 0 ? html`<p style="color: #888;">No repos synced yet.</p>` : ''}
505
+ </div>
506
+ `,
507
+ menuOptions: [
508
+ { name: 'Close', action: async (modal: any) => { modal.destroy(); } },
509
+ ],
510
+ });
511
+ }
512
+ }
@@ -0,0 +1,7 @@
1
+ import * as plugins from './plugins.js';
2
+ import { html } from '@design.estate/dees-element';
3
+ import './elements/index.js';
4
+
5
+ plugins.deesElement.render(html`
6
+ <gitops-dashboard></gitops-dashboard>
7
+ `, document.body);
@@ -0,0 +1,15 @@
1
+ // @design.estate scope
2
+ import * as deesElement from '@design.estate/dees-element';
3
+ import * as deesCatalog from '@design.estate/dees-catalog';
4
+
5
+ // @api.global scope
6
+ import * as typedsocket from '@api.global/typedsocket';
7
+
8
+ export {
9
+ deesElement,
10
+ deesCatalog,
11
+ typedsocket,
12
+ };
13
+
14
+ // domtools gives us TypedRequest, smartstate, smartrouter, and other utilities
15
+ export const domtools = deesElement.domtools;
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "types": [
7
+ "node"
8
+ ],
9
+ "esModuleInterop": true,
10
+ "verbatimModuleSyntax": true
11
+ },
12
+ "exclude": [
13
+ "dist_*/**/*.d.ts"
14
+ ]
15
+ }