@underpostnet/underpost 2.96.1 → 2.97.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/.dockerignore +1 -2
- package/.env.development +0 -3
- package/.env.production +0 -3
- package/.env.test +0 -3
- package/.prettierignore +1 -2
- package/README.md +31 -31
- package/baremetal/commission-workflows.json +94 -17
- package/bin/deploy.js +1 -1
- package/cli.md +75 -41
- package/conf.js +1 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +4 -4
- package/package.json +3 -2
- package/packer/scripts/fuse-tar-root +3 -3
- package/scripts/disk-clean.sh +128 -187
- package/scripts/gpu-diag.sh +2 -2
- package/scripts/ip-info.sh +11 -11
- package/scripts/ipxe-setup.sh +197 -0
- package/scripts/maas-upload-boot-resource.sh +1 -1
- package/scripts/nvim.sh +1 -1
- package/scripts/packer-setup.sh +13 -13
- package/scripts/ports-ls.sh +31 -0
- package/scripts/quick-tftp.sh +19 -0
- package/scripts/rocky-setup.sh +2 -2
- package/scripts/rpmfusion-ffmpeg-setup.sh +4 -4
- package/scripts/ssl.sh +7 -7
- package/src/api/document/document.controller.js +15 -0
- package/src/api/document/document.model.js +44 -1
- package/src/api/document/document.router.js +2 -0
- package/src/api/document/document.service.js +398 -26
- package/src/cli/baremetal.js +2001 -463
- package/src/cli/cloud-init.js +354 -231
- package/src/cli/cluster.js +51 -53
- package/src/cli/db.js +22 -0
- package/src/cli/deploy.js +7 -3
- package/src/cli/image.js +1 -0
- package/src/cli/index.js +40 -37
- package/src/cli/lxd.js +3 -3
- package/src/cli/run.js +78 -12
- package/src/cli/ssh.js +1 -1
- package/src/client/components/core/Css.js +16 -2
- package/src/client/components/core/Input.js +3 -1
- package/src/client/components/core/Modal.js +125 -159
- package/src/client/components/core/Panel.js +436 -31
- package/src/client/components/core/PanelForm.js +222 -37
- package/src/client/components/core/SearchBox.js +801 -0
- package/src/client/components/core/Translate.js +11 -0
- package/src/client/services/document/document.service.js +42 -0
- package/src/index.js +1 -1
- package/src/server/dns.js +12 -6
- package/src/server/start.js +14 -6
package/src/cli/lxd.js
CHANGED
|
@@ -74,8 +74,8 @@ class UnderpostLxd {
|
|
|
74
74
|
const npmRoot = getNpmRootPath();
|
|
75
75
|
const underpostRoot = options?.dev === true ? '.' : `${npmRoot}/underpost`;
|
|
76
76
|
if (options.reset === true) {
|
|
77
|
-
shellExec(`sudo systemctl stop snap.lxd.daemon
|
|
78
|
-
shellExec(`sudo snap remove lxd --purge
|
|
77
|
+
shellExec(`sudo systemctl stop snap.lxd.daemon`);
|
|
78
|
+
shellExec(`sudo snap remove lxd --purge`);
|
|
79
79
|
}
|
|
80
80
|
if (options.install === true) shellExec(`sudo snap install lxd`);
|
|
81
81
|
if (options.init === true) {
|
|
@@ -213,7 +213,7 @@ ipv6.address=none`);
|
|
|
213
213
|
for (const port of ports.split(',')) {
|
|
214
214
|
for (const protocol of protocols) {
|
|
215
215
|
const deviceName = `${vmName}-${protocol}-port-${port}`;
|
|
216
|
-
shellExec(`lxc config device remove ${vmName} ${deviceName}
|
|
216
|
+
shellExec(`lxc config device remove ${vmName} ${deviceName}`); // Use to prevent error if device doesn't exist
|
|
217
217
|
shellExec(
|
|
218
218
|
`lxc config device add ${vmName} ${deviceName} proxy listen=${protocol}:${hostIp}:${port} connect=${protocol}:${vmIp}:${port} nat=true`,
|
|
219
219
|
);
|
package/src/cli/run.js
CHANGED
|
@@ -57,6 +57,7 @@ class UnderpostRun {
|
|
|
57
57
|
* @property {boolean} force - Whether to force the operation.
|
|
58
58
|
* @property {boolean} reset - Whether to reset the operation.
|
|
59
59
|
* @property {boolean} tls - Whether to use TLS.
|
|
60
|
+
* @property {string} cmd - The command to run in the container.
|
|
60
61
|
* @property {string} tty - The TTY option for the container.
|
|
61
62
|
* @property {string} stdin - The stdin option for the container.
|
|
62
63
|
* @property {string} restartPolicy - The restart policy for the container.
|
|
@@ -87,6 +88,7 @@ class UnderpostRun {
|
|
|
87
88
|
* @property {string} deployId - The deployment ID.
|
|
88
89
|
* @property {string} instanceId - The instance ID.
|
|
89
90
|
* @property {string} user - The user to run as.
|
|
91
|
+
* @property {string} pid - The process ID.
|
|
90
92
|
* @memberof UnderpostRun
|
|
91
93
|
*/
|
|
92
94
|
static DEFAULT_OPTION = {
|
|
@@ -104,6 +106,7 @@ class UnderpostRun {
|
|
|
104
106
|
force: false,
|
|
105
107
|
reset: false,
|
|
106
108
|
tls: false,
|
|
109
|
+
cmd: '',
|
|
107
110
|
tty: '',
|
|
108
111
|
stdin: '',
|
|
109
112
|
restartPolicy: '',
|
|
@@ -134,6 +137,7 @@ class UnderpostRun {
|
|
|
134
137
|
deployId: '',
|
|
135
138
|
instanceId: '',
|
|
136
139
|
user: '',
|
|
140
|
+
pid: '',
|
|
137
141
|
};
|
|
138
142
|
/**
|
|
139
143
|
* @static
|
|
@@ -187,6 +191,7 @@ class UnderpostRun {
|
|
|
187
191
|
* @memberof UnderpostRun
|
|
188
192
|
*/
|
|
189
193
|
kill: (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
194
|
+
if (options.pid) return shellExec(`sudo kill -9 ${options.pid}`);
|
|
190
195
|
for (const _path of path.split(',')) {
|
|
191
196
|
if (_path.split('+')[1]) {
|
|
192
197
|
let [port, sumPortOffSet] = _path.split('+');
|
|
@@ -564,7 +569,7 @@ class UnderpostRun {
|
|
|
564
569
|
const currentTraffic = isDeployRunnerContext(path, options)
|
|
565
570
|
? UnderpostDeploy.API.getCurrentTraffic(deployId, { namespace: options.namespace })
|
|
566
571
|
: '';
|
|
567
|
-
let targetTraffic = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : '';
|
|
572
|
+
let targetTraffic = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : 'green';
|
|
568
573
|
if (targetTraffic) versions = targetTraffic;
|
|
569
574
|
|
|
570
575
|
shellExec(
|
|
@@ -574,8 +579,13 @@ class UnderpostRun {
|
|
|
574
579
|
);
|
|
575
580
|
|
|
576
581
|
if (isDeployRunnerContext(path, options)) {
|
|
582
|
+
const cmdString = options.cmd
|
|
583
|
+
? ` --cmd ${options.cmd.find((c) => c.match('"')) ? `"${options.cmd}"` : `'${options.cmd}'`}`
|
|
584
|
+
: '';
|
|
577
585
|
shellExec(
|
|
578
|
-
`${baseCommand} deploy --kubeadm
|
|
586
|
+
`${baseCommand} deploy --kubeadm${cmdString} --replicas ${
|
|
587
|
+
replicas
|
|
588
|
+
} --disable-update-proxy ${deployId} ${env} --versions ${versions}${options.namespace ? ` --namespace ${options.namespace}` : ''}`,
|
|
579
589
|
);
|
|
580
590
|
if (!targetTraffic)
|
|
581
591
|
targetTraffic = UnderpostDeploy.API.getCurrentTraffic(deployId, { namespace: options.namespace });
|
|
@@ -915,7 +925,7 @@ EOF
|
|
|
915
925
|
shellExec(`docker exec -i kind-worker bash -c "mkdir -p ${volumeHostPath}"`);
|
|
916
926
|
shellExec(`docker cp ${volumeHostPath}/engine kind-worker:${volumeHostPath}/engine`);
|
|
917
927
|
shellExec(
|
|
918
|
-
`docker exec -i kind-worker bash -c "chown -R 1000:1000 ${volumeHostPath}
|
|
928
|
+
`docker exec -i kind-worker bash -c "chown -R 1000:1000 ${volumeHostPath}; chmod -R 755 ${volumeHostPath}"`,
|
|
919
929
|
);
|
|
920
930
|
} else {
|
|
921
931
|
shellExec(`kubectl apply -f ${options.underpostRoot}/manifests/pv-pvc-dd.yaml -n ${options.namespace}`);
|
|
@@ -1157,6 +1167,7 @@ EOF
|
|
|
1157
1167
|
* @memberof UnderpostRun
|
|
1158
1168
|
*/
|
|
1159
1169
|
cluster: async (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1170
|
+
const { underpostRoot } = options;
|
|
1160
1171
|
const env = options.dev ? 'development' : 'production';
|
|
1161
1172
|
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
1162
1173
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
@@ -1166,12 +1177,16 @@ EOF
|
|
|
1166
1177
|
await timer(5000);
|
|
1167
1178
|
shellExec(`${baseCommand} cluster${baseClusterCommand} --${clusterType}`);
|
|
1168
1179
|
await timer(5000);
|
|
1169
|
-
let [runtimeImage, deployList] =
|
|
1170
|
-
|
|
1171
|
-
|
|
1180
|
+
let [runtimeImage, deployList] =
|
|
1181
|
+
path && path.trim() && path.split(',')
|
|
1182
|
+
? path.split(',')
|
|
1183
|
+
: [
|
|
1184
|
+
'express',
|
|
1185
|
+
fs.readFileSync(`${underpostRoot}/engine-private/deploy/dd.router`, 'utf8').replaceAll(',', '+'),
|
|
1186
|
+
];
|
|
1172
1187
|
shellExec(
|
|
1173
|
-
`${baseCommand} image${baseClusterCommand}${
|
|
1174
|
-
runtimeImage ? ` --pull-base --path /
|
|
1188
|
+
`${baseCommand} image${baseClusterCommand} --build ${
|
|
1189
|
+
runtimeImage ? ` --pull-base --path ${underpostRoot}/src/runtime/${runtimeImage}` : ''
|
|
1175
1190
|
} --${clusterType}`,
|
|
1176
1191
|
);
|
|
1177
1192
|
if (!deployList) {
|
|
@@ -1246,7 +1261,7 @@ EOF
|
|
|
1246
1261
|
'disk-clean': async (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1247
1262
|
const { underpostRoot } = options;
|
|
1248
1263
|
shellExec(`chmod +x ${underpostRoot}/scripts/disk-clean.sh`);
|
|
1249
|
-
shellExec(`./scripts/disk-clean.sh
|
|
1264
|
+
shellExec(`./scripts/disk-clean.sh`);
|
|
1250
1265
|
},
|
|
1251
1266
|
|
|
1252
1267
|
/**
|
|
@@ -1405,7 +1420,7 @@ EOF
|
|
|
1405
1420
|
shellExec(
|
|
1406
1421
|
`${baseCommand} deploy${options.dev ? '' : ' --kubeadm'}${options.devProxyPortOffset ? ' --disable-deployment-proxy' : ''} --disable-update-deployment ${deployId} ${env} --versions ${versions}`,
|
|
1407
1422
|
);
|
|
1408
|
-
} else logger.error(
|
|
1423
|
+
} else logger.error(`Service pod ${podToMonitor} failed to start in time.`);
|
|
1409
1424
|
if (options.etcHosts === true) {
|
|
1410
1425
|
const hostListenResult = UnderpostDeploy.API.etcHostFactory([host]);
|
|
1411
1426
|
logger.info(hostListenResult.renderHosts);
|
|
@@ -1447,6 +1462,32 @@ EOF
|
|
|
1447
1462
|
console.log(result);
|
|
1448
1463
|
},
|
|
1449
1464
|
|
|
1465
|
+
/**
|
|
1466
|
+
* @method ps
|
|
1467
|
+
* @description Displays running processes that match a specified path or keyword.
|
|
1468
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as a keyword to filter processes).
|
|
1469
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1470
|
+
* @memberof UnderpostRun
|
|
1471
|
+
*/
|
|
1472
|
+
ps: async (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1473
|
+
const out = shellExec(`ps aux${path ? `| grep '${path}' | grep -v grep` : ''}`, {
|
|
1474
|
+
stdout: true,
|
|
1475
|
+
silent: true,
|
|
1476
|
+
});
|
|
1477
|
+
console.log(path ? out.replaceAll(path, path.bgYellow.black.bold) : out);
|
|
1478
|
+
},
|
|
1479
|
+
|
|
1480
|
+
/**
|
|
1481
|
+
* @method ptls
|
|
1482
|
+
* @description Set on ~/.bashrc alias: ports <port> Command to list listening ports that match the given keyword.
|
|
1483
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as a keyword to filter listening ports).
|
|
1484
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1485
|
+
* @memberof UnderpostRun
|
|
1486
|
+
*/
|
|
1487
|
+
ptls: async (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1488
|
+
shellExec(`chmod +x ${options.underpostRoot}/scripts/ports-ls.sh`);
|
|
1489
|
+
shellExec(`${options.underpostRoot}/scripts/ports-ls.sh`);
|
|
1490
|
+
},
|
|
1450
1491
|
/**
|
|
1451
1492
|
* @method release-cmt
|
|
1452
1493
|
* @description Commits and pushes a new release for the `engine` repository with a message indicating the new version.
|
|
@@ -1462,6 +1503,31 @@ EOF
|
|
|
1462
1503
|
shellExec(`underpost push . ${process.env.GITHUB_USERNAME}/engine`, { silent: true });
|
|
1463
1504
|
},
|
|
1464
1505
|
|
|
1506
|
+
/**
|
|
1507
|
+
* @method deploy-test
|
|
1508
|
+
* @description Deploys a test deployment (`dd-test`) in either development or production mode, setting up necessary secrets and starting the deployment.
|
|
1509
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the deployment ID).
|
|
1510
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1511
|
+
* @memberof UnderpostRun
|
|
1512
|
+
*/
|
|
1513
|
+
'deploy-test': async (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1514
|
+
// Note: use recomendation empty deploy cluster: node bin --dev cluster
|
|
1515
|
+
const env = options.dev ? 'development' : 'production';
|
|
1516
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
1517
|
+
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
1518
|
+
const inputs = path ? path.split(',') : [];
|
|
1519
|
+
const deployId = inputs[0] ? inputs[0] : 'dd-test';
|
|
1520
|
+
const cmd = options.cmd
|
|
1521
|
+
? options.cmd
|
|
1522
|
+
: [
|
|
1523
|
+
`npm install -g npm@11.2.0`,
|
|
1524
|
+
`npm install -g underpost`,
|
|
1525
|
+
`${baseCommand} secret underpost --create-from-file /etc/config/.env.${env}`,
|
|
1526
|
+
`${baseCommand} start --build --run ${deployId} ${env} --underpost-quickly-install`,
|
|
1527
|
+
];
|
|
1528
|
+
shellExec(`node bin run sync${baseClusterCommand} --cron-jobs none dd-test --cmd "${cmd}"`);
|
|
1529
|
+
},
|
|
1530
|
+
|
|
1465
1531
|
/**
|
|
1466
1532
|
* @method sync-replica
|
|
1467
1533
|
* @description Syncs a replica for the dd.router
|
|
@@ -1597,7 +1663,7 @@ ${hostNetwork ? ` hostNetwork: ${hostNetwork}` : ''}
|
|
|
1597
1663
|
imagePullPolicy: ${imagePullPolicy}
|
|
1598
1664
|
tty: ${tty}
|
|
1599
1665
|
stdin: ${stdin}
|
|
1600
|
-
command: ${JSON.stringify(options.
|
|
1666
|
+
command: ${JSON.stringify(options.cmd ? options.cmd : ['/bin/bash', '-c'])}
|
|
1601
1667
|
${
|
|
1602
1668
|
args.length > 0
|
|
1603
1669
|
? ` args:
|
|
@@ -1649,7 +1715,7 @@ EOF`;
|
|
|
1649
1715
|
try {
|
|
1650
1716
|
const npmRoot = getNpmRootPath();
|
|
1651
1717
|
const underpostRoot = options?.dev === true ? '.' : `${npmRoot}/underpost`;
|
|
1652
|
-
if (options.
|
|
1718
|
+
if (options.cmd) options.cmd = options.cmd.split(',');
|
|
1653
1719
|
if (options.args) options.args = options.args.split(',');
|
|
1654
1720
|
if (!options.underpostRoot) options.underpostRoot = underpostRoot;
|
|
1655
1721
|
if (!options.namespace) options.namespace = 'default';
|
package/src/cli/ssh.js
CHANGED
|
@@ -280,7 +280,7 @@ EOF`);
|
|
|
280
280
|
options.password = confNode.users[options.user].password;
|
|
281
281
|
logger.info(`Using saved password for user ${options.user}`);
|
|
282
282
|
}
|
|
283
|
-
options.port = confNode.users[options.user].port ||
|
|
283
|
+
options.port = options.port || confNode.users[options.user].port || 22;
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
|
|
@@ -865,10 +865,17 @@ const subThemeManager = {
|
|
|
865
865
|
return html`<style>
|
|
866
866
|
button:hover,
|
|
867
867
|
.a-btn:hover,
|
|
868
|
-
.main-btn-menu-active
|
|
868
|
+
.main-btn-menu-active,
|
|
869
|
+
.top-bar-search-box-container:hover {
|
|
869
870
|
color: ${this.lightColor};
|
|
870
871
|
background-color: ${lightenHex(this.lightColor, 0.8)};
|
|
871
872
|
}
|
|
873
|
+
.top-bar-search-box-container i {
|
|
874
|
+
color: #000;
|
|
875
|
+
}
|
|
876
|
+
.top-bar-search-box-container:hover i {
|
|
877
|
+
color: ${this.lightColor};
|
|
878
|
+
}
|
|
872
879
|
.main-sub-btn-active {
|
|
873
880
|
color: ${this.lightColor};
|
|
874
881
|
background-color: rgba(0, 0, 0, 0.3);
|
|
@@ -887,10 +894,17 @@ const subThemeManager = {
|
|
|
887
894
|
return html`<style>
|
|
888
895
|
button:hover,
|
|
889
896
|
.a-btn:hover,
|
|
890
|
-
.main-btn-menu-active
|
|
897
|
+
.main-btn-menu-active,
|
|
898
|
+
.top-bar-search-box-container:hover {
|
|
891
899
|
color: ${lightenHex(this.darkColor, 0.8)};
|
|
892
900
|
background-color: ${darkenHex(this.darkColor, 0.75)};
|
|
893
901
|
}
|
|
902
|
+
.top-bar-search-box-container i {
|
|
903
|
+
color: #fff;
|
|
904
|
+
}
|
|
905
|
+
.top-bar-search-box-container:hover i {
|
|
906
|
+
color: ${lightenHex(this.darkColor, 0.8)};
|
|
907
|
+
}
|
|
894
908
|
.main-sub-btn-active {
|
|
895
909
|
color: ${lightenHex(this.darkColor, 0.8)};
|
|
896
910
|
background-color: rgba(255, 255, 255, 0.3);
|
|
@@ -34,6 +34,7 @@ import { Badge } from './Badge.js';
|
|
|
34
34
|
import { Worker } from './Worker.js';
|
|
35
35
|
import { Scroll } from './Scroll.js';
|
|
36
36
|
import { windowGetH, windowGetW } from './windowGetDimensions.js';
|
|
37
|
+
import { SearchBox } from './SearchBox.js';
|
|
37
38
|
|
|
38
39
|
const logger = loggerFactory(import.meta, { trace: true });
|
|
39
40
|
|
|
@@ -549,6 +550,9 @@ const Modal = {
|
|
|
549
550
|
const inputInfoNode = s(`.input-info-${inputSearchBoxId}`).cloneNode(true);
|
|
550
551
|
s(`.input-info-${inputSearchBoxId}`).remove();
|
|
551
552
|
{
|
|
553
|
+
// Inject SearchBox base styles
|
|
554
|
+
SearchBox.injectStyles();
|
|
555
|
+
|
|
552
556
|
const id = 'search-box-history';
|
|
553
557
|
const searchBoxHistoryId = id;
|
|
554
558
|
const formDataInfoNode = [
|
|
@@ -591,6 +595,7 @@ const Modal = {
|
|
|
591
595
|
|
|
592
596
|
const renderSearchResult = async (results) => {
|
|
593
597
|
htmls(`.html-${searchBoxHistoryId}`, '');
|
|
598
|
+
|
|
594
599
|
if (results.length === 0) {
|
|
595
600
|
append(
|
|
596
601
|
`.html-${searchBoxHistoryId}`,
|
|
@@ -609,108 +614,61 @@ const Modal = {
|
|
|
609
614
|
}),
|
|
610
615
|
}),
|
|
611
616
|
);
|
|
617
|
+
return;
|
|
612
618
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
style="${renderCssAttr({ style: { width: '25px', height: '25px' } })}"
|
|
628
|
-
/>`
|
|
629
|
-
: ''
|
|
630
|
-
} ${Translate.Render(result.routerId)}`,
|
|
631
|
-
class: `wfa search-result-btn-${result.routerId} ${
|
|
632
|
-
indexResult === currentKeyBoardSearchBoxIndex ? 'main-btn-menu-active' : ''
|
|
633
|
-
} search-result-btn-${indexResult}`,
|
|
634
|
-
style: renderCssAttr({
|
|
635
|
-
style: { padding: '3px', margin: '2px', 'text-align': 'left' },
|
|
636
|
-
}),
|
|
637
|
-
}),
|
|
638
|
-
);
|
|
639
|
-
s(`.search-result-btn-${result.routerId}`).onclick = () => {
|
|
640
|
-
if (!s(`.html-${searchBoxHistoryId}`) || !s(`.html-${searchBoxHistoryId}`).hasChildNodes()) return;
|
|
641
|
-
s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.remove(
|
|
642
|
-
`main-btn-menu-active`,
|
|
643
|
-
);
|
|
644
|
-
currentKeyBoardSearchBoxIndex = indexRender;
|
|
645
|
-
s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.add(
|
|
646
|
-
`main-btn-menu-active`,
|
|
647
|
-
);
|
|
648
|
-
setSearchValue(`.search-result-btn-${result.routerId}`);
|
|
649
|
-
};
|
|
650
|
-
}
|
|
619
|
+
|
|
620
|
+
// Use SearchBox component for rendering results
|
|
621
|
+
const searchContext = {
|
|
622
|
+
RouterInstance: Worker.RouterInstance,
|
|
623
|
+
options: options,
|
|
624
|
+
onResultClick: () => {
|
|
625
|
+
// Dismiss search box on result click
|
|
626
|
+
if (s(`.${searchBoxHistoryId}`)) {
|
|
627
|
+
Modal.removeModal(searchBoxHistoryId);
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
SearchBox.renderResults(results, `html-${searchBoxHistoryId}`, searchContext);
|
|
651
633
|
};
|
|
652
634
|
|
|
653
|
-
const getResultSearchBox = (validatorData) => {
|
|
654
|
-
if (!s(`.html-${searchBoxHistoryId}`)
|
|
635
|
+
const getResultSearchBox = async (validatorData) => {
|
|
636
|
+
if (!s(`.html-${searchBoxHistoryId}`)) return;
|
|
655
637
|
const { model, id } = validatorData;
|
|
638
|
+
|
|
656
639
|
switch (model) {
|
|
657
640
|
case 'search-box':
|
|
658
641
|
{
|
|
659
|
-
if (
|
|
660
|
-
s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex] &&
|
|
661
|
-
s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList
|
|
662
|
-
)
|
|
663
|
-
s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.remove(
|
|
664
|
-
`main-btn-menu-active`,
|
|
665
|
-
);
|
|
666
642
|
currentKeyBoardSearchBoxIndex = 0;
|
|
667
|
-
if (
|
|
668
|
-
s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex] &&
|
|
669
|
-
s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList
|
|
670
|
-
)
|
|
671
|
-
s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.add(
|
|
672
|
-
`main-btn-menu-active`,
|
|
673
|
-
);
|
|
674
643
|
results = [];
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
) {
|
|
689
|
-
const fontAwesomeIcon = getAllChildNodes(s(`.main-btn-${routerId}`)).find((e) => {
|
|
690
|
-
return (
|
|
691
|
-
e.classList &&
|
|
692
|
-
Array.from(e.classList).find((e) => e.match('fa-') && !e.match('fa-grip-vertical'))
|
|
693
|
-
);
|
|
694
|
-
});
|
|
695
|
-
const imgElement = getAllChildNodes(s(`.main-btn-${routerId}`)).find((e) => {
|
|
696
|
-
return (
|
|
697
|
-
e.classList &&
|
|
698
|
-
Array.from(e.classList).find((e) =>
|
|
699
|
-
options.searchCustomImgClass
|
|
700
|
-
? e.match(options.searchCustomImgClass)
|
|
701
|
-
: e.match('img-btn-square-menu'),
|
|
702
|
-
)
|
|
703
|
-
);
|
|
704
|
-
});
|
|
705
|
-
if (imgElement || fontAwesomeIcon) {
|
|
706
|
-
results.push({
|
|
707
|
-
routerId,
|
|
708
|
-
fontAwesomeIcon: fontAwesomeIcon,
|
|
709
|
-
imgElement,
|
|
710
|
-
});
|
|
711
|
-
}
|
|
644
|
+
|
|
645
|
+
const query = s(`.${id}`) ? s(`.${id}`).value : '';
|
|
646
|
+
const trimmedQuery = query.trim();
|
|
647
|
+
|
|
648
|
+
// Use SearchBox component for extensible search
|
|
649
|
+
const searchContext = {
|
|
650
|
+
RouterInstance: Worker.RouterInstance,
|
|
651
|
+
options: options,
|
|
652
|
+
minQueryLength: options?.minSearchQueryLength || 1, // Allow single character search by default
|
|
653
|
+
onResultClick: () => {
|
|
654
|
+
// Dismiss search box on result click
|
|
655
|
+
if (s(`.${searchBoxHistoryId}`)) {
|
|
656
|
+
Modal.removeModal(searchBoxHistoryId);
|
|
712
657
|
}
|
|
713
|
-
}
|
|
658
|
+
},
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
// Check minimum query length (default: 1 character)
|
|
662
|
+
const minLength = searchContext.minQueryLength;
|
|
663
|
+
if (trimmedQuery.length >= minLength) {
|
|
664
|
+
results = await SearchBox.search(trimmedQuery, searchContext);
|
|
665
|
+
renderSearchResult(results);
|
|
666
|
+
} else if (trimmedQuery.length === 0) {
|
|
667
|
+
// Show history when query is empty
|
|
668
|
+
renderSearchResult(historySearchBox);
|
|
669
|
+
} else {
|
|
670
|
+
// Query is too short - show nothing or a hint
|
|
671
|
+
renderSearchResult([]);
|
|
714
672
|
}
|
|
715
673
|
}
|
|
716
674
|
break;
|
|
@@ -718,8 +676,6 @@ const Modal = {
|
|
|
718
676
|
default:
|
|
719
677
|
break;
|
|
720
678
|
}
|
|
721
|
-
if (s(`.${inputSearchBoxId}`).value.trim()) renderSearchResult(results);
|
|
722
|
-
else renderSearchResult(historySearchBox);
|
|
723
679
|
};
|
|
724
680
|
|
|
725
681
|
const searchBoxCallBack = async (validatorData) => {
|
|
@@ -745,25 +701,19 @@ const Modal = {
|
|
|
745
701
|
const getDefaultSearchBoxSelector = () => `.search-result-btn-${currentKeyBoardSearchBoxIndex}`;
|
|
746
702
|
|
|
747
703
|
const updateSearchBoxValue = (selector) => {
|
|
748
|
-
if (!selector)
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
s(selector).childNodes[s(selector).childNodes.length - 2] &&
|
|
762
|
-
s(selector).childNodes[s(selector).childNodes.length - 2].outerText &&
|
|
763
|
-
s(selector).childNodes[s(selector).childNodes.length - 2].outerText.trim()
|
|
764
|
-
) {
|
|
765
|
-
s(`.${inputSearchBoxId}`).value =
|
|
766
|
-
s(selector).childNodes[s(selector).childNodes.length - 2].outerText.trim();
|
|
704
|
+
if (!selector) {
|
|
705
|
+
// Get the currently active search result item
|
|
706
|
+
const activeItem = s(`.html-${searchBoxHistoryId} .search-result-item.active-search-result`);
|
|
707
|
+
if (activeItem) {
|
|
708
|
+
const titleEl = activeItem.querySelector('.search-result-title');
|
|
709
|
+
if (titleEl && titleEl.textContent) {
|
|
710
|
+
s(`.${inputSearchBoxId}`).value = titleEl.textContent.trim();
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
} else if (s(selector)) {
|
|
714
|
+
const titleEl = s(selector).querySelector('.search-result-title');
|
|
715
|
+
if (titleEl && titleEl.textContent) {
|
|
716
|
+
s(`.${inputSearchBoxId}`).value = titleEl.textContent.trim();
|
|
767
717
|
}
|
|
768
718
|
}
|
|
769
719
|
checkHistoryBoxTitleStatus();
|
|
@@ -771,17 +721,29 @@ const Modal = {
|
|
|
771
721
|
};
|
|
772
722
|
|
|
773
723
|
const setSearchValue = (selector) => {
|
|
774
|
-
|
|
724
|
+
// Get all search result items
|
|
725
|
+
const allItems = sa(`.html-${searchBoxHistoryId} .search-result-item`);
|
|
726
|
+
if (!allItems || allItems.length === 0) return;
|
|
775
727
|
|
|
776
|
-
|
|
777
|
-
if (!
|
|
728
|
+
const activeItem = allItems[currentKeyBoardSearchBoxIndex];
|
|
729
|
+
if (!activeItem) return;
|
|
778
730
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
731
|
+
const resultId = activeItem.getAttribute('data-result-id');
|
|
732
|
+
const resultType = activeItem.getAttribute('data-result-type');
|
|
733
|
+
|
|
734
|
+
if (resultType === 'route' && results[currentKeyBoardSearchBoxIndex]) {
|
|
735
|
+
historySearchBox = historySearchBox.filter(
|
|
736
|
+
(h) => h.routerId !== results[currentKeyBoardSearchBoxIndex].routerId,
|
|
737
|
+
);
|
|
738
|
+
historySearchBox.unshift(results[currentKeyBoardSearchBoxIndex]);
|
|
739
|
+
updateSearchBoxValue();
|
|
740
|
+
if (s(`.main-btn-${resultId}`)) {
|
|
741
|
+
s(`.main-btn-${resultId}`).click();
|
|
742
|
+
}
|
|
743
|
+
} else {
|
|
744
|
+
// Trigger click on custom result
|
|
745
|
+
activeItem.click();
|
|
746
|
+
}
|
|
785
747
|
Modal.removeModal(searchBoxHistoryId);
|
|
786
748
|
};
|
|
787
749
|
let boxHistoryDelayRender = 0;
|
|
@@ -879,27 +841,29 @@ const Modal = {
|
|
|
879
841
|
keys: ['ArrowUp'],
|
|
880
842
|
eventCallBack: () => {
|
|
881
843
|
if (s(`.${id}`)) {
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
844
|
+
const allItems = sa(`.html-${searchBoxHistoryId} .search-result-item`);
|
|
845
|
+
if (!allItems || allItems.length === 0) return;
|
|
846
|
+
|
|
847
|
+
// Remove active class from current
|
|
848
|
+
if (allItems[currentKeyBoardSearchBoxIndex]) {
|
|
849
|
+
allItems[currentKeyBoardSearchBoxIndex].classList.remove('active-search-result');
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Navigate up
|
|
853
|
+
if (currentKeyBoardSearchBoxIndex > 0) {
|
|
889
854
|
currentKeyBoardSearchBoxIndex--;
|
|
890
|
-
s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.add(
|
|
891
|
-
`main-btn-menu-active`,
|
|
892
|
-
);
|
|
893
855
|
} else {
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
856
|
+
currentKeyBoardSearchBoxIndex = allItems.length - 1;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// Add active class to new and ensure visibility
|
|
860
|
+
if (allItems[currentKeyBoardSearchBoxIndex]) {
|
|
861
|
+
allItems[currentKeyBoardSearchBoxIndex].classList.add('active-search-result');
|
|
862
|
+
// Use optimized scroll method to ensure item is always visible
|
|
863
|
+
const container = s(`.html-${searchBoxHistoryId}`);
|
|
864
|
+
if (container) {
|
|
865
|
+
SearchBox.scrollIntoViewIfNeeded(allItems[currentKeyBoardSearchBoxIndex], container);
|
|
866
|
+
}
|
|
903
867
|
}
|
|
904
868
|
updateSearchBoxValue();
|
|
905
869
|
}
|
|
@@ -912,27 +876,29 @@ const Modal = {
|
|
|
912
876
|
keys: ['ArrowDown'],
|
|
913
877
|
eventCallBack: () => {
|
|
914
878
|
if (s(`.${id}`)) {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
879
|
+
const allItems = sa(`.html-${searchBoxHistoryId} .search-result-item`);
|
|
880
|
+
if (!allItems || allItems.length === 0) return;
|
|
881
|
+
|
|
882
|
+
// Remove active class from current
|
|
883
|
+
if (allItems[currentKeyBoardSearchBoxIndex]) {
|
|
884
|
+
allItems[currentKeyBoardSearchBoxIndex].classList.remove('active-search-result');
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Navigate down
|
|
888
|
+
if (currentKeyBoardSearchBoxIndex < allItems.length - 1) {
|
|
922
889
|
currentKeyBoardSearchBoxIndex++;
|
|
923
|
-
s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.add(
|
|
924
|
-
`main-btn-menu-active`,
|
|
925
|
-
);
|
|
926
890
|
} else {
|
|
927
|
-
if (s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex])
|
|
928
|
-
s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.remove(
|
|
929
|
-
`main-btn-menu-active`,
|
|
930
|
-
);
|
|
931
891
|
currentKeyBoardSearchBoxIndex = 0;
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// Add active class to new and ensure visibility
|
|
895
|
+
if (allItems[currentKeyBoardSearchBoxIndex]) {
|
|
896
|
+
allItems[currentKeyBoardSearchBoxIndex].classList.add('active-search-result');
|
|
897
|
+
// Use optimized scroll method to ensure item is always visible
|
|
898
|
+
const container = s(`.html-${searchBoxHistoryId}`);
|
|
899
|
+
if (container) {
|
|
900
|
+
SearchBox.scrollIntoViewIfNeeded(allItems[currentKeyBoardSearchBoxIndex], container);
|
|
901
|
+
}
|
|
936
902
|
}
|
|
937
903
|
updateSearchBoxValue();
|
|
938
904
|
}
|