underpost 2.8.852 → 2.8.854
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/.env.development +1 -1
- package/.env.production +1 -1
- package/.env.test +1 -1
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -1
- package/.github/workflows/release.cd.yml +35 -0
- package/README.md +6 -65
- package/bin/db.js +1 -3
- package/bin/deploy.js +12 -349
- package/bin/file.js +2 -0
- package/cli.md +35 -18
- package/manifests/deployment/{dd-template-development → dd-default-development}/deployment.yaml +16 -16
- package/manifests/deployment/{dd-template-development → dd-default-development}/proxy.yaml +3 -3
- package/manifests/grafana/deployment.yaml +57 -0
- package/manifests/grafana/kustomization.yaml +7 -0
- package/manifests/grafana/pvc.yaml +12 -0
- package/manifests/grafana/service.yaml +14 -0
- package/manifests/maas/ssh-cluster-info.sh +14 -0
- package/manifests/prometheus/deployment.yaml +82 -0
- package/package.json +1 -2
- package/src/cli/cluster.js +41 -2
- package/src/cli/cron.js +3 -31
- package/src/cli/db.js +124 -0
- package/src/cli/deploy.js +6 -77
- package/src/cli/index.js +17 -6
- package/src/cli/run.js +18 -0
- package/src/client/Default.index.js +0 -2
- package/src/client/components/core/Account.js +1 -1
- package/src/client/components/core/Modal.js +2 -2
- package/src/client/components/core/Recover.js +4 -3
- package/src/client/components/core/Scroll.js +65 -120
- package/src/index.js +1 -1
- package/src/server/conf.js +1 -272
- package/src/server/proxy.js +1 -2
- package/docker-compose.yml +0 -67
- package/prometheus.yml +0 -36
package/src/cli/db.js
CHANGED
|
@@ -3,6 +3,7 @@ import { loggerFactory } from '../server/logger.js';
|
|
|
3
3
|
import { shellExec } from '../server/process.js';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
import UnderpostDeploy from './deploy.js';
|
|
6
|
+
import UnderpostCron from './cron.js';
|
|
6
7
|
|
|
7
8
|
const logger = loggerFactory(import.meta);
|
|
8
9
|
|
|
@@ -216,6 +217,129 @@ class UnderpostDB {
|
|
|
216
217
|
}
|
|
217
218
|
}
|
|
218
219
|
},
|
|
220
|
+
async updateDashboardData(
|
|
221
|
+
deployId = process.env.DEFAULT_DEPLOY_ID,
|
|
222
|
+
host = process.env.DEFAULT_DEPLOY_HOST,
|
|
223
|
+
path = process.env.DEFAULT_DEPLOY_PATH,
|
|
224
|
+
) {
|
|
225
|
+
try {
|
|
226
|
+
deployId = deployId ?? process.env.DEFAULT_DEPLOY_ID;
|
|
227
|
+
host = host ?? process.env.DEFAULT_DEPLOY_HOST;
|
|
228
|
+
path = path ?? process.env.DEFAULT_DEPLOY_PATH;
|
|
229
|
+
|
|
230
|
+
const { db } = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'))[host][
|
|
231
|
+
path
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
await DataBaseProvider.load({ apis: ['instance'], host, path, db });
|
|
235
|
+
|
|
236
|
+
/** @type {import('../api/instance/instance.model.js').InstanceModel} */
|
|
237
|
+
const Instance = DataBaseProvider.instance[`${host}${path}`].mongoose.models.Instance;
|
|
238
|
+
|
|
239
|
+
await Instance.deleteMany();
|
|
240
|
+
|
|
241
|
+
for (const _deployId of deployList.split(',')) {
|
|
242
|
+
const deployId = _deployId.trim();
|
|
243
|
+
if (!deployId) continue;
|
|
244
|
+
const confServer = loadReplicas(
|
|
245
|
+
JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
|
|
246
|
+
'proxy',
|
|
247
|
+
);
|
|
248
|
+
const router = await UnderpostDeploy.API.routerFactory(deployId, env);
|
|
249
|
+
const pathPortAssignmentData = pathPortAssignmentFactory(router, confServer);
|
|
250
|
+
|
|
251
|
+
for (const host of Object.keys(confServer)) {
|
|
252
|
+
for (const { path, port } of pathPortAssignmentData[host]) {
|
|
253
|
+
if (!confServer[host][path]) continue;
|
|
254
|
+
|
|
255
|
+
const { client, runtime, apis } = confServer[host][path];
|
|
256
|
+
|
|
257
|
+
const body = {
|
|
258
|
+
deployId,
|
|
259
|
+
host,
|
|
260
|
+
path,
|
|
261
|
+
port,
|
|
262
|
+
client,
|
|
263
|
+
runtime,
|
|
264
|
+
apis,
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
logger.info('save', body);
|
|
268
|
+
|
|
269
|
+
await new Instance(body).save();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
|
|
275
|
+
} catch (error) {
|
|
276
|
+
logger.error(error, error.stack);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
281
|
+
const confServer = JSON.parse(fs.readFileSync(confServerPath, 'utf8'));
|
|
282
|
+
const { db } = confServer[host][path];
|
|
283
|
+
|
|
284
|
+
await DataBaseProvider.load({ apis: ['cron'], host, path, db });
|
|
285
|
+
|
|
286
|
+
/** @type {import('../api/cron/cron.model.js').CronModel} */
|
|
287
|
+
const Cron = DataBaseProvider.instance[`${host}${path}`].mongoose.models.Cron;
|
|
288
|
+
|
|
289
|
+
await Cron.deleteMany();
|
|
290
|
+
|
|
291
|
+
for (const cronInstance of UnderpostCron.NETWORK) {
|
|
292
|
+
logger.info('save', cronInstance);
|
|
293
|
+
await new Cron(cronInstance).save();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
|
|
297
|
+
} catch (error) {
|
|
298
|
+
logger.error(error, error.stack);
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
clusterMetadataBackupCallback(
|
|
302
|
+
deployId = process.env.DEFAULT_DEPLOY_ID,
|
|
303
|
+
host = process.env.DEFAULT_DEPLOY_HOST,
|
|
304
|
+
path = process.env.DEFAULT_DEPLOY_PATH,
|
|
305
|
+
options = {
|
|
306
|
+
import: false,
|
|
307
|
+
export: false,
|
|
308
|
+
instances: false,
|
|
309
|
+
crons: false,
|
|
310
|
+
},
|
|
311
|
+
) {
|
|
312
|
+
deployId = deployId ?? process.env.DEFAULT_DEPLOY_ID;
|
|
313
|
+
host = host ?? process.env.DEFAULT_DEPLOY_HOST;
|
|
314
|
+
path = path ?? process.env.DEFAULT_DEPLOY_PATH;
|
|
315
|
+
|
|
316
|
+
if (options.instances === true) {
|
|
317
|
+
const outputPath = './engine-private/instances';
|
|
318
|
+
if (fs.existsSync(outputPath)) fs.mkdirSync(outputPath, { recursive: true });
|
|
319
|
+
const collection = 'instances';
|
|
320
|
+
if (options.export === true)
|
|
321
|
+
shellExec(
|
|
322
|
+
`node bin db --export --collections ${collection} --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
|
|
323
|
+
);
|
|
324
|
+
if (options.import === true)
|
|
325
|
+
shellExec(
|
|
326
|
+
`node bin db --import --drop --preserveUUID --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
if (options.crons === true) {
|
|
330
|
+
const outputPath = './engine-private/crons';
|
|
331
|
+
if (fs.existsSync(outputPath)) fs.mkdirSync(outputPath, { recursive: true });
|
|
332
|
+
const collection = 'crons';
|
|
333
|
+
if (options.export === true)
|
|
334
|
+
shellExec(
|
|
335
|
+
`node bin db --export --collections ${collection} --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
|
|
336
|
+
);
|
|
337
|
+
if (options.import === true)
|
|
338
|
+
shellExec(
|
|
339
|
+
`node bin db --import --drop --preserveUUID --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
},
|
|
219
343
|
};
|
|
220
344
|
}
|
|
221
345
|
|
package/src/cli/deploy.js
CHANGED
|
@@ -242,12 +242,10 @@ spec:
|
|
|
242
242
|
cert: false,
|
|
243
243
|
versions: '',
|
|
244
244
|
traffic: '',
|
|
245
|
-
dashboardUpdate: false,
|
|
246
245
|
replicas: '',
|
|
247
246
|
restoreHosts: false,
|
|
248
247
|
disableUpdateDeployment: false,
|
|
249
248
|
infoTraffic: false,
|
|
250
|
-
rebuildClientsBundle: false,
|
|
251
249
|
},
|
|
252
250
|
) {
|
|
253
251
|
if (options.infoUtil === true)
|
|
@@ -311,18 +309,20 @@ Password: <Your Key>
|
|
|
311
309
|
deployId,
|
|
312
310
|
env,
|
|
313
311
|
traffic: UnderpostDeploy.API.getCurrentTraffic(deployId),
|
|
312
|
+
router: await UnderpostDeploy.API.routerFactory(deployId, env),
|
|
313
|
+
pods: await UnderpostDeploy.API.get(deployId),
|
|
314
314
|
});
|
|
315
315
|
}
|
|
316
316
|
return;
|
|
317
317
|
}
|
|
318
|
-
if (options.rebuildClientsBundle === true) await UnderpostDeploy.API.rebuildClientsBundle(deployList);
|
|
319
318
|
if (!(options.versions && typeof options.versions === 'string')) options.versions = 'blue,green';
|
|
320
319
|
if (!options.replicas) options.replicas = 1;
|
|
321
320
|
if (options.sync) UnderpostDeploy.API.sync(deployList, options);
|
|
322
321
|
if (options.buildManifest === true) await UnderpostDeploy.API.buildManifest(deployList, env, options);
|
|
323
|
-
if (options.infoRouter === true)
|
|
324
|
-
|
|
325
|
-
|
|
322
|
+
if (options.infoRouter === true) {
|
|
323
|
+
logger.info('router', await UnderpostDeploy.API.routerFactory(deployList, env));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
326
|
shellExec(`kubectl delete configmap underpost-config`);
|
|
327
327
|
shellExec(
|
|
328
328
|
`kubectl create configmap underpost-config --from-file=/home/dd/engine/engine-private/conf/dd-cron/.env.${env}`,
|
|
@@ -435,23 +435,6 @@ Password: <Your Key>
|
|
|
435
435
|
|
|
436
436
|
return result;
|
|
437
437
|
},
|
|
438
|
-
rebuildClientsBundle(deployList) {
|
|
439
|
-
for (const _deployId of deployList.split(',')) {
|
|
440
|
-
const deployId = _deployId.trim();
|
|
441
|
-
const repoName = `engine-${deployId.split('-')[1]}`;
|
|
442
|
-
|
|
443
|
-
shellExec(`underpost script set ${deployId}-client-build '
|
|
444
|
-
cd /home/dd/engine &&
|
|
445
|
-
git checkout . &&
|
|
446
|
-
underpost pull . underpostnet/${repoName} &&
|
|
447
|
-
underpost pull ./engine-private underpostnet/${repoName}-private &&
|
|
448
|
-
underpost env ${deployId} production &&
|
|
449
|
-
node bin/deploy build-full-client ${deployId}
|
|
450
|
-
'`);
|
|
451
|
-
|
|
452
|
-
shellExec(`node bin script run ${deployId}-client-build --itc --pod-name ${deployId}`);
|
|
453
|
-
}
|
|
454
|
-
},
|
|
455
438
|
resourcesFactory() {
|
|
456
439
|
return {
|
|
457
440
|
requests: {
|
|
@@ -465,60 +448,6 @@ node bin/deploy build-full-client ${deployId}
|
|
|
465
448
|
totalPods: UnderpostRootEnv.API.get('total-pods'),
|
|
466
449
|
};
|
|
467
450
|
},
|
|
468
|
-
async updateDashboardData(deployList, env, options) {
|
|
469
|
-
try {
|
|
470
|
-
const deployId = process.env.DEFAULT_DEPLOY_ID;
|
|
471
|
-
const host = process.env.DEFAULT_DEPLOY_HOST;
|
|
472
|
-
const path = process.env.DEFAULT_DEPLOY_PATH;
|
|
473
|
-
const { db } = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'))[host][
|
|
474
|
-
path
|
|
475
|
-
];
|
|
476
|
-
|
|
477
|
-
await DataBaseProvider.load({ apis: ['instance'], host, path, db });
|
|
478
|
-
|
|
479
|
-
/** @type {import('../api/instance/instance.model.js').InstanceModel} */
|
|
480
|
-
const Instance = DataBaseProvider.instance[`${host}${path}`].mongoose.models.Instance;
|
|
481
|
-
|
|
482
|
-
await Instance.deleteMany();
|
|
483
|
-
|
|
484
|
-
for (const _deployId of deployList.split(',')) {
|
|
485
|
-
const deployId = _deployId.trim();
|
|
486
|
-
if (!deployId) continue;
|
|
487
|
-
const confServer = loadReplicas(
|
|
488
|
-
JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
|
|
489
|
-
'proxy',
|
|
490
|
-
);
|
|
491
|
-
const router = await UnderpostDeploy.API.routerFactory(deployId, env);
|
|
492
|
-
const pathPortAssignmentData = pathPortAssignmentFactory(router, confServer);
|
|
493
|
-
|
|
494
|
-
for (const host of Object.keys(confServer)) {
|
|
495
|
-
for (const { path, port } of pathPortAssignmentData[host]) {
|
|
496
|
-
if (!confServer[host][path]) continue;
|
|
497
|
-
|
|
498
|
-
const { client, runtime, apis } = confServer[host][path];
|
|
499
|
-
|
|
500
|
-
const body = {
|
|
501
|
-
deployId,
|
|
502
|
-
host,
|
|
503
|
-
path,
|
|
504
|
-
port,
|
|
505
|
-
client,
|
|
506
|
-
runtime,
|
|
507
|
-
apis,
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
logger.info('save', body);
|
|
511
|
-
|
|
512
|
-
await new Instance(body).save();
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
|
|
518
|
-
} catch (error) {
|
|
519
|
-
logger.error(error, error.stack);
|
|
520
|
-
}
|
|
521
|
-
},
|
|
522
451
|
existsContainerFile({ podName, path }) {
|
|
523
452
|
return JSON.parse(
|
|
524
453
|
shellExec(`kubectl exec ${podName} -- test -f ${path} && echo "true" || echo "false"`, {
|
package/src/cli/index.js
CHANGED
|
@@ -123,6 +123,11 @@ program
|
|
|
123
123
|
.option('--full', 'Initializes the cluster with all available statefulsets and services.')
|
|
124
124
|
.option('--ns-use <ns-name>', 'Switches the current Kubernetes context to the specified namespace.')
|
|
125
125
|
.option('--kubeadm', 'Initializes the cluster using kubeadm for control plane management.')
|
|
126
|
+
.option('--grafana', 'Initializes the cluster with a Grafana deployment.')
|
|
127
|
+
.option(
|
|
128
|
+
'--prom [hosts]',
|
|
129
|
+
'Initializes the cluster with a Prometheus Operator deployment and monitor scrap for specified hosts.',
|
|
130
|
+
)
|
|
126
131
|
.option('--dev', 'Initializes a development-specific cluster configuration.')
|
|
127
132
|
.option('--list-pods', 'Displays detailed information about all pods.')
|
|
128
133
|
.option('--info-capacity', 'Displays the current total machine capacity information.')
|
|
@@ -155,7 +160,6 @@ program
|
|
|
155
160
|
'--build-manifest',
|
|
156
161
|
'Builds Kubernetes YAML manifests, including deployments, services, proxies, and secrets.',
|
|
157
162
|
)
|
|
158
|
-
.option('--dashboard-update', 'Updates dashboard instance data with the current router configuration.')
|
|
159
163
|
.option('--replicas <replicas>', 'Sets a custom number of replicas for deployments.')
|
|
160
164
|
.option('--versions <deployment-versions>', 'A comma-separated list of custom deployment versions.')
|
|
161
165
|
.option('--traffic <traffic-versions>', 'A comma-separated list of custom deployment traffic weights.')
|
|
@@ -163,10 +167,6 @@ program
|
|
|
163
167
|
.option('--info-traffic', 'Retrieves traffic configuration from current resource deployments.')
|
|
164
168
|
.option('--kubeadm', 'Enables the kubeadm context for deployment operations.')
|
|
165
169
|
.option('--restore-hosts', 'Restores default `/etc/hosts` entries.')
|
|
166
|
-
.option(
|
|
167
|
-
'--rebuild-clients-bundle',
|
|
168
|
-
'Inside the container, rebuilds client bundles (only static public or storage client files).',
|
|
169
|
-
)
|
|
170
170
|
.description('Manages application deployments, defaulting to deploying development pods.')
|
|
171
171
|
.action(Underpost.deploy.callback);
|
|
172
172
|
|
|
@@ -240,6 +240,18 @@ program
|
|
|
240
240
|
.description('Manages database operations, including import, export, and collection management.')
|
|
241
241
|
.action(Underpost.db.callback);
|
|
242
242
|
|
|
243
|
+
program
|
|
244
|
+
.command('metadata')
|
|
245
|
+
.argument('[deploy-id]', 'The deployment ID to manage metadata.')
|
|
246
|
+
.argument('[host]', 'The host to manage metadata.')
|
|
247
|
+
.argument('[path]', 'The path to manage metadata.')
|
|
248
|
+
.option('--import', 'Imports from local storage.')
|
|
249
|
+
.option('--export', 'Exports to local storage.')
|
|
250
|
+
.option('--crons', 'Apply to cron data collection')
|
|
251
|
+
.option('--instances', 'Apply to instance data collection')
|
|
252
|
+
.description('Manages cluster metadata operations, including import and export.')
|
|
253
|
+
.action(Underpost.db.clusterMetadataBackupCallback);
|
|
254
|
+
|
|
243
255
|
// 'script' command: Execute scripts
|
|
244
256
|
program
|
|
245
257
|
.command('script')
|
|
@@ -268,7 +280,6 @@ program
|
|
|
268
280
|
.option('--itc', 'Executes cron jobs within the container execution context.')
|
|
269
281
|
.option('--init', 'Initializes cron jobs for the default deployment ID.')
|
|
270
282
|
.option('--git', 'Uploads cron job configurations to GitHub.')
|
|
271
|
-
.option('--dashboard-update', 'Updates dashboard cron data with the current job configurations.')
|
|
272
283
|
.description('Manages cron jobs, including initialization, execution, and configuration updates.')
|
|
273
284
|
.action(Underpost.cron.callback);
|
|
274
285
|
|
package/src/cli/run.js
CHANGED
|
@@ -76,6 +76,11 @@ class UnderpostRun {
|
|
|
76
76
|
shellExec(`${baseCommand} deploy --expose mongo`, { async: true });
|
|
77
77
|
shellExec(`${baseCommand} deploy --expose valkey`, { async: true });
|
|
78
78
|
},
|
|
79
|
+
'ssh-cluster-info': (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
80
|
+
const { underpostRoot } = options;
|
|
81
|
+
shellExec(`chmod +x ${underpostRoot}/manifests/maas/ssh-cluster-info.sh`);
|
|
82
|
+
shellExec(`${underpostRoot}/manifests/maas/ssh-cluster-info.sh`);
|
|
83
|
+
},
|
|
79
84
|
'cyberia-ide': (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
80
85
|
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
81
86
|
shellExec(`${baseCommand} run ide /home/dd/cyberia-server`);
|
|
@@ -93,6 +98,19 @@ class UnderpostRun {
|
|
|
93
98
|
shellExec(`${baseCommand} cmt . --empty ci package-pwa-microservices-template`);
|
|
94
99
|
shellExec(`${baseCommand} push . underpostnet/engine`);
|
|
95
100
|
},
|
|
101
|
+
clean: (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
102
|
+
shellCd(path ?? `/home/dd/engine`);
|
|
103
|
+
shellExec(`node bin/deploy clean-core-repo`);
|
|
104
|
+
},
|
|
105
|
+
upgrade: (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
106
|
+
const { underpostRoot } = options;
|
|
107
|
+
shellExec(`npm install -g underpost`);
|
|
108
|
+
shellExec(`underpost run secret`);
|
|
109
|
+
shellCd(`/home/dd/engine`);
|
|
110
|
+
shellExec(`node bin/deploy clean-core-repo`);
|
|
111
|
+
shellExec(`underpost pull . underpostnet/engine`);
|
|
112
|
+
shellExec(`underpost pull engine-private underpostnet/engine-private`, { silent: true });
|
|
113
|
+
},
|
|
96
114
|
'ssh-deploy': (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
97
115
|
const baseCommand = options.dev || true ? 'node bin' : 'underpost';
|
|
98
116
|
shellCd('/home/dd/engine');
|
|
@@ -15,7 +15,6 @@ import { DefaultParams } from './components/default/CommonDefault.js';
|
|
|
15
15
|
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
|
-
import { Scroll } from './components/core/Scroll.js';
|
|
19
18
|
import { CssDefaultDark, CssDefaultLight } from './components/default/CssDefault.js';
|
|
20
19
|
|
|
21
20
|
const htmlMainBody = async () => {
|
|
@@ -39,7 +38,6 @@ window.onload = () =>
|
|
|
39
38
|
await LogInDefault();
|
|
40
39
|
await LogOutDefault();
|
|
41
40
|
await SignUpDefault();
|
|
42
|
-
await Scroll.pullTopRefresh();
|
|
43
41
|
await Keyboard.Init({ callBackTime: DefaultParams.EVENT_CALLBACK_TIME });
|
|
44
42
|
},
|
|
45
43
|
});
|
|
@@ -10,7 +10,7 @@ import { Modal } from './Modal.js';
|
|
|
10
10
|
import { NotificationManager } from './NotificationManager.js';
|
|
11
11
|
import { Translate } from './Translate.js';
|
|
12
12
|
import { Validator } from './Validator.js';
|
|
13
|
-
import { append, htmls, s } from './VanillaJs.js';
|
|
13
|
+
import { append, getProxyPath, htmls, s } from './VanillaJs.js';
|
|
14
14
|
|
|
15
15
|
const Account = {
|
|
16
16
|
UpdateEvent: {},
|
|
@@ -502,7 +502,7 @@ const Modal = {
|
|
|
502
502
|
class="abs modal slide-menu-top-bar-fix"
|
|
503
503
|
style="height: ${options.heightTopBar}px; top: 0px"
|
|
504
504
|
>
|
|
505
|
-
<a class="a-link-top-banner">
|
|
505
|
+
<a class="a-link-top-banner fl">
|
|
506
506
|
<div class="inl">${await options.slideMenuTopBarBannerFix()}</div></a
|
|
507
507
|
>
|
|
508
508
|
</div>`
|
|
@@ -1160,7 +1160,7 @@ const Modal = {
|
|
|
1160
1160
|
if (s(`.slide-menu-top-bar-fix`)) {
|
|
1161
1161
|
htmls(
|
|
1162
1162
|
`.slide-menu-top-bar-fix`,
|
|
1163
|
-
html`<a class="a-link-top-banner">${await options.slideMenuTopBarBannerFix()}</a>`,
|
|
1163
|
+
html`<a class="a-link-top-banner fl">${await options.slideMenuTopBarBannerFix()}</a>`,
|
|
1164
1164
|
);
|
|
1165
1165
|
Modal.setTopBannerLink();
|
|
1166
1166
|
}
|
|
@@ -80,9 +80,10 @@ const Recover = {
|
|
|
80
80
|
}
|
|
81
81
|
switch (mode) {
|
|
82
82
|
case 'recover-verify-email': {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
const result = await UserService.post({
|
|
84
|
+
id: 'recover-verify-email',
|
|
85
|
+
body: { ...body, proxyPath: getProxyPath(), hostname: `${location.hostname}` },
|
|
86
|
+
});
|
|
86
87
|
NotificationManager.Push({
|
|
87
88
|
html:
|
|
88
89
|
result.status === 'error' ? result.message : Translate.Render(`${result.status}-recover-verify-email`),
|
|
@@ -1,131 +1,76 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Modal } from './Modal.js';
|
|
3
|
-
import { append, s } from './VanillaJs.js';
|
|
1
|
+
import { s } from './VanillaJs.js';
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
class Scroll {
|
|
4
|
+
/**
|
|
5
|
+
* Attach scroll listener to an element (resolved with s(selector)).
|
|
6
|
+
* @param {string} selector - selector passed to s(selector)
|
|
7
|
+
* @param {function} [callback] - callback function to be called on scroll
|
|
8
|
+
* @param {object} options
|
|
9
|
+
* @param {number} [options.threshold=1] - px margin to treat as bottom
|
|
10
|
+
* @param {number} [options.precision=3] - decimal places for percentages
|
|
11
|
+
*/
|
|
12
|
+
static setEvent(selector, callback = async () => {}, options = { threshold: 1, precision: 3 }) {
|
|
13
|
+
const el = s(selector);
|
|
14
|
+
if (!el) return;
|
|
15
|
+
|
|
16
|
+
const threshold = options.threshold ?? 1; // px tolerance for bottom detection
|
|
17
|
+
const precision = options.precision ?? 3;
|
|
18
|
+
let ticking = false;
|
|
19
|
+
|
|
20
|
+
const round = (v) => {
|
|
21
|
+
const m = Math.pow(10, precision);
|
|
22
|
+
return Math.round(v * m) / m;
|
|
11
23
|
};
|
|
12
|
-
return Scroll.data[selector];
|
|
13
|
-
},
|
|
14
|
-
getScrollPosition: function (selector) {
|
|
15
|
-
// Scroll.data[selector].element.clientHeight -
|
|
16
|
-
return Scroll.data[selector].element.scrollTop;
|
|
17
|
-
},
|
|
18
|
-
scrollHandler: async function () {
|
|
19
|
-
for (const selector in Scroll.data) await Scroll.data[selector].callback(Scroll.getScrollPosition(selector));
|
|
20
|
-
},
|
|
21
|
-
addEvent: function (selector = '', callback = (position = 0) => {}) {
|
|
22
|
-
Scroll.data[selector].callback = callback;
|
|
23
|
-
},
|
|
24
|
-
removeEvent: function (selector) {
|
|
25
|
-
delete Scroll.data[selector];
|
|
26
|
-
},
|
|
27
|
-
to: function (elector = '', options = { top: 100, left: 100, behavior: 'smooth' }) {
|
|
28
|
-
Scroll.data[selector].element.scrollTo({
|
|
29
|
-
top: options.top || Scroll.getScrollPosition(selector),
|
|
30
|
-
left: options.left || 0,
|
|
31
|
-
behavior: options.behavior || 'smooth',
|
|
32
|
-
});
|
|
33
|
-
},
|
|
34
|
-
topRefreshEvents: {},
|
|
35
|
-
addTopRefreshEvent: function (options = { id: '', callback: () => {}, condition: () => {} }) {
|
|
36
|
-
this.topRefreshEvents[options.id] = options;
|
|
37
|
-
},
|
|
38
|
-
removeTopRefreshEvent: function (id = '') {
|
|
39
|
-
delete this.topRefreshEvents[id];
|
|
40
|
-
},
|
|
41
|
-
pullTopRefresh: function () {
|
|
42
|
-
return;
|
|
43
|
-
append(
|
|
44
|
-
'body',
|
|
45
|
-
html` <style>
|
|
46
|
-
.pull-refresh-icon-container {
|
|
47
|
-
height: 60px;
|
|
48
|
-
width: 100%;
|
|
49
|
-
z-index: 10;
|
|
50
|
-
transition: 0.3s;
|
|
51
|
-
left: 0px;
|
|
52
|
-
}
|
|
53
|
-
.pull-refresh-icon {
|
|
54
|
-
width: 60px;
|
|
55
|
-
height: 60px;
|
|
56
|
-
margin: auto;
|
|
57
|
-
color: white;
|
|
58
|
-
font-size: 30px;
|
|
59
|
-
}
|
|
60
|
-
</style>
|
|
61
|
-
${borderChar(2, 'black', [' .pull-refresh-icon-container'])}
|
|
62
|
-
<div style="top: -60px" class="abs pull-refresh-icon-container">
|
|
63
|
-
<div class="in pull-refresh-icon">
|
|
64
|
-
<div class="abs center"><i class="fa-solid fa-arrows-rotate"></i></div>
|
|
65
|
-
</div>
|
|
66
|
-
</div>`,
|
|
67
|
-
);
|
|
68
24
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
25
|
+
const listener = (event) => {
|
|
26
|
+
if (ticking) return;
|
|
27
|
+
ticking = true;
|
|
28
|
+
|
|
29
|
+
requestAnimationFrame(() => {
|
|
30
|
+
const scrollHeight = el.scrollHeight;
|
|
31
|
+
const clientHeight = el.clientHeight;
|
|
32
|
+
const scrollTop = el.scrollTop;
|
|
33
|
+
|
|
34
|
+
// pixels left to scroll (clamped to >= 0)
|
|
35
|
+
const remaining = Math.max(0, scrollHeight - clientHeight - scrollTop);
|
|
73
36
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// console.warn('touchstart', touchstartY);
|
|
77
|
-
});
|
|
37
|
+
// maximum possible remaining (0 if content fits without scrolling)
|
|
38
|
+
const maxRemaining = Math.max(0, scrollHeight - clientHeight);
|
|
78
39
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
(!s(`.btn-bar-center-icon-close`).classList.contains('hide') &&
|
|
83
|
-
!s(
|
|
84
|
-
`.btn-icon-menu-mode-${Modal.Data['modal-menu'].options.mode !== 'slide-menu-right' ? 'left' : 'right'}`,
|
|
85
|
-
).classList.contains('hide'))
|
|
86
|
-
)
|
|
87
|
-
return;
|
|
40
|
+
// percentRemaining: 1 = top (all remaining), 0 = bottom (none remaining)
|
|
41
|
+
let percentRemaining = maxRemaining === 0 ? 0 : remaining / maxRemaining;
|
|
42
|
+
percentRemaining = Math.max(0, Math.min(1, percentRemaining));
|
|
88
43
|
|
|
89
|
-
|
|
90
|
-
|
|
44
|
+
// percentScrolled: complementary value (0 = top, 1 = bottom)
|
|
45
|
+
let percentScrolled = 1 - percentRemaining;
|
|
46
|
+
percentScrolled = Math.max(0, Math.min(1, percentScrolled));
|
|
91
47
|
|
|
92
|
-
|
|
48
|
+
const payload = {
|
|
49
|
+
scrollHeight,
|
|
50
|
+
clientHeight,
|
|
51
|
+
scrollTop,
|
|
52
|
+
remaining, // px left (>= 0)
|
|
53
|
+
scrollBottom: remaining <= threshold ? 0 : remaining,
|
|
54
|
+
atBottom: remaining <= threshold,
|
|
55
|
+
percentRemaining: round(percentRemaining), // 0..1
|
|
56
|
+
percentScrolled: round(percentScrolled), // 0..1
|
|
57
|
+
};
|
|
93
58
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
59
|
+
// replace this with an event dispatch or callback if you prefer
|
|
60
|
+
// console.warn('scroll', event, JSON.stringify(payload, null, 2));
|
|
61
|
+
callback(payload);
|
|
97
62
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (Scroll.topRefreshEvents[event].condition()) Scroll.topRefreshEvents[event].callback();
|
|
110
|
-
}
|
|
111
|
-
reload = false;
|
|
112
|
-
});
|
|
113
|
-
Scroll.addTopRefreshEvent({
|
|
114
|
-
id: 'main-body',
|
|
115
|
-
callback: () => {
|
|
116
|
-
location.reload();
|
|
117
|
-
},
|
|
118
|
-
condition: () => {
|
|
119
|
-
return (
|
|
120
|
-
s('.main-body') &&
|
|
121
|
-
s('.main-body').scrollTop === 0 &&
|
|
122
|
-
!Object.keys(Modal.Data).find(
|
|
123
|
-
(idModal) => !['modal-menu', 'main-body', 'bottom-bar', 'main-body-top'].includes(idModal),
|
|
124
|
-
)
|
|
125
|
-
);
|
|
126
|
-
},
|
|
127
|
-
});
|
|
128
|
-
},
|
|
129
|
-
};
|
|
63
|
+
ticking = false;
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
el.addEventListener('scroll', listener, { passive: true });
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
removeEvent: () => el.removeEventListener('scroll', listener),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
130
74
|
|
|
131
75
|
export { Scroll };
|
|
76
|
+
export default Scroll;
|