underpost 3.1.2 → 3.1.3

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/src/cli/run.js CHANGED
@@ -87,6 +87,7 @@ const logger = loggerFactory(import.meta);
87
87
  * @property {boolean} logs - Whether to enable logs.
88
88
  * @property {boolean} dryRun - Whether to perform a dry run.
89
89
  * @property {boolean} createJobNow - Whether to create the job immediately.
90
+ * @property {number} fromNCommit - Number of commits back to use for message propagation (default: 1, last commit only).
90
91
  * @property {string|Array<{ip: string, hostnames: string[]}>} hostAliases - Adds entries to the Pod /etc/hosts via Kubernetes hostAliases.
91
92
  * As a string (CLI): semicolon-separated entries of "ip=hostname1,hostname2" (e.g., "127.0.0.1=foo.local,bar.local;10.1.2.3=foo.remote").
92
93
  * As an array (programmatic): objects with `ip` and `hostnames` fields (e.g., [{ ip: "127.0.0.1", hostnames: ["foo.local"] }]).
@@ -151,6 +152,7 @@ const DEFAULT_OPTION = {
151
152
  logs: false,
152
153
  dryRun: false,
153
154
  createJobNow: false,
155
+ fromNCommit: 0,
154
156
  hostAliases: '',
155
157
  };
156
158
 
@@ -354,8 +356,10 @@ class UnderpostRun {
354
356
  },
355
357
  /**
356
358
  * @method template-deploy
357
- * @description Pushes `engine-private`, dispatches CI workflow to build `pwa-microservices-template`, and optionally dispatches CD sync workflow.
358
- * @param {string} path - The input value, identifier, or path for the operation.
359
+ * @description Pushes `engine-private`, dispatches CI workflow to build `pwa-microservices-template`,
360
+ * and optionally triggers engine-<conf-id> CI with sync/init which in turn dispatches the CD workflow
361
+ * after the build chain completes (template → ghpkg → engine-<conf-id> → CD).
362
+ * @param {string} path - The deployment path identifier (e.g., 'sync-engine-core', 'init-engine-core', or empty for build-only).
359
363
  * @param {Object} options - The default underpost runner options for customizing workflow
360
364
  * @memberof UnderpostRun
361
365
  */
@@ -368,7 +372,14 @@ class UnderpostRun {
368
372
  return;
369
373
  }
370
374
  shellExec(`${baseCommand} run pull`);
