underpost 2.8.846 → 2.8.848

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 (34) hide show
  1. package/README.md +15 -2
  2. package/bin/build.js +7 -1
  3. package/bin/deploy.js +7 -1
  4. package/cli.md +17 -2
  5. package/conf.js +1 -1
  6. package/docker-compose.yml +1 -1
  7. package/manifests/deployment/dd-template-development/deployment.yaml +2 -2
  8. package/package.json +1 -1
  9. package/src/api/user/user.service.js +1 -4
  10. package/src/cli/cluster.js +6 -0
  11. package/src/cli/deploy.js +2 -1
  12. package/src/cli/index.js +7 -0
  13. package/src/cli/run.js +9 -1
  14. package/src/cli/ssh.js +32 -0
  15. package/src/client/Default.index.js +6 -2
  16. package/src/client/components/core/Account.js +1 -1
  17. package/src/client/components/core/Css.js +190 -14
  18. package/src/client/components/core/CssCore.js +3 -4
  19. package/src/client/components/core/Docs.js +4 -4
  20. package/src/client/components/core/LoadingAnimation.js +8 -15
  21. package/src/client/components/core/Modal.js +100 -7
  22. package/src/client/components/core/PanelForm.js +2 -3
  23. package/src/client/components/core/Router.js +22 -23
  24. package/src/client/components/core/SocketIo.js +3 -3
  25. package/src/client/components/core/VanillaJs.js +0 -3
  26. package/src/client/components/core/Worker.js +3 -1
  27. package/src/client/components/default/CssDefault.js +17 -3
  28. package/src/client/components/default/MenuDefault.js +241 -48
  29. package/src/client/components/default/RoutesDefault.js +6 -11
  30. package/src/client/public/default/assets/background/dark.jpg +0 -0
  31. package/src/client/public/default/assets/background/dark.svg +557 -0
  32. package/src/client/public/default/assets/logo/underpost.gif +0 -0
  33. package/src/index.js +9 -1
  34. package/src/server/client-build.js +1 -8
package/README.md CHANGED
@@ -35,6 +35,12 @@ template
35
35
 
36
36
 
37
37
 
38
+
39
+
40
+
41
+
42
+
43
+
38
44
 
39
45
 
40
46
 
@@ -44,7 +50,7 @@ template
44
50
  <!-- badges -->
45
51
 
46
52
 