371
- const message = shellExec(`node bin cmt --changelog --changelog-no-hash`, { silent: true, stdout: true }).trim();
375
+
376
+ // Capture last N commit messages for propagation (default: last 1 commit)
377
+ const fromN = options.fromNCommit && parseInt(options.fromNCommit) > 0 ? parseInt(options.fromNCommit) : 1;
378
+ const message = shellExec(`node bin cmt --changelog ${fromN} --changelog-no-hash`, {
379
+ silent: true,
380
+ stdout: true,
381
+ }).trim();
382
+
372
383
  shellExec(
373
384
  `${baseCommand} push ./engine-private ${options.force ? '-f ' : ''}${
374
385
  process.env.GITHUB_USERNAME
@@ -376,52 +387,40 @@ class UnderpostRun {
376
387
  );
377
388
  shellCd('/home/dd/engine');
378
389
 
379
- // Store deploy boundary hash for changelog before dispatch
380
- const deployBoundaryHash = shellExec('git rev-parse HEAD', {
381
- stdout: true,
382
- silent: true,
383
- disableLog: true,
384
- }).trim();
385
-
386
- function replaceNthNewline(str, n, replacement = ' ') {
387
- let count = 0;
388
- return str.replace(/\r\n?|\n/g, (match) => {
389
- count++;
390
- return count === n ? replacement : match;
391
- });
392
- }
393
- const sanitizedMessage = message
394
- ? replaceNthNewline(message.replaceAll('"', '').replaceAll('`', '').replaceAll('#', '').replaceAll('- ', ''), 2)
395
- .replace(/\r\n?|\n/g, ' ')
396
- .trim()
397
- : '';
390
+ const sanitizedMessage = Underpost.repo.sanitizeChangelogMessage(message);
398
391
 
399
392
  // Push engine repo so workflow YAML changes reach GitHub
400
393
  shellExec(`git reset`);
401
394
  shellExec(`${baseCommand} push . ${options.force ? '-f ' : ''}${process.env.GITHUB_USERNAME}/engine`);
402
395
 
403
- // Dispatch CI workflow instead of empty commit + push
396
+ // Determine deploy conf and type from path (sync-engine-core, init-engine-core, etc.)
397
+ let deployConfId = '';
398
+ let deployType = '';
399
+ if (path.startsWith('sync-')) {
400
+ deployConfId = path.replace(/^sync-/, '');
401
+ deployType = 'sync-and-deploy';
402
+ } else if (path.startsWith('init-')) {
403
+ deployConfId = path.replace(/^init-/, '');
404
+ deployType = 'init';
405
+ }
406
+
407
+ // Dispatch npmpkg CI workflow — this builds pwa-microservices-template first.
408
+ // If deployConfId is set, npmpkg.ci.yml will dispatch the engine-<conf-id> CI
409
+ // with sync=true after template build completes. The engine CI then dispatches
410
+ // the CD workflow after the engine repo build finishes — ensuring correct sequence:
411
+ // npmpkg.ci → engine-<id>.ci → engine-<id>.cd
404
412
  const repo = `${process.env.GITHUB_USERNAME}/engine`;
413
+ const inputs = {};
414
+ if (sanitizedMessage) inputs.message = sanitizedMessage;
415
+ if (deployConfId) inputs.deploy_conf_id = deployConfId;
416
+ if (deployType) inputs.deploy_type = deployType;
417
+
405
418
  Underpost.repo.dispatchWorkflow({
406
419
  repo,
407
420
  workflowFile: 'npmpkg.ci.yml',
408
421
  ref: 'master',
409
- inputs: sanitizedMessage ? { message: sanitizedMessage } : {},
422
+ inputs,
410
423
  });
411
-
412
- // Dispatch CD sync-and-deploy if path starts with 'sync'
413
- if (path.startsWith('sync')) {
414
- const confId = path.replace(/^sync-/, '');
415
- Underpost.repo.dispatchWorkflow({
416
- repo,
417
- workflowFile: `${confId}.cd.yml`,
418
- ref: 'master',
419
- inputs: { job: 'sync-and-deploy' },
420
- });
421
- }
422
-
423
- // Store deploy boundary for changelog
424
- shellExec(`${baseCommand} config set LAST_CI_DEPLOY_HASH ${deployBoundaryHash}`);
425
424
  },
426
425
 