47
- [![Node.js CI](https://github.com/underpostnet/engine/actions/workflows/docker-image.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [![Test](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/coverall.yml) [![Downloads](https://img.shields.io/npm/dm/underpost.svg)](https://www.npmjs.com/package/underpost) [![Socket Badge](https://socket.dev/api/badge/npm/package/underpost/2.8.846)](https://socket.dev/npm/package/underpost/overview/2.8.846) [![Coverage Status](https://coveralls.io/repos/github/underpostnet/engine/badge.svg?branch=master)](https://coveralls.io/github/underpostnet/engine?branch=master) [![Version](https://img.shields.io/npm/v/underpost.svg)](https://www.npmjs.org/package/underpost) [![License](https://img.shields.io/npm/l/underpost.svg)](https://www.npmjs.com/package/underpost)
53
+ [![Node.js CI](https://github.com/underpostnet/engine/actions/workflows/docker-image.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [![Test](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/coverall.yml) [![Downloads](https://img.shields.io/npm/dm/underpost.svg)](https://www.npmjs.com/package/underpost) [![Socket Badge](https://socket.dev/api/badge/npm/package/underpost/2.8.848)](https://socket.dev/npm/package/underpost/overview/2.8.848) [![Coverage Status](https://coveralls.io/repos/github/underpostnet/engine/badge.svg?branch=master)](https://coveralls.io/github/underpostnet/engine?branch=master) [![Version](https://img.shields.io/npm/v/underpost.svg)](https://www.npmjs.org/package/underpost) [![License](https://img.shields.io/npm/l/underpost.svg)](https://www.npmjs.com/package/underpost)
48
54
 
49
55
 
50
56
  <!-- end-badges -->
@@ -62,6 +68,12 @@ template
62
68
 
63
69
 
64
70
 
71
+
72
+
73
+
74
+
75
+
76
+
65
77
 
66
78
 
67
79
 
@@ -112,7 +124,7 @@ Run dev client server
112
124
  npm run dev
113
125
  ```
114
126
  <!-- -->
115
- ## underpost ci/cd cli v2.8.846
127
+ ## underpost ci/cd cli v2.8.848
116
128
 
117
129
  ### Usage: `underpost [options] [command]`
118
130
  ```
@@ -142,6 +154,7 @@ Commands:
142
154
  fs [options] [path] Manages file storage, defaulting to file upload operations.
143
155
  test [options] [deploy-list] Manages and runs tests, defaulting to the current Underpost default test suite.
144
156
  monitor [options] <deploy-id> [env] Manages health server monitoring for specified deployments.
157
+ ssh [options] Import and start ssh server and client based on current default deployment ID.
145
158
  run [options] <runner-id> [path] Runs a script from the specified path.
146
159
  lxd [options] Manages LXD containers and virtual machines.
147
160
  baremetal [options] [workflow-id] [hostname] [ip-address] Manages baremetal server operations, including installation, database setup, commissioning, and user management.
package/bin/build.js CHANGED
@@ -53,7 +53,9 @@ if (process.argv.includes('conf')) {
53
53
  fs.removeSync(toPath);
54
54
  fs.mkdirSync(toPath, { recursive: true });
55
55
  fs.copySync(`./engine-private/conf/${_confName}`, toPath);
56
- if (fs.existsSync(`./engine-private/replica`)) {
56
+ if (process.argv.includes('remove-replica') && fs.existsSync(`../${privateRepoName}/replica`)) {
57
+ fs.removeSync(`../${privateRepoName}/replica`);
58
+ } else if (fs.existsSync(`./engine-private/replica`)) {
57
59
  const replicas = await fs.readdir(`./engine-private/replica`);
58
60
  for (const replica of replicas)
59
61
  if (replica.match(_confName))
@@ -187,4 +189,8 @@ const { DefaultConf } = await import(`../conf.${confName}.js`);
187
189
 
188
190
  fs.copyFileSync(`./.github/workflows/${repoName}.ci.yml`, `${basePath}/.github/workflows/${repoName}.ci.yml`);
189
191
  fs.copyFileSync(`./.github/workflows/${repoName}.cd.yml`, `${basePath}/.github/workflows/${repoName}.cd.yml`);
192
+
193
+ if (fs.existsSync(`./src/ws/${confName.split('-')[1]}`)) {
194
+ fs.copySync(`./src/ws/${confName.split('-')[1]}`, `${basePath}/src/ws/${confName.split('-')[1]}`);
195
+ }
190
196
  }
package/bin/deploy.js CHANGED
@@ -835,6 +835,12 @@ ${shellExec(`git log | grep Author: | sort -u`, { stdout: true }).split(`\n`).jo
835
835
  }
836
836
 
837
837
  case 'ssh': {
838
+ // only import + start
839
+ // node bin/deploy ssh root@<host> <password> import
840
+
841
+ // generate + import + start
842
+ // node bin/deploy ssh root@<host> <password>
843
+
838
844
  const host = process.argv[3] ?? `root@${await ip.public.ipv4()}`;
839
845
  const domain = host.split('@')[1];
840
846
  const user = 'root'; // host.split('@')[0];
@@ -907,7 +913,7 @@ EOF`);
907
913
  // shellExec(`sudo echo "" > ~/.ssh/known_hosts`);
908
914
 
909
915
  // ssh-copy-id -i ~/.ssh/id_rsa.pub -p <port_number> <username>@<host>
910
- shellExec(`ssh-copy-id -i ~/.ssh/id_rsa.pub -p ${port} ${host}`);
916
+ // shellExec(`ssh-copy-id -i ~/.ssh/id_rsa.pub -p ${port} ${host}`);
911
917
  // debug:
912
918
  // shellExec(`ssh -vvv ${host}`);
913
919
 
package/cli.md CHANGED
@@ -1,4 +1,4 @@
1
- ## underpost ci/cd cli v2.8.846
1
+ ## underpost ci/cd cli v2.8.848
2
2
 
3
3
  ### Usage: `underpost [options] [command]`
4
4
  ```
@@ -28,6 +28,7 @@ Commands:
28
28
  fs [options] [path] Manages file storage, defaulting to file upload operations.
29
29
  test [options] [deploy-list] Manages and runs tests, defaulting to the current Underpost default test suite.
30
30
  monitor [options] <deploy-id> [env] Manages health server monitoring for specified deployments.
31
+ ssh [options] Import and start ssh server and client based on current default deployment ID.
31
32
  run [options] <runner-id> [path] Runs a script from the specified path.
32
33
  lxd [options] Manages LXD containers and virtual machines.
33
34
  baremetal [options] [workflow-id] [hostname] [ip-address] Manages baremetal server operations, including installation, database setup, commissioning, and user management.
@@ -552,6 +553,20 @@ Options:
552
553
  ```
553
554
 
554
555
 
556
+ ### `ssh` :
557
+ ```
558
+ Usage: underpost ssh [options]
559
+
560
+ Import and start ssh server and client based on current default deployment ID.
561
+
562
+ Options:
563
+ --generate Generates new ssh credential and stores it in current private
564
+ keys file storage.
565
+ -h, --help display help for command
566
+
567
+ ```
568
+
569
+
555
570
  ### `run` :
556
571
  ```
557
572
  Usage: underpost run [options] <runner-id> [path]
@@ -559,7 +574,7 @@ Options:
559
574
  Runs a script from the specified path.
560
575
 
561
576
  Arguments:
562
- runner-id The runner ID to run. Options: spark-template, rmi, kill, secret, gpu-env, tf-gpu-test, dev-cluster, cyberia-ide, engine-ide, ssh-deploy, ide, monitor, db-client, cluster, deploy, tf-vae-test, deploy-job.
577
+ runner-id The runner ID to run. Options: spark-template, rmi, kill, secret, gpu-env, tf-gpu-test, dev-cluster, cyberia-ide, engine-ide, template-deploy, ssh-deploy, ide, monitor, db-client, cluster, deploy, tf-vae-test, deploy-job.
563
578
  path The absolute or relative directory path where the script is located.
564
579
 
565
580
  Options:
package/conf.js CHANGED
@@ -6,7 +6,7 @@ const DefaultConf = /**/ {
6
6
  client: {
7
7
  default: {
8
8
  metadata: {
9
- title: 'Demo App',
9
+ title: 'PWA Demo App',
10
10
  backgroundImage: 'assets/background/white0-min.jpg',
11
11
  description: 'Web application',
12
12
  keywords: ['web', 'app', 'spa', 'demo', 'github-pages'],
@@ -58,7 +58,7 @@ services:
58
58
  cpus: '0.25'
59
59
  memory: 20M
60
60
  labels: # labels in Compose file instead of Dockerfile
61
- engine.version: '2.8.846'
61
+ engine.version: '2.8.848'
62
62
  networks:
63
63
  - load-balancer
64
64
 
@@ -17,7 +17,7 @@ spec:
17
17
  spec:
18
18
  containers:
19
19
  - name: dd-template-development-blue
20
- image: localhost/rockylinux9-underpost:v2.8.846
20
+ image: localhost/rockylinux9-underpost:v2.8.848
21
21
  # resources:
22
22
  # requests:
23
23
  # memory: "124Ki"
@@ -100,7 +100,7 @@ spec:
100
100
  spec:
101
101
  containers:
102
102
  - name: dd-template-development-green
103
- image: localhost/rockylinux9-underpost:v2.8.846
103
+ image: localhost/rockylinux9-underpost:v2.8.848
104
104
  # resources:
105
105
  # requests:
106
106
  # memory: "124Ki"
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "main": "src/index.js",
4
4
  "name": "underpost",
5
- "version": "2.8.846",
5
+ "version": "2.8.848",
6
6
  "description": "pwa api rest template",
7
7
  "scripts": {
8
8
  "start": "env-cmd -f .env.production node --max-old-space-size=8192 src/server",
@@ -444,7 +444,7 @@ const UserService = {
444
444
  _id: user._id,
445
445
  }).select(UserDto.select.get());
446
446
  } else throw new Error('invalid token');
447
- }
447
+ } else delete req.body.password;
448
448
 
449
449
  switch (req.params.id) {
450
450
  default: {
@@ -453,9 +453,6 @@ const UserService = {
453
453
  });
454
454
  switch (user.role) {
455
455
  case 'admin': {
456
- if (req.body.password !== undefined && req.body.password !== user.password)
457
- req.body.password = await hashPassword(req.body.password);
458
- else delete req.body.password;
459
456
  return await User.findByIdAndUpdate(req.params.id, req.body, {
460
457
  runValidators: true,
461
458
  });
@@ -476,6 +476,12 @@ net.bridge.bridge-nf-call-arptables = 1
476
476
  net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
477
477
  { silent: true },
478
478
  );
479
+
480
+ // Increase inotify limits
481
+ shellExec(`sudo sysctl -w fs.inotify.max_user_watches=2099999999`);
482
+ shellExec(`sudo sysctl -w fs.inotify.max_user_instances=2099999999`);
483
+ shellExec(`sudo sysctl -w fs.inotify.max_queued_events=2099999999`);
484
+
479
485
  // shellExec(`sudo sysctl --system`); // Apply sysctl changes immediately
480
486
  // Apply NAT iptables rules.
481
487
  shellExec(`${underpostRoot}/manifests/maas/nat-iptables.sh`, { silent: true });
package/src/cli/deploy.js CHANGED
@@ -528,13 +528,14 @@ node bin/deploy build-full-client ${deployId}
528
528
  }).trim(),
529
529
  );
530
530
  },
531
- checkDeploymentReadyStatus(deployId, env, traffic) {
531
+ checkDeploymentReadyStatus(deployId, env, traffic, ignoresNames = []) {
532
532
  const cmd = `underpost config get container-status`;
533
533
  const pods = UnderpostDeploy.API.get(`${deployId}-${env}-${traffic}`);
534
534
  const readyPods = [];
535
535
  const notReadyPods = [];
536
536
  for (const pod of pods) {
537
537
  const { NAME } = pod;
538
+ if (ignoresNames && ignoresNames.find((t) => NAME.trim().toLowerCase().match(t.trim().toLowerCase()))) continue;
538
539
  if (
539
540
  shellExec(`sudo kubectl exec -i ${NAME} -- sh -c "${cmd}"`, { stdout: true }).match(
540
541
  `${deployId}-${env}-running-deployment`,
package/src/cli/index.js CHANGED
@@ -316,6 +316,13 @@ program
316
316
  .description('Manages health server monitoring for specified deployments.')
317
317
  .action(Underpost.monitor.callback);
318
318
 
319
+ // 'ssh' command: SSH management
320
+ program
321
+ .command('ssh')
322
+ .option('--generate', 'Generates new ssh credential and stores it in current private keys file storage.')
323
+ .description('Import and start ssh server and client based on current default deployment ID.')
324
+ .action(Underpost.ssh.callback);
325
+
319
326
  // 'run' command: Run a script
320
327
  program
321
328
  .command('run')
package/src/cli/run.js CHANGED
@@ -86,6 +86,13 @@ class UnderpostRun {
86
86
  shellExec(`${baseCommand} run ide /home/dd/engine`);
87
87
  shellExec(`${baseCommand} run ide /home/dd/engine/engine-private`);
88
88
  },
89
+ 'template-deploy': (path, options = UnderpostRun.DEFAULT_OPTION) => {
90
+ const baseCommand = options.dev || true ? 'node bin' : 'underpost';
91
+ shellCd('/home/dd/engine');
92
+ shellExec(`git reset`);
93
+ shellExec(`${baseCommand} cmt . --empty ci package-pwa-microservices-template`);
94
+ shellExec(`${baseCommand} push . underpostnet/engine`);
95
+ },
89
96
  'ssh-deploy': (path, options = UnderpostRun.DEFAULT_OPTION) => {
90
97
  const baseCommand = options.dev || true ? 'node bin' : 'underpost';
91
98
  shellCd('/home/dd/engine');
@@ -206,12 +213,13 @@ class UnderpostRun {
206
213
  const currentTraffic = UnderpostDeploy.API.getCurrentTraffic(deployId);
207
214
  const targetTraffic = currentTraffic === 'blue' ? 'green' : 'blue';
208
215
  const env = 'production';
216
+ const ignorePods = UnderpostDeploy.API.get(`${deployId}-${env}-${targetTraffic}`).map((p) => p.NAME);
209
217
  shellExec(`sudo kubectl rollout restart deployment/${deployId}-${env}-${targetTraffic}`);
210
218
 
211
219
  let secondsElapsed = 0;
212
220
  logger.info('Deployment init', { deployId, env, targetTraffic });
213
221
 
214
- while (!UnderpostDeploy.API.checkDeploymentReadyStatus(deployId, env, targetTraffic).ready) {
222
+ while (!UnderpostDeploy.API.checkDeploymentReadyStatus(deployId, env, targetTraffic, ignorePods).ready) {
215
223
  await timer(1000);
216
224
  secondsElapsed++;
217
225
  logger.info(`Deployment in progress, seconds elapsed: ${secondsElapsed}`);
package/src/cli/ssh.js ADDED
@@ -0,0 +1,32 @@
1
+ import { getNpmRootPath } from '../server/conf.js';
2
+ import { shellExec } from '../server/process.js';
3
+
4
+ class UnderpostSSH {
5
+ static API = {
6
+ /**
7
+ * @method callback
8
+ * @param {object} options
9
+ * @param {boolean} options.generate - Generates new ssh credential and stores it in current private keys file storage.
10
+ * @description Import and start ssh server and client based on current default deployment ID.
11
+ */
12
+ callback: async (
13
+ options = {
14
+ generate: false,
15
+ },
16
+ ) => {
17
+ // only import + start
18
+ // node bin/deploy ssh root@<host> <password> import
19
+
20
+ // generate + import + start
21
+ // node bin/deploy ssh root@<host> <password>
22
+
23
+ shellExec(
24
+ `node bin/deploy ssh root@${process.env.DEFAULT_DEPLOY_HOST} ${process.env.DEFAULT_DEPLOY_PASSWORD ?? `''`}${
25
+ options.generate === true ? '' : ' import'
26
+ }`,
27
+ );
28
+ },
29
+ };
30
+ }
31
+
32
+ export default UnderpostSSH;
@@ -16,6 +16,7 @@ import { SocketIo } from './components/core/SocketIo.js';
16
16
  import { SocketIoDefault } from './components/default/SocketIoDefault.js';
17
17
  import { ElementsDefault } from './components/default/ElementsDefault.js';
18
18
  import { Scroll } from './components/core/Scroll.js';
19
+ import { CssDefaultDark, CssDefaultLight } from './components/default/CssDefault.js';
19
20
 
20
21
  const htmlMainBody = async () => {
21
22
  return html`<span>Hello World!!</span>`;
@@ -25,12 +26,15 @@ window.onload = () =>
25
26
  Worker.instance({
26
27
  router: RouterDefault,
27
28
  render: async () => {
28
- await Css.loadThemes();
29
+ await Css.loadThemes([CssDefaultLight, CssDefaultDark]);
29
30
  await TranslateCore.Init();
30
31
  await TranslateDefault.Init();
31
32
  await Responsive.Init();
32
33
  await MenuDefault.Render({ htmlMainBody });
33
- await SocketIo.Init({ channels: ElementsDefault.Data });
34
+ await SocketIo.Init({
35
+ channels: ElementsDefault.Data,
36
+ path: `/`,
37
+ });
34
38
  await SocketIoDefault.Init();
35
39
  await LogInDefault();
36
40
  await LogOutDefault();
@@ -44,7 +44,7 @@ const Account = {
44
44
  { model: 'email', id: `account-email`, rules: [{ type: 'isEmpty' }, { type: 'isEmail' }] },
45
45
  {
46
46
  model: 'password',
47
- defaultValue: '*******',
47
+ defaultValue: '#Changethis123',
48
48
  id: `account-password`,
49
49
  rules: [{ type: 'isStrongPassword' }],
50
50
  },
@@ -51,8 +51,8 @@ const Css = {
51
51
  // if (!ThemesScope.find((t) => t.dark)) addTheme(CssCoreDark);
52
52
  // if (!ThemesScope.find((t) => !t.dark)) addTheme(CssCoreLight);
53
53
  if (ThemesScope.length === 0) {
54
- addTheme(CssCoreDark);
55
54
  addTheme(CssCoreLight);
55
+ addTheme(CssCoreDark);
56
56
  }
57
57
  const localStorageTheme = localStorage.getItem('_theme');
58
58
  if (localStorageTheme && Themes[localStorageTheme]) {
@@ -199,6 +199,7 @@ const addTheme = (options) => {
199
199
  if (!['core', 'css-core'].includes(options.theme))
200
200
  render += darkTheme ? await CssCoreDark.render() : await CssCoreLight.render();
201
201
  render += await options.render();
202
+ render += await subThemeManager.render();
202
203
  htmls('.theme', render);
203
204
  TriggerThemeEvents();
204
205
  }
@@ -232,18 +233,29 @@ const borderChar = (px, color, selectors, hover = false) => {
232
233
  ${color}, ${px}px ${px}px ${px}px ${color};
233
234
  `;
234
235
  };
235
-
236
236
  const boxShadow = ({ selector }) => html`
237
- <style>
238
- ${selector} {
239
- box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
240
- }
241
- ${selector}:hover {
242
- box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2), 0 10px 30px 0 rgba(0, 0, 0, 0.3);
243
- }
244
- </style>
237
+ ${darkTheme
238
+ ? html`
239
+ <style>
240
+ ${selector} {
241
+ box-shadow: 0 4px 8px 0 rgba(255, 255, 255, 0.1), 0 6px 20px 0 rgba(255, 255, 255, 0.08);
242
+ }
243
+ ${selector}:hover {
244
+ box-shadow: 0 8px 16px 0 rgba(255, 255, 255, 0.15), 0 10px 30px 0 rgba(255, 255, 255, 0.1);
245
+ }
246
+ </style>
247
+ `
248
+ : html`
249
+ <style>
250
+ ${selector} {
251
+ box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
252
+ }
253
+ ${selector}:hover {
254
+ box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2), 0 10px 30px 0 rgba(0, 0, 0, 0.3);
255
+ }
256
+ </style>
257
+ `}
245
258
  `;
246
-
247
259
  const renderMediaQuery = (mediaData) => {
248
260
  // first limit should be '0'
249
261
  return html`
@@ -677,6 +689,166 @@ const scrollBarLightRender = () => {
677
689
  .join('');
678
690
  };
679
691
 
692
+ /**
693
+ * Adjust hex color brightness toward white/black ("mix") or by modifying HSL lightness ("hsl").
694
+ *
695
+ * @param {string} hex - Color as '#rrggbb', 'rrggbb', '#rgb', or 'rgb'.
696
+ * @param {number} factor - -1..1 or -100..100 (percent). Positive = lighten, negative = darken.
697
+ * @param {{mode?: 'mix'|'hsl'}} [options]
698
+ * @returns {string} - Adjusted color as '#rrggbb' (lowercase).
699
+ */
700
+ function adjustHex(hex, factor = 0.1, options = {}) {
701
+ if (typeof hex !== 'string') throw new TypeError('hex must be a string');
702
+ if (typeof factor !== 'number') throw new TypeError('factor must be a number');
703
+
704
+ // normalize factor: allow -100..100 or -1..1
705
+ if (factor > 1 && factor <= 100) factor = factor / 100;
706
+ if (factor < -1 && factor >= -100) factor = factor / 100;
707
+ factor = Math.max(-1, Math.min(1, factor));
708
+
709
+ const mode = options.mode === 'hsl' ? 'hsl' : 'mix';
710
+
711
+ // normalize hex
712
+ let h = hex.replace(/^#/, '').trim();
713
+ if (!(h.length === 3 || h.length === 6)) throw new Error('Invalid hex format');
714
+ if (h.length === 3)
715
+ h = h
716
+ .split('')
717
+ .map((c) => c + c)
718
+ .join('');
719
+
720
+ const r = parseInt(h.slice(0, 2), 16);
721
+ const g = parseInt(h.slice(2, 4), 16);
722
+ const b = parseInt(h.slice(4, 6), 16);
723
+
724
+ const clamp = (v, a = 0, z = 255) => Math.max(a, Math.min(z, v));
725
+
726
+ const rgbToHex = (rr, gg, bb) =>
727
+ '#' +
728
+ [rr, gg, bb]
729
+ .map((v) => Math.round(v).toString(16).padStart(2, '0'))
730
+ .join('')
731
+ .toLowerCase();
732
+
733
+ if (mode === 'mix') {
734
+ // positive: mix toward white (255); negative: mix toward black (0)
735
+ const mixChannel = (c) => {
736
+ if (factor >= 0) {
737
+ return clamp(Math.round(c + (255 - c) * factor));
738
+ } else {
739
+ const a = Math.abs(factor);
740
+ return clamp(Math.round(c * (1 - a)));
741
+ }
742
+ };
743
+ return rgbToHex(mixChannel(r), mixChannel(g), mixChannel(b));
744
+ } else {
745
+ // HSL mode: convert rgb to hsl, adjust L by factor, convert back
746
+ const rgbToHsl = (r, g, b) => {
747
+ r /= 255;
748
+ g /= 255;
749
+ b /= 255;
750
+ const max = Math.max(r, g, b),
751
+ min = Math.min(r, g, b);
752
+ let h = 0,
753
+ s = 0,
754
+ l = (max + min) / 2;
755
+ if (max !== min) {
756
+ const d = max - min;
757
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
758
+ switch (max) {
759
+ case r:
760
+ h = (g - b) / d + (g < b ? 6 : 0);
761
+ break;
762
+ case g:
763
+ h = (b - r) / d + 2;
764
+ break;
765
+ case b:
766
+ h = (r - g) / d + 4;
767
+ break;
768
+ }
769
+ h /= 6;
770
+ }
771
+ return { h, s, l };
772
+ };
773
+
774
+ const hslToRgb = (h, s, l) => {
775
+ let r, g, b;
776
+ if (s === 0) {
777
+ r = g = b = l;
778
+ } else {
779
+ const hue2rgb = (p, q, t) => {
780
+ if (t < 0) t += 1;
781
+ if (t > 1) t -= 1;
782
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
783
+ if (t < 1 / 2) return q;
784
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
785
+ return p;
786
+ };
787
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
788
+ const p = 2 * l - q;
789
+ r = hue2rgb(p, q, h + 1 / 3);
790
+ g = hue2rgb(p, q, h);
791
+ b = hue2rgb(p, q, h - 1 / 3);
792
+ }
793
+ return { r: r * 255, g: g * 255, b: b * 255 };
794
+ };
795
+
796
+ const { h: hh, s: ss, l: ll } = rgbToHsl(r, g, b);
797
+ // add factor to lightness (factor already normalized -1..1)
798
+ let newL = ll + factor;
799
+ newL = Math.max(0, Math.min(1, newL));
800
+ const { r: r2, g: g2, b: b2 } = hslToRgb(hh, ss, newL);
801
+ return rgbToHex(r2, g2, b2);
802
+ }
803
+ }
804
+
805
+ // Convenience helpers:
806
+ function lightenHex(hex, percentOr01 = 0.1, options = {}) {
807
+ return adjustHex(hex, Math.abs(percentOr01), options);
808
+ }
809
+ function darkenHex(hex, percentOr01 = 0.1, options = {}) {
810
+ return adjustHex(hex, -Math.abs(percentOr01), options);
811
+ }
812
+
813
+ const subThemeManager = {
814
+ render: async function () {
815
+ if (darkTheme && this.renderDark) {
816
+ return await this.renderDark();
817
+ } else if (!darkTheme && this.renderLight) {
818
+ return await this.renderLight();
819
+ }
820
+ return html``;
821
+ },
822
+ lightColor: null,
823
+ setLightTheme: function (color) {
824
+ this.lightColor = color;
825
+ this.renderLight = async function () {
826
+ return html`<style>
827
+ button:hover,
828
+ .a-btn:hover,
829
+ .main-btn-menu-active {
830
+ color: ${this.lightColor};
831
+ background-color: ${lightenHex(this.lightColor, 0.8)};
832
+ }
833
+ </style>`;
834
+ };
835
+ },
836
+ darkColor: null,
837
+ setDarkTheme: function (color) {
838
+ this.darkColor = color;
839
+ this.renderDark = async function () {
840
+ return html`<style>
841
+ button:hover,
842
+ .a-btn:hover,
843
+ .main-btn-menu-active {
844
+ color: ${lightenHex(this.darkColor, 0.8)};
845
+ background-color: ${darkenHex(this.darkColor, 0.75)};
846
+ }
847
+ </style>`;
848
+ };
849
+ },
850
+ };
851
+
680
852
  const scrollBarDarkRender = () => {
681
853
  return cssBrowserCodes
682
854
  .map(
@@ -685,8 +857,8 @@ const scrollBarDarkRender = () => {
685
857
  ::-` +
686
858
  b +
687
859
  `-scrollbar {
688
- width: 5px;
689
- height: 5px;
860
+ width: 8px;
861
+ height: 8px;
690
862
  /* line-height: 1em; */
691
863
  }
692
864
 
@@ -702,7 +874,7 @@ const scrollBarDarkRender = () => {
702
874
  b +
703
875
  `-scrollbar-thumb {
704
876
  background: #74747457;
705
- border-radius: 3px;
877
+ border-radius: 4px;
706
878
  }
707
879
 
708
880
  /* Handle on hover */
@@ -907,4 +1079,8 @@ export {
907
1079
  simpleIconsRender,
908
1080
  extractBackgroundImageUrl,
909
1081
  renderChessPattern,
1082
+ subThemeManager,
1083
+ lightenHex,
1084
+ darkenHex,
1085
+ adjustHex,
910
1086
  };
@@ -17,8 +17,8 @@ const CssCommonCore = async () => {
17
17
  await AgGrid.RenderStyle();
18
18
  return html`<style>
19
19
  .top-bar-app-icon {
20
- width: 35px;
21
- height: 35px;
20
+ width: 40px;
21
+ height: 40px;
22
22
  }
23
23
  .mini-title {
24
24
  font-size: 15px;
@@ -33,7 +33,7 @@ const CssCommonCore = async () => {
33
33
  top: 0;
34
34
  left: 0;
35
35
  transition: 0.3s;
36
- height: 5px;
36
+ height: 10px;
37
37
  width: 100%;
38
38
  z-index: 11;
39
39
  }
@@ -51,7 +51,6 @@ const CssCommonCore = async () => {
51
51
  cursor: grabbing !important;
52
52
  }
53
53
  .btn-label-content {
54
- height: 100%;
55
54
  top: 15px;
56
55
  }
57
56
  .badge {
@@ -16,10 +16,7 @@ const Docs = {
16
16
  const docData = this.Data.find((d) => d.type === type);
17
17
  const ModalId = `modal-docs-${docData.type}`;
18
18
  const { barConfig } = await Themes[Css.currentTheme]();
19
- barConfig.buttons.close.onClick = () => {
20
- setQueryPath({ path: 'docs' });
21
- Modal.removeModal(ModalId);
22
- };
19
+
23
20
  await Modal.Render({
24
21
  barConfig,
25
22
  title: renderViewTitle(docData),
@@ -53,6 +50,9 @@ const Docs = {
53
50
  }
54
51
  };
55
52
  Modal.Data[ModalId].onObserverListener[ModalId]();
53
+ Modal.Data[ModalId].onCloseListener[ModalId] = () => {
54
+ setQueryPath({ path: 'docs', queryPath: '' });
55
+ };
56
56
  },
57
57
  Data: [
58
58
  {