427
426
  /**
@@ -1573,13 +1572,73 @@ EOF
1573
1572
  },
1574
1573
 
1575
1574
  /**
1576
- * @method ptls
1575
+ * @method pid-info
1576
+ * @description Displays detailed information about a process by PID, including service details, command line, executable path, working directory, environment variables, and parent process tree.
1577
+ * @param {string} path - The PID of the process to inspect.
1578
+ * @param {Object} options - The default underpost runner options for customizing workflow
1579
+ * @memberof UnderpostRun
1580
+ */
1581
+ 'pid-info': (path, options = DEFAULT_OPTION) => {
1582
+ const pid = path;
1583
+ if (!pid) {
1584
+ logger.error('PID is required. Usage: underpost run pid-info <pid>');
1585
+ return;
1586
+ }
1587
+
1588
+ // Services
1589
+ logger.info('Process info');
1590
+ shellExec(`sudo ps -p ${pid} -o pid,ppid,user,stime,etime,cmd`);
1591
+ logger.info('Command line');
1592
+ shellExec(`sudo cat /proc/${pid}/cmdline | tr '\\0' ' ' ; echo`);
1593
+ logger.info('Executable path');
1594
+ shellExec(`sudo readlink -f /proc/${pid}/exe`);
1595
+ logger.info('Working directory');
1596
+ shellExec(`sudo readlink -f /proc/${pid}/cwd`);
1597
+ logger.info('Environment variables (first 200)');
1598
+ shellExec(`sudo tr '\\0' '\\n' </proc/${pid}/environ | head -200`);
1599
+
1600
+ // Parent
1601
+ logger.info('Parent process');
1602
+ const parentInfo = shellExec(`sudo ps -o pid,ppid,user,cmd -p ${pid}`, { stdout: true, silent: true });
1603
+ console.log(parentInfo);
1604
+ const ppidMatch = parentInfo.split('\n').find((l) => l.trim().startsWith(pid));
1605
+ if (ppidMatch) {
1606
+ const ppid = ppidMatch.trim().split(/\s+/)[1];
1607
+ logger.info(`Parent PID: ${ppid}`);
1608
+ shellExec(`ps -fp ${ppid}`);
1609
+ }
1610
+ logger.info('Process tree');
1611
+ shellExec(`pstree -s ${pid}`);
1612
+ },
1613
+
1614
+ /**
1615
+ * @method background
1616
+ * @description Runs a custom command in the background using nohup, logging output to `/var/log/<id>.log` and saving the PID to `/var/run/<id>.pid`.
1617
+ * @param {string} path - The command to run in the background (e.g. 'npm run prod:container dd-cyberia-r3').
1618
+ * @param {Object} options - The default underpost runner options for customizing workflow
1619
+ * @memberof UnderpostRun
1620
+ */
1621
+ background: (path, options = DEFAULT_OPTION) => {
1622
+ if (!path) {
1623
+ logger.error('Command is required. Usage: underpost run background <command>');
1624
+ return;
1625
+ }
1626
+ const id = path.split(/\s+/).pop();
1627
+ const logFile = `/var/log/${id}.log`;
1628
+ const pidFile = `/var/run/${id}.pid`;
1629
+ logger.info(`Starting background process`, { id, logFile, pidFile });
1630
+ shellExec(`nohup ${path} > ${logFile} 2>&1 & pid=$!; echo $pid > ${pidFile}; disown`);
1631
+ logger.info(`Background process started for '${id}'`);
1632
+ },
1633
+
1634
+ /**
1635
+ * @method ports
1577
1636
  * @description Set on ~/.bashrc alias: ports <port> Command to list listening ports that match the given keyword.
1578
1637
  * @param {string} path - The input value, identifier, or path for the operation (used as a keyword to filter listening ports).
1579
1638
  * @param {Object} options - The default underpost runner options for customizing workflow
1580
1639
  * @memberof UnderpostRun
1581
1640
  */
1582
- ptls: async (path = '', options = DEFAULT_OPTION) => {
1641
+ ports: async (path = '', options = DEFAULT_OPTION) => {
1583
1642
  shellExec(`chmod +x ${options.underpostRoot}/scripts/ports-ls.sh`);
1584
1643
  shellExec(`${options.underpostRoot}/scripts/ports-ls.sh`);
1585
1644
  },
@@ -49,7 +49,7 @@ const e404 = async () => {
49
49
  <br />
50
50
  <br />${Translate.Render('page-not-found')} <br />
51
51
  <br />
52
- <a href="${location.origin}">${Translate.Render('back')}</a>
52
+ <a target="_top" href="${location.origin}">${Translate.Render('back')}</a>
53
53
  </div>`;
54
54
  };
55
55
 
@@ -68,7 +68,7 @@ const e500 = async () => {
68
68
  <br />
69
69
  <br />${Translate.Render('page-broken')} <br />
70
70
  <br />
71
- <a href="${location.origin}">${Translate.Render('back')}</a>
71
+ <a target="_top" href="${location.origin}">${Translate.Render('back')}</a>
72
72
  </div>`;
73
73
  };
74
74
 
@@ -24,11 +24,18 @@ const Docs = {
24
24
  html: async () => {
25
25
  if (docData.renderHtml) return await docData.renderHtml();
26
26
  return html`
27
+ <style>
28
+ .iframe-${ModalId} {
29
+ width: 100%;
30
+ border: none;
31
+ background: white;
32
+ display: block;
33
+ }
34
+ </style>
27
35
  <iframe
28
36
  class="in iframe-${ModalId}"
29
- style="width: 100%; border: none; background: white; display: block"
30
37
  src="${docData.url()}"
31
- sandbox="allow-same-origin allow-scripts allow-popups allow-forms allow-popups-to-escape-sandbox"
38
+ sandbox="allow-same-origin allow-scripts allow-popups allow-forms allow-popups-to-escape-sandbox allow-top-navigation"
32
39
  >
33
40
  </iframe>
34
41
  `;
@@ -30,10 +30,9 @@ const RichText = {
30
30
  return html` <style>
31
31
  .md-container {
32
32
  background: white;
33
- color: black;
34
33
  }
35
34
  .md-container button {
36
- color: black !important;
35
+ color: black;
37
36
  }
38
37
  </style>
39
38
  <div class="in md-container"><textarea class="${id}"></textarea></div>`;
@@ -47,9 +47,12 @@ const main = () => {
47
47
  a {
48
48
  color: black;
49
49
  }
50
+ .main-body-ssr-404 {
51
+ top: 45%;
52
+ }
50
53
  </style>
51
54
 
52
- <div class="abs center" style="top: 45%">
55
+ <div class="abs center main-body-ssr-404">
53
56
  ${icon}
54
57
  <br />
55
58
  <br />
@@ -57,17 +60,18 @@ const main = () => {
57
60
  <br />
58
61
  <br />${Translate.Render('page-not-found')} <br />
59
62
  <br />
60
- <a href="${location.origin}">${Translate.Render('back')}</a>
63
+ <a target="_top" href="${location.origin}">${Translate.Render('back')}</a>
61
64
  </div>`,
62
65
  );
63
66
  };
64
67
 
65
- SrrComponent = () => html`<script>
66
- {
67
- const s = ${s};
68
- const append = ${append};
69
- const getLang = ${getLang};
70
- const main = ${main};
71
- window.onload = main;
72
- }
73
- </script>`;
68
+ SrrComponent = () =>
69
+ html`<script>
70
+ {
71
+ const s = ${s};
72
+ const append = ${append};
73
+ const getLang = ${getLang};
74
+ const main = ${main};
75
+ window.onload = main;
76
+ }
77
+ </script>`;
@@ -46,9 +46,12 @@ const main = () => {
46
46
  a {
47
47
  color: black;
48
48
  }
49
+ .main-body-ssr-500 {
50
+ top: 45%;
51
+ }
49
52
  </style>
50
53
 
51
- <div class="abs center" style="top: 45%">
54
+ <div class="abs center main-body-ssr-500">
52
55
  ${icon}
53
56
  <br />
54
57
  <br />
@@ -56,17 +59,18 @@ const main = () => {
56
59
  <br />
57
60
  <br />${Translate.Render('page-broken')} <br />
58
61
  <br />
59
- <a href="${location.origin}">${Translate.Render('back')}</a>
62
+ <a target="_top" href="${location.origin}">${Translate.Render('back')}</a>
60
63
  </div>`,
61
64
  );
62
65
  };
63
66
 
64
- SrrComponent = () => html`<script>
65
- {
66
- const s = ${s};
67
- const append = ${append};
68
- const getLang = ${getLang};
69
- const main = ${main};
70
- window.onload = main;
71
- }
72
- </script>`;
67
+ SrrComponent = () =>
68
+ html`<script>
69
+ {
70
+ const s = ${s};
71
+ const append = ${append};
72
+ const getLang = ${getLang};
73
+ const main = ${main};
74
+ window.onload = main;
75
+ }
76
+ </script>`;
@@ -0,0 +1,285 @@
1
+ const swaggerDarkCss = css`
2
+ /* ── Toggle button ── */
3
+ .swagger-ui .topbar-wrapper {
4
+ display: flex;
5
+ align-items: center;
6
+ }
7
+ .swagger-theme-toggle-btn {
8
+ background: rgba(255, 255, 255, 0.12);
9
+ border: 1px solid rgba(255, 255, 255, 0.3);
10
+ color: #fff;
11
+ padding: 5px 14px;
12
+ border-radius: 4px;
13
+ cursor: pointer;
14
+ font-size: 13px;
15
+ margin-left: auto;
16
+ margin-right: 16px;
17
+ white-space: nowrap;
18
+ transition: background 0.2s ease;
19
+ }
20
+ .swagger-theme-toggle-btn:hover {
21
+ background: rgba(255, 255, 255, 0.25);
22
+ }
23
+
24
+ /* ── Dark mode overrides — black/gray gradients ── */
25
+ body.swagger-dark {
26
+ background: #0f0f0f;
27
+ }
28
+ body.swagger-dark .swagger-ui {
29
+ background: #0f0f0f;
30
+ }
31
+ body.swagger-dark .swagger-ui .wrapper {
32
+ background: #0f0f0f;
33
+ }
34
+
35
+ /* Info block */
36
+ body.swagger-dark .swagger-ui .info .title,
37
+ body.swagger-dark .swagger-ui .info h1,
38
+ body.swagger-dark .swagger-ui .info h2,
39
+ body.swagger-dark .swagger-ui .info h3,
40
+ body.swagger-dark .swagger-ui .info p,
41
+ body.swagger-dark .swagger-ui .info li,
42
+ body.swagger-dark .swagger-ui .info a {
43
+ color: #e8e8e8;
44
+ }
45
+
46
+ /* Scheme / server selector bar */
47
+ body.swagger-dark .swagger-ui .scheme-container {
48
+ background: linear-gradient(180deg, #1a1a1a 0%, #222222 100%);
49
+ box-shadow: 0 1px 0 #383838;
50
+ }
51
+ body.swagger-dark .swagger-ui select {
52
+ background: #2a2a2a;
53
+ color: #e8e8e8;
54
+ border-color: #383838;
55
+ }
56
+
57
+ /* Operation tags */
58
+ body.swagger-dark .swagger-ui .opblock-tag {
59
+ color: #e8e8e8;
60
+ border-color: #383838;
61
+ }
62
+ body.swagger-dark .swagger-ui .opblock-tag small {
63
+ color: #a8a8a8;
64
+ }
65
+ body.swagger-dark .swagger-ui .opblock-tag:hover {
66
+ background: rgba(255, 255, 255, 0.04);
67
+ }
68
+
69
+ /* Operation blocks */
70
+ body.swagger-dark .swagger-ui .opblock {
71
+ background: linear-gradient(180deg, #161616 0%, #1e1e1e 100%);
72
+ border-color: #383838;
73
+ }
74
+ body.swagger-dark .swagger-ui .opblock .opblock-summary {
75
+ border-color: #383838;
76
+ }
77
+ body.swagger-dark .swagger-ui .opblock .opblock-summary-description,
78
+ body.swagger-dark .swagger-ui .opblock .opblock-summary-operation-id {
79
+ color: #a8a8a8;
80
+ }
81
+ body.swagger-dark .swagger-ui .opblock-body {
82
+ background: #0f0f0f;
83
+ }
84
+ body.swagger-dark .swagger-ui .opblock-description-wrapper p,
85
+ body.swagger-dark .swagger-ui .opblock-external-docs-wrapper p {
86
+ color: #a8a8a8;
87
+ }
88
+ body.swagger-dark .swagger-ui .opblock-section-header {
89
+ background: linear-gradient(180deg, #1a1a1a 0%, #222222 100%);
90
+ border-color: #383838;
91
+ }
92
+ body.swagger-dark .swagger-ui .opblock-section-header h4 {
93
+ color: #e8e8e8;
94
+ }
95
+
96
+ /* Models section */
97
+ body.swagger-dark .swagger-ui section.models {
98
+ background: linear-gradient(180deg, #161616 0%, #1e1e1e 100%);
99
+ border-color: #383838;
100
+ }
101
+ body.swagger-dark .swagger-ui section.models h4,
102
+ body.swagger-dark .swagger-ui section.models h5 {
103
+ color: #e8e8e8;
104
+ border-color: #383838;
105
+ }
106
+ body.swagger-dark .swagger-ui .model-container {
107
+ background: #0f0f0f;
108
+ border-color: #2a2a2a;
109
+ }
110
+ body.swagger-dark .swagger-ui .model-box {
111
+ background: #1a1a1a;
112
+ }
113
+ body.swagger-dark .swagger-ui .model,
114
+ body.swagger-dark .swagger-ui .model-title,
115
+ body.swagger-dark .swagger-ui .model span,
116
+ body.swagger-dark .swagger-ui .model .property {
117
+ color: #e8e8e8;
118
+ }
119
+ body.swagger-dark .swagger-ui .model .property.primitive {
120
+ color: #a8a8a8;
121
+ }
122
+
123
+ /* Tables */
124
+ body.swagger-dark .swagger-ui table thead tr th {
125
+ background: linear-gradient(180deg, #222222 0%, #2a2a2a 100%);
126
+ color: #e8e8e8;
127
+ border-color: #383838;
128
+ }
129
+ body.swagger-dark .swagger-ui table tbody tr td {
130
+ color: #a8a8a8;
131
+ border-color: #2a2a2a;
132
+ }
133
+
134
+ /* Parameters */
135
+ body.swagger-dark .swagger-ui .parameter__name,
136
+ body.swagger-dark .swagger-ui .parameter__type,
137
+ body.swagger-dark .swagger-ui .parameter__in,
138
+ body.swagger-dark .swagger-ui .parameter__extension,
139
+ body.swagger-dark .swagger-ui .parameter__empty_value_toggle {
140
+ color: #a8a8a8;
141
+ }
142
+ body.swagger-dark .swagger-ui .parameter__name.required:after {
143
+ color: #ff6b6b;
144
+ }
145
+
146
+ /* Inputs & textareas */
147
+ body.swagger-dark .swagger-ui input[type='text'],
148
+ body.swagger-dark .swagger-ui input[type='email'],
149
+ body.swagger-dark .swagger-ui input[type='password'],
150
+ body.swagger-dark .swagger-ui input[type='search'],
151
+ body.swagger-dark .swagger-ui input[type='number'],
152
+ body.swagger-dark .swagger-ui textarea {
153
+ background: #2a2a2a;
154
+ color: #e8e8e8;
155
+ border-color: #383838;
156
+ }
157
+
158
+ /* Buttons */
159
+ body.swagger-dark .swagger-ui .btn {
160
+ color: #e8e8e8;
161
+ border-color: #383838;
162
+ background: #222222;
163
+ }
164
+ body.swagger-dark .swagger-ui .btn:hover {
165
+ background: #2a2a2a;
166
+ }
167
+ body.swagger-dark .swagger-ui .btn.authorize {
168
+ color: #49cc90;
169
+ border-color: #49cc90;
170
+ background: transparent;
171
+ }
172
+ body.swagger-dark .swagger-ui .btn.execute {
173
+ background: linear-gradient(180deg, #333333 0%, #262626 100%);
174
+ border-color: #484848;
175
+ color: #e8e8e8;
176
+ }
177
+ body.swagger-dark .swagger-ui .btn.cancel {
178
+ color: #ff6b6b;
179
+ border-color: #ff6b6b;
180
+ }
181
+
182
+ /* Responses */
183
+ body.swagger-dark .swagger-ui .responses-inner h4,
184
+ body.swagger-dark .swagger-ui .responses-inner h5 {
185
+ color: #a8a8a8;
186
+ }
187
+ body.swagger-dark .swagger-ui .response-col_status {
188
+ color: #e8e8e8;
189
+ }
190
+ body.swagger-dark .swagger-ui .response-col_description__inner p {
191
+ color: #a8a8a8;
192
+ }
193
+ body.swagger-dark .swagger-ui .response {
194
+ border-color: #2a2a2a;
195
+ }
196
+
197
+ /* Code / highlight */
198
+ body.swagger-dark .swagger-ui .highlight-code,
199
+ body.swagger-dark .swagger-ui .microlight {
200
+ background: #1a1a1a;
201
+ border: 1px solid #2a2a2a;
202
+ }
203
+
204
+ /* Markdown */
205
+ body.swagger-dark .swagger-ui .markdown p,
206
+ body.swagger-dark .swagger-ui .markdown li,
207
+ body.swagger-dark .swagger-ui .markdown a {
208
+ color: #a8a8a8;
209
+ }
210
+
211
+ /* Tabs */
212
+ body.swagger-dark .swagger-ui .tab li {
213
+ color: #a8a8a8;
214
+ }
215
+ body.swagger-dark .swagger-ui .tab li.active {
216
+ color: #e8e8e8;
217
+ }
218
+
219
+ /* Auth / modal dialog */
220
+ body.swagger-dark .swagger-ui .dialog-ux .modal-ux {
221
+ background: linear-gradient(180deg, #1a1a1a 0%, #222222 100%);
222
+ border: 1px solid #383838;
223
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.7);
224
+ }
225
+ body.swagger-dark .swagger-ui .dialog-ux .modal-ux-header {
226
+ border-bottom: 1px solid #383838;
227
+ background: linear-gradient(180deg, #222222 0%, #1a1a1a 100%);
228
+ }
229
+ body.swagger-dark .swagger-ui .dialog-ux .modal-ux-header h3 {
230
+ color: #e8e8e8;
231
+ }
232
+ body.swagger-dark .swagger-ui .dialog-ux .modal-ux-content p,
233
+ body.swagger-dark .swagger-ui .dialog-ux .modal-ux-content label,
234
+ body.swagger-dark .swagger-ui .dialog-ux .modal-ux-content h4 {
235
+ color: #e8e8e8;
236
+ }
237
+ body.swagger-dark .swagger-ui .auth-container .errors {
238
+ color: #ff6b6b;
239
+ }
240
+
241
+ /* Info / description */
242
+ body.swagger-dark .swagger-ui .info .base-url,
243
+ body.swagger-dark .swagger-ui .servers-title,
244
+ body.swagger-dark .swagger-ui .servers > label {
245
+ color: #a8a8a8;
246
+ }
247
+
248
+ /* Expand/collapse arrows */
249
+ body.swagger-dark .swagger-ui svg.arrow path {
250
+ fill: #a8a8a8;
251
+ }
252
+ `;
253
+
254
+ const swaggerDarkJs = `(function () {
255
+ function injectThemeToggle() {
256
+ var topbarWrapper = document.querySelector('.swagger-ui .topbar-wrapper');
257
+ if (!topbarWrapper || document.getElementById('swagger-theme-toggle')) return;
258
+
259
+ var savedTheme = localStorage.getItem('swagger-theme') || 'light';
260
+ if (savedTheme === 'dark') document.body.classList.add('swagger-dark');
261
+
262
+ var btn = document.createElement('button');
263
+ btn.id = 'swagger-theme-toggle';
264
+ btn.className = 'swagger-theme-toggle-btn';
265
+ btn.setAttribute('title', 'Toggle dark / light mode');
266
+ btn.textContent = savedTheme === 'dark' ? '\u2600\uFE0F Light Mode' : '\uD83C\uDF19 Dark Mode';
267
+
268
+ btn.addEventListener('click', function () {
269
+ var isDark = document.body.classList.toggle('swagger-dark');
270
+ localStorage.setItem('swagger-theme', isDark ? 'dark' : 'light');
271
+ btn.textContent = isDark ? '\u2600\uFE0F Light Mode' : '\uD83C\uDF19 Dark Mode';
272
+ });
273
+
274
+ topbarWrapper.appendChild(btn);
275
+ }
276
+
277
+ var poll = setInterval(function () {
278
+ if (document.querySelector('.swagger-ui .topbar-wrapper')) {
279
+ injectThemeToggle();
280
+ clearInterval(poll);
281
+ }
282
+ }, 100);
283
+ })();`;
284
+
285
+ SrrComponent = () => ({ css: swaggerDarkCss, js: swaggerDarkJs });
@@ -51,17 +51,18 @@ const main = () => {
51
51
  <br />
52
52
  <br />${Translate.Render('no-internet-connection')} <br />
53
53
  <br />
54
- <a href="${location.origin}">${Translate.Render('back')}</a>
54
+ <a target="_top" href="${location.origin}">${Translate.Render('back')}</a>
55
55
  </div>`,
56
56
  );
57
57
  };
58
58
 
59
- SrrComponent = () => html`<script>
60
- {
61
- const s = ${s};
62
- const append = ${append};
63
- const getLang = ${getLang};
64
- const main = ${main};
65
- window.onload = main;
66
- }
67
- </script>`;
59
+ SrrComponent = () =>
60
+ html`<script>
61
+ {
62
+ const s = ${s};
63
+ const append = ${append};
64
+ const getLang = ${getLang};
65
+ const main = ${main};
66
+ window.onload = main;
67
+ }
68
+ </script>`;
@@ -182,17 +182,18 @@ const main = () => {
182
182
  <span class="bold">Test Page</span>
183
183
  <br />
184
184
  <br />
185
- <a href="${location.origin}">${Translate.Render('back')}</a>
185
+ <a target="_top" href="${location.origin}">${Translate.Render('back')}</a>
186
186
  </div>`,
187
187
  );
188
188
  };
189
189
 
190
- SrrComponent = () => html`<script>
191
- {
192
- const s = ${s};
193
- const append = ${append};
194
- const getLang = ${getLang};
195
- const main = ${main};
196
- window.onload = main;
197
- }
198
- </script>`;
190
+ SrrComponent = () =>
191
+ html`<script>
192
+ {
193
+ const s = ${s};
194
+ const append = ${append};
195
+ const getLang = ${getLang};
196
+ const main = ${main};
197
+ window.onload = main;
198
+ }
199
+ </script>`;