Ns/simplify 2 (#2790)

* Remove mostly unused TechnicalError and featureflag. Fold Group into GroupService.

* move modutil to util

* remove unused public/assets

* Remove anomaly service, fold logic into groups

* remove unused modules

* Remove AnomalyService import from tests

* remove shutdownservice

* remove unused filetracker service

* [will break prod] remove Puter AI Module, inline to coremodue

* MariaDB compatibility
This commit is contained in:
ProgrammerIn-wonderland
2026-04-15 12:03:51 -04:00
committed by GitHub
parent edf1736292
commit 389e11f33b
66 changed files with 194 additions and 4872 deletions
Submodule
+1
Submodule package added at 27901553e9
-6
View File
@@ -19,14 +19,11 @@
import CoreModule from './src/CoreModule.js';
import DatabaseModule from './src/DatabaseModule.js';
import { Kernel } from './src/Kernel.js';
import { PuterAIModule } from './src/modules/ai/PuterAIChatModule.js';
import { AppsModule } from './src/modules/apps/AppsModule.js';
import { BroadcastModule } from './src/modules/broadcast/BroadcastModule.js';
import { CaptchaModule } from './src/modules/captcha/CaptchaModule.js';
import { Core2Module } from './src/modules/core/Core2Module.js';
import { DataAccessModule } from './src/modules/data-access/DataAccessModule.js';
import { DNSModule } from './src/modules/dns/DNSModule.js';
import { DomainModule } from './src/modules/domain/DomainModule.js';
import { EntityStoreModule } from './src/modules/entitystore/EntityStoreModule.js';
import { HostOSModule } from './src/modules/hostos/HostOSModule.js';
import { InternetModule } from './src/modules/internet/InternetModule.js';
@@ -68,11 +65,8 @@ export default {
SelfHostedModule,
TestDriversModule,
TestConfigModule,
PuterAIModule,
BroadcastModule,
InternetModule,
CaptchaModule,
KVStoreModule,
DNSModule,
DomainModule,
};
+38 -11
View File
@@ -39,6 +39,10 @@ const { PuterS3Service } = require('./deprecated/filesystem/PuterS3Service');
*/
const install = async ({ context, services, app, useapi, modapi }) => {
const config = require('./config');
const registerServiceIfMissing = (name, service, options) => {
if ( services.has(name) ) return;
services.registerService(name, service, options);
};
const { TelemetryService } = require('./modules/perfmon/TelemetryService');
if ( ! services.has('telemetry') ) {
services.registerService('telemetry', TelemetryService);
@@ -315,21 +319,12 @@ const install = async ({ context, services, app, useapi, modapi }) => {
const { PermissionAPIService } = require('./services/PermissionAPIService');
services.registerService('__permission-api', PermissionAPIService);
const { AnomalyService } = require('./services/AnomalyService');
services.registerService('anomaly', AnomalyService);
const { HelloWorldService } = require('./services/HelloWorldService');
services.registerService('hello-world', HelloWorldService);
const { SystemDataService } = require('./services/SystemDataService');
services.registerService('system-data', SystemDataService);
const { SUService } = require('./services/SUService');
services.registerService('su', SUService);
const { ShutdownService } = require('./services/ShutdownService');
services.registerService('shutdown', ShutdownService);
const { BootScriptService } = require('./services/BootScriptService');
services.registerService('boot-script', BootScriptService);
@@ -356,8 +351,7 @@ const install = async ({ context, services, app, useapi, modapi }) => {
const { WispService } = require('./services/WispService');
services.registerService('wisp', WispService);
// const { AWSSecretsPopulator } = require('./services/AWSSecretsPopulator.js');
// services.registerService('awsthing', AWSSecretsPopulator);
const { WebDavFS } = require('./services/WebDAV/WebDAVService.js');
services.registerService('dav', WebDavFS);
@@ -382,6 +376,39 @@ const install = async ({ context, services, app, useapi, modapi }) => {
const { PeerService } = require('./services/PeerService');
services.registerService('peer', PeerService);
const { AIInterfaceService } = await import('./services/ai/AIInterfaceService.js');
const { AIChatService } = await import('./services/ai/chat/AIChatService.js');
const { AIImageGenerationService } = await import('./services/ai/image/AIImageGenerationService.js');
const { AIVideoGenerationService } = await import('./services/ai/video/AIVideoGenerationService.js');
registerServiceIfMissing('__ai-interfaces', AIInterfaceService);
registerServiceIfMissing('ai-chat', AIChatService);
registerServiceIfMissing('ai-image', AIImageGenerationService);
registerServiceIfMissing('ai-video', AIVideoGenerationService);
if ( config?.services?.['aws-textract']?.aws ) {
const { AWSTextractService } = await import('./services/ai/ocr/AWSTextractService.js');
registerServiceIfMissing('aws-textract', AWSTextractService);
}
if ( config?.services?.['aws-polly']?.aws ) {
const { AWSPollyService } = await import('./services/ai/tts/AWSPollyService.js');
registerServiceIfMissing('aws-polly', AWSPollyService);
}
if ( config?.services?.['elevenlabs'] || config?.elevenlabs ) {
const { ElevenLabsTTSService } = await import('./services/ai/tts/ElevenLabsTTSService.js');
const { ElevenLabsVoiceChangerService } = await import('./services/ai/sts/ElevenLabsVoiceChangerService.js');
registerServiceIfMissing('elevenlabs-tts', ElevenLabsTTSService);
registerServiceIfMissing('elevenlabs-voice-changer', ElevenLabsVoiceChangerService);
}
if ( config?.services?.openai || config?.openai ) {
const { OpenAITTSService } = await import('./services/ai/tts/OpenAITTSService.js');
const { OpenAISpeechToTextService } = await import('./services/ai/stt/OpenAISpeechToTextService.js');
registerServiceIfMissing('openai-tts', OpenAITTSService);
registerServiceIfMissing('openai-speech2txt', OpenAISpeechToTextService);
}
// === Services which are deprecated and should at most be maintained for legacy support ===
services.registerService('puter-s3', PuterS3Service);
+3 -3
View File
@@ -27,7 +27,7 @@ const { ExtensionModule } = require('./ExtensionModule');
const { spawn } = require('node:child_process');
const fs = require('fs');
const path_ = require('path');
const { prependToJSFiles } = require('./kernel/modutil');
const { prependToJSFiles } = require('./util/modutil');
const { tmp_provide_services } = require('./helpers');
const uuid = require('uuid');
const readline = require('node:readline/promises');
@@ -255,9 +255,9 @@ class Kernel extends AdvancedBase {
for ( const mods_dirpath of mod_paths ) {
const p = (async () => {
if ( ! fs.existsSync(mods_dirpath) ) {
this.services.logger.error(`mod directory not found: ${quot(mods_dirpath)}; skipping...`);
console.error(`mod directory not found: ${quot(mods_dirpath)}; skipping...`);
// intentional delay so error is seen
this.services.logger.info('boot will continue in 4 seconds');
console.info('boot will continue in 4 seconds');
await new Promise(rslv => setTimeout(rslv, 4000));
return;
}
+1 -2
View File
@@ -18,7 +18,6 @@
*/
const { AdvancedBase } = require('@heyputer/putility');
const { quot } = require('@heyputer/putility').libs.string;
const { TechnicalError } = require('../errors/TechnicalError');
const { print_error_help } = require('../errors/error_help_details');
const default_config = require('./default_config');
const config = require('../config');
@@ -364,7 +363,7 @@ class RuntimeEnvironment extends AdvancedBase {
}
if ( meta.optional ) return;
throw new TechnicalError(`No suitable path found for ${meta.pathFor}.`);
throw new Error(`No suitable path found for ${meta.pathFor}.`);
}
}
-43
View File
@@ -1,43 +0,0 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const SimpleEntity = require('../definitions/SimpleEntity');
module.exports = SimpleEntity({
name: 'group',
fetchers: {
async members () {
const svc_group = this.services.get('group');
const members = await svc_group.list_members({ uid: this.values.uid });
return members;
},
},
methods: {
async get_client_value (options = {}) {
if ( options.members ) {
await this.fetch_members();
}
const group = {
uid: this.values.uid,
metadata: this.values.metadata,
...(options.members ? { members: this.values.members } : {}),
};
return group;
},
},
});
-45
View File
@@ -1,45 +0,0 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* @class TechnicalError
* @extends Error
*
* This error type is used for errors that may be presented in a
* technical context, such as a terminal or log file.
*
* @todo This could be a trait errors can have rather than a class.
*/
class TechnicalError extends Error {
constructor (message, ...details) {
super(message);
for ( const detail of details ) {
detail(this);
}
}
}
const ERR_HINT_NOSTACK = e => {
e.toString = () => e.message;
};
module.exports = {
TechnicalError,
ERR_HINT_NOSTACK,
};
-41
View File
@@ -1,41 +0,0 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require('../api/APIError');
const { Context } = require('../util/context');
const featureflag = options => async (req, res, next) => {
const { feature } = options;
const context = Context.get();
const services = context.get('services');
const svc_featureFlag = services.get('feature-flag');
if ( ! await svc_featureFlag.check({
actor: req.actor,
}, feature) ) {
const e = APIError.create('forbidden');
e.write(res);
return;
}
next();
};
module.exports = featureflag;
@@ -1,86 +0,0 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { AdvancedBase } from '@heyputer/putility';
import config from '../../config.js';
import { AIInterfaceService } from '../../services/ai/AIInterfaceService.js';
import { AIChatService } from '../../services/ai/chat/AIChatService.js';
import { AIImageGenerationService } from '../../services/ai/image/AIImageGenerationService.js';
import { AWSTextractService } from '../../services/ai/ocr/AWSTextractService.js';
import { ElevenLabsVoiceChangerService } from '../../services/ai/sts/ElevenLabsVoiceChangerService.js';
import { OpenAISpeechToTextService } from '../../services/ai/stt/OpenAISpeechToTextService.js';
import { AWSPollyService } from '../../services/ai/tts/AWSPollyService.js';
import { ElevenLabsTTSService } from '../../services/ai/tts/ElevenLabsTTSService.js';
import { OpenAITTSService } from '../../services/ai/tts/OpenAITTSService.js';
import { AIVideoGenerationService } from '../../services/ai/video/AIVideoGenerationService.js';
/**
* PuterAIModule class extends AdvancedBase to manage and register various AI services.
* This module handles the initialization and registration of multiple AI-related services
* including text processing, speech synthesis, chat completion, and image generation.
* Services are conditionally registered based on configuration settings, allowing for
* flexible deployment with different AI providers like AWS, OpenAI, Claude, Together AI,
* Mistral, Groq, and XAI.
* @extends AdvancedBase
*/
export class PuterAIModule extends AdvancedBase {
/**
* Module for managing AI-related services in the Puter platform
* Extends AdvancedBase to provide core functionality
* Handles registration and configuration of various AI services like OpenAI, Claude, AWS services etc.
*/
async install (context) {
const services = context.get('services');
services.registerService('__ai-interfaces', AIInterfaceService);
// completion ai service
services.registerService('ai-chat', AIChatService);
// image generation ai service
services.registerService('ai-image', AIImageGenerationService);
// video generation ai service
services.registerService('ai-video', AIVideoGenerationService);
// TODO DS: centralize other service types too
// TODO: services should govern their own availability instead of the module deciding what to register
if ( config?.services?.['aws-textract']?.aws ) {
services.registerService('aws-textract', AWSTextractService);
}
if ( config?.services?.['aws-polly']?.aws ) {
services.registerService('aws-polly', AWSPollyService);
}
if ( config?.services?.['elevenlabs'] || config?.elevenlabs ) {
services.registerService('elevenlabs-tts', ElevenLabsTTSService);
services.registerService('elevenlabs-voice-changer', ElevenLabsVoiceChangerService);
}
if ( config?.services?.openai || config?.openai ) {
services.registerService('openai-tts', OpenAITTSService);
services.registerService('openai-speech2txt', OpenAISpeechToTextService);
}
}
}
+2 -5
View File
@@ -285,11 +285,8 @@ class AlarmService extends BaseService {
const args = this.Context.get('args') ?? {};
if ( args['quit-on-alarm'] ) {
const svc_shutdown = this.services.get('shutdown');
svc_shutdown.shutdown({
reason: '--quit-on-alarm is set',
code: 1,
});
console.log('shutting down: --quit-on-alarm is set');
process.exit(1);
}
if ( alarm.no_alert ) return;
@@ -1,39 +0,0 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { AdvancedBase } = require('@heyputer/putility');
/**
* Enable this module when you want performance monitoring.
*
* Performance monitoring requires additional setup. Jaegar should be installed
* and running.
*/
class DevelopmentModule extends AdvancedBase {
async install (context) {
const services = context.get('services');
const LocalTerminalService = require('./LocalTerminalService');
services.registerService('local-terminal', LocalTerminalService);
}
}
module.exports = {
DevelopmentModule,
};
@@ -1,173 +0,0 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { spawn } = require('child_process');
const APIError = require('../../api/APIError');
const eggspress = require('../../api/eggspress');
const configurable_auth = require('../../middleware/configurable_auth');
const PERM_LOCAL_TERMINAL = 'local-terminal:access';
const path_ = require('path');
const { Actor } = require('../../services/auth/Actor');
const BaseService = require('../../services/BaseService');
const { Context } = require('../../util/context');
class LocalTerminalService extends BaseService {
_construct () {
this.sessions_ = {};
}
get_profiles () {
return {
'api-test': {
cwd: path_.join(
__dirname,
'../../../../../',
'tools/api-tester',
),
shell: [
'/usr/bin/env', 'node',
'apitest.js',
'--config=config.yml',
],
allow_args: true,
},
};
};
'__on_install.routes' (_, { app }) {
const r_group = (() => {
const require = this.require;
const express = require('express');
return express.Router();
})();
app.use('/local-terminal', r_group);
r_group.use(eggspress('/new', {
allowedMethods: ['POST'],
mw: [configurable_auth()],
}, async (req, res) => {
const term_uuid = require('uuid').v4();
const svc_permission = this.services.get('permission');
const actor = Context.get('actor');
const can_access = actor &&
await svc_permission.check(actor, PERM_LOCAL_TERMINAL);
if ( ! can_access ) {
throw APIError.create('permission_denied', null, {
permission: PERM_LOCAL_TERMINAL,
});
}
const profiles = this.get_profiles();
if ( ! profiles[req.body.profile] ) {
throw APIError.create('invalid_profile', null, {
profile: req.body.profile,
});
}
const profile = profiles[req.body.profile];
const args = profile.shell.slice(1);
if ( profile.allow_args && req.body.args ) {
args.push(...req.body.args);
}
const proc = spawn(profile.shell[0], args, {
shell: true,
env: {
...process.env,
...(profile.env ?? {}),
},
cwd: profile.cwd,
});
// stdout to websocket
{
const svc_socketio = req.services.get('socketio');
proc.stdout.on('data', data => {
const base64 = data.toString('base64');
console.debug('---------------------- CHUNK?', base64);
svc_socketio.send(
{ room: req.user.id },
'local-terminal.stdout',
{ term_uuid, base64 },
);
});
proc.stderr.on('data', data => {
const base64 = data.toString('base64');
console.debug('---------------------- CHUNK?', base64);
svc_socketio.send(
{ room: req.user.id },
'local-terminal.stderr',
{ term_uuid, base64 },
);
});
}
proc.on('exit', () => {
this.log.noticeme(`[${term_uuid}] Process exited (${proc.exitCode})`);
delete this.sessions_[term_uuid];
const svc_socketio = req.services.get('socketio');
svc_socketio.send(
{ room: req.user.id },
'local-terminal.exit',
{ term_uuid },
);
});
this.sessions_[term_uuid] = {
uuid: term_uuid,
proc,
};
res.json({ term_uuid });
}));
}
async _init () {
const svc_event = this.services.get('event');
svc_event.on('web.socket.user-connected', async (_, {
socket,
user,
}) => {
const svc_permission = this.services.get('permission');
const actor = Actor.adapt(user);
const can_access = actor &&
await svc_permission.check(actor, PERM_LOCAL_TERMINAL);
if ( ! can_access ) {
return;
}
socket.on('local-terminal.stdin', async msg => {
console.log('local term message', msg);
const session = this.sessions_[msg.term_uuid];
if ( ! session ) {
return;
}
const base64 = Buffer.from(msg.data, 'base64');
session.proc.stdin.write(base64);
});
});
}
}
module.exports = LocalTerminalService;
-14
View File
@@ -1,14 +0,0 @@
const { AdvancedBase } = require('@heyputer/putility');
class DNSModule extends AdvancedBase {
async install (context) {
const services = context.get('services');
const { DNSService } = require('./DNSService');
services.registerService('dns', DNSService);
}
}
module.exports = {
DNSModule,
};
-115
View File
@@ -1,115 +0,0 @@
const BaseService = require('../../services/BaseService');
const { sleep } = require('../../util/asyncutil');
/**
* DNS service that provides DNS client functionality and optional test server
* @extends BaseService
*/
class DNSService extends BaseService {
/**
* Initializes the DNS service by creating a DNS client and optionally starting a test server
* @returns {Promise<void>}
*/
async _init () {
const dns2 = require('dns2');
// this.dns = new dns2(this.config.client);
this.dns = new dns2({
nameServers: ['127.0.0.1'],
port: 5300,
});
if ( this.config.test_server ) {
this.test_server_();
}
}
/**
* Returns the DNS client instance
* @returns {Object} The DNS client
*/
get_client () {
return this.dns;
}
/**
* Creates and starts a test DNS server that responds to A and TXT record queries
* The server listens on port 5300 and returns mock responses for testing purposes
*/
test_server_ () {
const dns2 = require('dns2');
const { Packet } = dns2;
const server = dns2.createServer({
udp: true,
handle: (request, send, rinfo) => {
const { questions } = request;
const response = Packet.createResponseFromRequest(request);
for ( const question of questions ) {
if ( question.type === Packet.TYPE.A || question.type === Packet.TYPE.ANY ) {
response.answers.push({
name: question.name,
type: Packet.TYPE.A,
class: Packet.CLASS.IN,
ttl: 300,
address: '127.0.0.11',
});
}
if ( question.type === Packet.TYPE.TXT || question.type === Packet.TYPE.ANY ) {
response.answers.push({
name: question.name,
type: Packet.TYPE.TXT,
class: Packet.CLASS.IN,
ttl: 300,
data: [
JSON.stringify({ username: 'ed3' }),
],
});
}
}
send(response);
},
});
server.on('listening', () => {
this.log.debug('Fake DNS server listening', server.addresses());
if ( this.config.test_server_selftest ) {
(async () => {
await sleep(5000);
{
console.log('Trying first test');
const result = await this.dns.resolveA('test.local');
console.log('Test 1', result);
}
{
console.log('Trying second test');
const result = await this.dns.resolve('_puter-verify.test.local', 'TXT');
console.log('Test 2', result);
}
})();
}
});
server.on('close', () => {
console.log('Fake DNS server closed');
});
server.on('request', (request, response, rinfo) => {
console.log(request.header.id, request.questions[0]);
});
server.on('requestError', (error) => {
console.log('Client sent an invalid request', error);
});
server.listen({
udp: {
port: 5300,
address: '127.0.0.1',
},
});
}
}
module.exports = { DNSService };
@@ -1,16 +0,0 @@
const { AdvancedBase } = require('@heyputer/putility');
class DomainModule extends AdvancedBase {
async install (context) {
const services = context.get('services');
const { DomainVerificationService } = require('./DomainVerificationService');
services.registerService('domain-verification', DomainVerificationService);
// TODO: enable flag
const { TXTVerifyService } = require('./TXTVerifyService');
services.registerService('__txt-verify', TXTVerifyService);
}
}
module.exports = { DomainModule };
@@ -1,30 +0,0 @@
const { get_user } = require('../../helpers');
const BaseService = require('../../services/BaseService');
class DomainVerificationService extends BaseService {
_init () {
this._register_commands();
}
async get_controlling_user ({ domain }) {
const svc_event = this.services.get('event');
// 1 :: Allow event listeners to verify domains
const event = {
domain,
user: undefined,
};
await svc_event.emit('domain.get-controlling-user', event);
if ( event.user ) {
return event.user;
}
// 2 :: If there is no controlling user, 'admin' is the
// controlling user.
return await get_user({ username: 'admin' });
}
}
module.exports = {
DomainVerificationService,
};
@@ -1,32 +0,0 @@
const { get_user } = require('../../helpers');
const BaseService = require('../../services/BaseService');
const { atimeout } = require('../../util/asyncutil');
class TXTVerifyService extends BaseService {
'__on_boot.consolidation' () {
const svc_dns = this.services.get('dns');
const dns = svc_dns.get_client();
const svc_event = this.services.get('event');
svc_event.on('domain.get-controlling-user', async (_, event) => {
const record_name = `_puter-verify.${event.domain}`;
try {
const result = await atimeout(5000,
dns.resolve(record_name, 'TXT'));
const answer = result.answers.filter(a => a.name === record_name &&
a.type === 16)[0];
const data_raw = answer.data;
const data = JSON.parse(data_raw);
event.user = await get_user({ username: data.username });
} catch (e) {
console.error('ERROR', e);
}
});
}
}
module.exports = {
TXTVerifyService,
};
@@ -1,82 +0,0 @@
const BaseService = require('../../services/BaseService');
/**
* PermissiveCreditService listens to the event where DriverService asks
* for a credit context, and always provides one that allows use of
* cost-incurring services for no charge. This grants free use to
* everyone to services that incur a cost, as long as the user has
* permission to call the respective service.
*/
class PermissiveCreditService extends BaseService {
static MODULES = {
uuidv4: require('uuid').v4,
};
_init () {
// Maps usernames to simulated credit amounts
// (used when config.simulated_credit is set)
this.simulated_credit_ = {};
const svc_event = this.services.get('event');
svc_event.on('credit.check-available', (_, event) => {
const username = event.actor.type.user.username;
event.available = this.get_user_credit_(username);
// Useful for testing with Dall-E
// event.available = 4 * Math.pow(10,6);
// Useful for testing with Polly
// event.available = 9000;
// Useful for testing judge0
// event.available = 50_000;
// event.avaialble = 49_999;
// Useful for testing ConvertAPI
// event.available = 4_500_000;
// event.available = 4_499_999;
// Useful for testing with textract
// event.available = 150_000;
// event.available = 149_999;
});
svc_event.on('usages.query', (_, event) => {
const username = event.actor.type.user.username;
if ( ! this.config.simulated_credit ) {
event.usages.push({
id: 'dev-credit',
name: 'Unlimited Credit',
used: 0,
available: 1,
});
return;
}
event.usages.push({
id: 'dev-credit',
name: `Simulated Credit (${this.config.simulated_credit})`,
used: this.config.simulated_credit -
this.get_user_credit_(username),
available: this.config.simulated_credit,
});
});
}
get_user_credit_ (username) {
if ( ! this.config.simulated_credit ) {
return Number.MAX_SAFE_INTEGER;
}
return this.simulated_credit_[username] ??
(this.simulated_credit_[username] = this.config.simulated_credit);
}
consume_user_credit_ (username, amount) {
if ( ! this.config.simulated_credit ) return;
if ( ! this.simulated_credit_[username] ) {
this.simulated_credit_[username] = this.config.simulated_credit;
}
this.simulated_credit_[username] -= amount;
}
}
module.exports = PermissiveCreditService;
@@ -31,10 +31,6 @@ class SelfHostedModule extends AdvancedBase {
const DevWatcherService = require('./DevWatcherService');
const path_ = require('path');
const DevCreditService = require('./DevCreditService');
services.registerService('dev-credit', DevCreditService);
// TODO: sucks
const RELATIVE_PATH = '../../../../../';
@@ -97,21 +93,27 @@ class SelfHostedModule extends AdvancedBase {
const { ServeSingleFileService } = require('./ServeSingeFileService');
services.registerService('__serve-puterjs-new', ServeSingleFileService, {
path: path_.resolve(__dirname,
RELATIVE_PATH,
'src/puter-js/dist/puter.dev.js'),
path: path_.resolve(
__dirname,
RELATIVE_PATH,
'src/puter-js/dist/puter.dev.js',
),
route: '/puter.js/v2',
});
services.registerService('__serve-putilityjs-new', ServeSingleFileService, {
path: path_.resolve(__dirname,
RELATIVE_PATH,
'src/putility/dist/putility.dev.js'),
path: path_.resolve(
__dirname,
RELATIVE_PATH,
'src/putility/dist/putility.dev.js',
),
route: '/putility.js/v1',
});
services.registerService('__serve-gui-js', ServeSingleFileService, {
path: path_.resolve(__dirname,
RELATIVE_PATH,
'src/gui/dist/gui.dev.js'),
path: path_.resolve(
__dirname,
RELATIVE_PATH,
'src/gui/dist/gui.dev.js',
),
route: '/putility.js/v1',
});
}
@@ -1,6 +1,5 @@
import { DDBClientWrapper } from '../../clients/dynamodb/DDBClientWrapper.js';
import { FilesystemService } from '../../deprecated/filesystem/FilesystemService.js';
import { AnomalyService } from '../../services/AnomalyService.js';
import { AuthService } from '../../services/auth/AuthService.js';
import { GroupService } from '../../services/auth/GroupService.js';
import { PermissionService } from '../../services/auth/PermissionService.js';
@@ -36,7 +35,6 @@ export class TestCoreModule {
services.registerService('puter-kvstore', DynamoKVStoreWrapper);
services.registerService('permission', PermissionService);
services.registerService('group', GroupService);
services.registerService('anomaly', AnomalyService);
services.registerService('api-error', APIErrorService);
services.registerService('system-validation', SystemValidationService);
services.registerService('registry', RegistryService);
+3 -4
View File
@@ -204,10 +204,9 @@ class SQLES extends BaseES {
tasks.add(`sql_row_to_entity_::${prop.name}`, async () => {
value = await prop.sql_dereference(value);
if ( prop.typ.name === 'json' ) {
value = this.db.case({
mysql: () => value,
otherwise: () => JSON.parse(value ?? '{}'),
})();
if ( !value || typeof (value) === 'string' ) {
value = JSON.parse(value || '{}');
}
}
entity_data[prop.name] = value;
});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,838 +0,0 @@
/*!
* Bootstrap v5.1.3 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
!function (t, e) {
'object' == typeof exports && 'undefined' != typeof module ? module.exports = e(require('@popperjs/core')) : 'function' == typeof define && define.amd ? define(['@popperjs/core'], e) : (t = 'undefined' != typeof globalThis ? globalThis : t || self).bootstrap = e(t.Popper);
}(this, (function (t) {
'use strict';function e (t) {
if ( t && t.__esModule ) return t;const e = Object.create(null);if (t) {
for ( const i in t ) {
if ( 'default' !== i ) {
const s = Object.getOwnPropertyDescriptor(t, i);Object.defineProperty(e, i, s.get ? s : { enumerable: !0, get: () => t[i] });
}
}
} return e.default = t, Object.freeze(e);
} const i = e(t), s = 'transitionend', n = t => {
let e = t.getAttribute('data-bs-target');if ( !e || '#' === e ) {
let i = t.getAttribute('href');if ( !i || !i.includes('#') && !i.startsWith('.') ) return null;i.includes('#') && !i.startsWith('#') && (i = `#${i.split('#')[1]}`), e = i && '#' !== i ? i.trim() : null;
} return e;
}, o = t => {
const e = n(t);return e && document.querySelector(e) ? e : null;
}, r = t => {
const e = n(t);return e ? document.querySelector(e) : null;
}, a = t => {
t.dispatchEvent(new Event(s));
}, l = t => !(!t || 'object' != typeof t) && (void 0 !== t.jquery && (t = t[0]), void 0 !== t.nodeType), c = t => l(t) ? t.jquery ? t[0] : t : 'string' == typeof t && t.length > 0 ? document.querySelector(t) : null, h = (t, e, i) => {
Object.keys(i).forEach((s => {
const n = i[s], o = e[s], r = o && l(o) ? 'element' : null == (a = o) ? `${a}` : {}.toString.call(a).match(/\s([a-z]+)/i)[1].toLowerCase();var a;if ( ! new RegExp(n).test(r) ) throw new TypeError(`${t.toUpperCase()}: Option "${s}" provided type "${r}" but expected type "${n}".`);
}));
}, d = t => !(!l(t) || 0 === t.getClientRects().length) && 'visible' === getComputedStyle(t).getPropertyValue('visibility'), u = t => !t || t.nodeType !== Node.ELEMENT_NODE || !!t.classList.contains('disabled') || (void 0 !== t.disabled ? t.disabled : t.hasAttribute('disabled') && 'false' !== t.getAttribute('disabled')), g = t => {
if ( ! document.documentElement.attachShadow ) return null;if ( 'function' == typeof t.getRootNode ) {
const e = t.getRootNode();return e instanceof ShadowRoot ? e : null;
} return t instanceof ShadowRoot ? t : t.parentNode ? g(t.parentNode) : null;
}, _ = () => {
}, f = t => {
t.offsetHeight;
}, p = () => {
const { jQuery: t } = window;return t && !document.body.hasAttribute('data-bs-no-jquery') ? t : null;
}, m = [], b = () => 'rtl' === document.documentElement.dir, v = t => {
var e;e = () => {
const e = p();if (e) {
const i = t.NAME, s = e.fn[i];e.fn[i] = t.jQueryInterface, e.fn[i].Constructor = t, e.fn[i].noConflict = () => (e.fn[i] = s, t.jQueryInterface);
}
}, 'loading' === document.readyState ? (m.length || document.addEventListener('DOMContentLoaded', (() => {
m.forEach((t => t()));
})), m.push(e)) : e();
}, y = t => {
'function' == typeof t && t();
}, E = (t, e, i = !0) => {
if ( ! i ) return void y(t);const n = (t => {
if ( ! t ) return 0;let { transitionDuration: e, transitionDelay: i } = window.getComputedStyle(t);const s = Number.parseFloat(e), n = Number.parseFloat(i);return s || n ? (e = e.split(',')[0], i = i.split(',')[0], 1e3 * (Number.parseFloat(e) + Number.parseFloat(i))) : 0;
})(e) + 5;let o = !1;const r = ({ target: i }) => {
i === e && (o = !0, e.removeEventListener(s, r), y(t));
};e.addEventListener(s, r), setTimeout((() => {
o || a(e);
}), n);
}, w = (t, e, i, s) => {
let n = t.indexOf(e);if ( -1 === n ) return t[!i && s ? t.length - 1 : 0];const o = t.length;return n += i ? 1 : -1, s && (n = (n + o) % o), t[Math.max(0, Math.min(n, o - 1))];
}, A = /[^.]*(?=\..*)\.|.*/, T = /\..*/, C = /::\d+$/, k = {};let L = 1;const S = { mouseenter: 'mouseover', mouseleave: 'mouseout' }, O = /^(mouseenter|mouseleave)/i, N = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']);function D (t, e) {
return e && `${e}::${L++}` || t.uidEvent || L++;
} function I (t) {
const e = D(t);return t.uidEvent = e, k[e] = k[e] || {}, k[e];
} function P (t, e, i = null) {
const s = Object.keys(t);for ( let n = 0, o = s.length;n < o;n++ ) {
const o = t[s[n]];if ( o.originalHandler === e && o.delegationSelector === i ) return o;
} return null;
} function x (t, e, i) {
const s = 'string' == typeof e, n = s ? i : e;let o = H(t);return N.has(o) || (o = t), [s, n, o];
} function M (t, e, i, s, n) {
if ( 'string' != typeof e || !t ) return;if ( i || (i = s, s = null), O.test(e) ) {
const t = t => function (e) {
if ( !e.relatedTarget || e.relatedTarget !== e.delegateTarget && !e.delegateTarget.contains(e.relatedTarget) ) return t.call(this, e);
};s ? s = t(s) : i = t(i);
} const [o, r, a] = x(e, i, s), l = I(t), c = l[a] || (l[a] = {}), h = P(c, r, o ? i : null);if (h) return void (h.oneOff = h.oneOff && n);const d = D(r, e.replace(A, '')), u = o ? function (t, e, i) {
return function s (n) {
const o = t.querySelectorAll(e);for ( let { target: r } = n;r && r !== this;r = r.parentNode ) for ( let a = o.length;a--; ) if ( o[a] === r ) return n.delegateTarget = r, s.oneOff && $.off(t, n.type, e, i), i.apply(r, [n]);return null;
};
}(t, i, s) : function (t, e) {
return function i (s) {
return s.delegateTarget = t, i.oneOff && $.off(t, s.type, e), e.apply(t, [s]);
};
}(t, i);u.delegationSelector = o ? i : null, u.originalHandler = r, u.oneOff = n, u.uidEvent = d, c[d] = u, t.addEventListener(a, u, o);
} function j (t, e, i, s, n) {
const o = P(e[i], s, n);o && (t.removeEventListener(i, o, Boolean(n)), delete e[i][o.uidEvent]);
} function H (t) {
return t = t.replace(T, ''), S[t] || t;
} const $ = { on (t, e, i, s) {
M(t, e, i, s, !1);
},
one (t, e, i, s) {
M(t, e, i, s, !0);
},
off (t, e, i, s) {
if ( 'string' != typeof e || !t ) return;const [n, o, r] = x(e, i, s), a = r !== e, l = I(t), c = e.startsWith('.');if ( void 0 !== o ) {
if ( !l || !l[r] ) return;return void j(t, l, r, o, n ? i : null);
}c && Object.keys(l).forEach((i => {
!function (t, e, i, s) {
const n = e[i] || {};Object.keys(n).forEach((o => {
if ( o.includes(s) ) {
const s = n[o];j(t, e, i, s.originalHandler, s.delegationSelector);
}
}));
}(t, l, i, e.slice(1));
}));const h = l[r] || {};Object.keys(h).forEach((i => {
const s = i.replace(C, '');if ( !a || e.includes(s) ) {
const e = h[i];j(t, l, r, e.originalHandler, e.delegationSelector);
}
}));
},
trigger (t, e, i) {
if ( 'string' != typeof e || !t ) return null;const s = p(), n = H(e), o = e !== n, r = N.has(n);let a, l = !0, c = !0, h = !1, d = null;return o && s && (a = s.Event(e, i), s(t).trigger(a), l = !a.isPropagationStopped(), c = !a.isImmediatePropagationStopped(), h = a.isDefaultPrevented()), r ? (d = document.createEvent('HTMLEvents'), d.initEvent(n, l, !0)) : d = new CustomEvent(e, { bubbles: l, cancelable: !0 }), void 0 !== i && Object.keys(i).forEach((t => {
Object.defineProperty(d, t, { get: () => i[t] });
})), h && d.preventDefault(), c && t.dispatchEvent(d), d.defaultPrevented && void 0 !== a && a.preventDefault(), d;
} }, B = new Map, z = { set (t, e, i) {
B.has(t) || B.set(t, new Map);const s = B.get(t);s.has(e) || 0 === s.size ? s.set(e, i) : console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`);
},
get: (t, e) => B.has(t) && B.get(t).get(e) || null,
remove (t, e) {
if ( ! B.has(t) ) return;const i = B.get(t);i.delete(e), 0 === i.size && B.delete(t);
} };class R {
constructor (t) {
(t = c(t)) && (this._element = t, z.set(this._element, this.constructor.DATA_KEY, this));
}dispose () {
z.remove(this._element, this.constructor.DATA_KEY), $.off(this._element, this.constructor.EVENT_KEY), Object.getOwnPropertyNames(this).forEach((t => {
this[t] = null;
}));
}_queueCallback (t, e, i = !0) {
E(t, e, i);
} static getInstance (t) {
return z.get(c(t), this.DATA_KEY);
} static getOrCreateInstance (t, e = {}) {
return this.getInstance(t) || new this(t, 'object' == typeof e ? e : null);
} static get VERSION () {
return '5.1.3';
} static get NAME () {
throw new Error('You have to implement the static method "NAME", for each component!');
} static get DATA_KEY () {
return `bs.${this.NAME}`;
} static get EVENT_KEY () {
return `.${this.DATA_KEY}`;
}
} const F = (t, e = 'hide') => {
const i = `click.dismiss${t.EVENT_KEY}`, s = t.NAME;$.on(document, i, `[data-bs-dismiss="${s}"]`, (function (i) {
if ( ['A', 'AREA'].includes(this.tagName) && i.preventDefault(), u(this) ) return;const n = r(this) || this.closest(`.${s}`);t.getOrCreateInstance(n)[e]();
}));
};class q extends R {
static get NAME () {
return 'alert';
}close () {
if ( $.trigger(this._element, 'close.bs.alert').defaultPrevented ) return;this._element.classList.remove('show');const t = this._element.classList.contains('fade');this._queueCallback((() => this._destroyElement()), this._element, t);
}_destroyElement () {
this._element.remove(), $.trigger(this._element, 'closed.bs.alert'), this.dispose();
} static jQueryInterface (t) {
return this.each((function () {
const e = q.getOrCreateInstance(this);if ( 'string' == typeof t ) {
if ( void 0 === e[t] || t.startsWith('_') || 'constructor' === t ) throw new TypeError(`No method named "${t}"`);e[t](this);
}
}));
}
}F(q, 'close'), v(q);const W = '[data-bs-toggle="button"]';class U extends R {
static get NAME () {
return 'button';
}toggle () {
this._element.setAttribute('aria-pressed', this._element.classList.toggle('active'));
} static jQueryInterface (t) {
return this.each((function () {
const e = U.getOrCreateInstance(this);'toggle' === t && e[t]();
}));
}
} function K (t) {
return 'true' === t || 'false' !== t && (t === Number(t).toString() ? Number(t) : '' === t || 'null' === t ? null : t);
} function V (t) {
return t.replace(/[A-Z]/g, (t => `-${t.toLowerCase()}`));
}$.on(document, 'click.bs.button.data-api', W, (t => {
t.preventDefault();const e = t.target.closest(W);U.getOrCreateInstance(e).toggle();
})), v(U);const X = { setDataAttribute (t, e, i) {
t.setAttribute(`data-bs-${V(e)}`, i);
},
removeDataAttribute (t, e) {
t.removeAttribute(`data-bs-${V(e)}`);
},
getDataAttributes (t) {
if ( ! t ) return {};const e = {};return Object.keys(t.dataset).filter((t => t.startsWith('bs'))).forEach((i => {
let s = i.replace(/^bs/, '');s = s.charAt(0).toLowerCase() + s.slice(1, s.length), e[s] = K(t.dataset[i]);
})), e;
},
getDataAttribute: (t, e) => K(t.getAttribute(`data-bs-${V(e)}`)),
offset (t) {
const e = t.getBoundingClientRect();return { top: e.top + window.pageYOffset, left: e.left + window.pageXOffset };
},
position: t => ({ top: t.offsetTop, left: t.offsetLeft }) }, Y = { find: (t, e = document.documentElement) => [].concat(...Element.prototype.querySelectorAll.call(e, t)),
findOne: (t, e = document.documentElement) => Element.prototype.querySelector.call(e, t),
children: (t, e) => [].concat(...t.children).filter((t => t.matches(e))),
parents (t, e) {
const i = [];let s = t.parentNode;for ( ;s && s.nodeType === Node.ELEMENT_NODE && 3 !== s.nodeType; )s.matches(e) && i.push(s), s = s.parentNode;return i;
},
prev (t, e) {
let i = t.previousElementSibling;for ( ;i; ) {
if ( i.matches(e) ) return [i];i = i.previousElementSibling;
} return [];
},
next (t, e) {
let i = t.nextElementSibling;for ( ;i; ) {
if ( i.matches(e) ) return [i];i = i.nextElementSibling;
} return [];
},
focusableChildren (t) {
const e = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable="true"]'].map((t => `${t}:not([tabindex^="-"])`)).join(', ');return this.find(e, t).filter((t => !u(t) && d(t)));
} }, Q = 'carousel', G = { interval: 5e3, keyboard: !0, slide: !1, pause: 'hover', wrap: !0, touch: !0 }, Z = { interval: '(number|boolean)', keyboard: 'boolean', slide: '(boolean|string)', pause: '(string|boolean)', wrap: 'boolean', touch: 'boolean' }, J = 'next', tt = 'prev', et = 'left', it = 'right', st = { ArrowLeft: it, ArrowRight: et }, nt = 'slid.bs.carousel', ot = 'active', rt = '.active.carousel-item';class at extends R {
constructor (t, e) {
super(t), this._items = null, this._interval = null, this._activeElement = null, this._isPaused = !1, this._isSliding = !1, this.touchTimeout = null, this.touchStartX = 0, this.touchDeltaX = 0, this._config = this._getConfig(e), this._indicatorsElement = Y.findOne('.carousel-indicators', this._element), this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0, this._pointerEvent = Boolean(window.PointerEvent), this._addEventListeners();
} static get Default () {
return G;
} static get NAME () {
return Q;
}next () {
this._slide(J);
}nextWhenVisible () {
!document.hidden && d(this._element) && this.next();
}prev () {
this._slide(tt);
}pause (t) {
t || (this._isPaused = !0), Y.findOne('.carousel-item-next, .carousel-item-prev', this._element) && (a(this._element), this.cycle(!0)), clearInterval(this._interval), this._interval = null;
}cycle (t) {
t || (this._isPaused = !1), this._interval && (clearInterval(this._interval), this._interval = null), this._config && this._config.interval && !this._isPaused && (this._updateInterval(), this._interval = setInterval((document.visibilityState ? this.nextWhenVisible : this.next).bind(this), this._config.interval));
}to (t) {
this._activeElement = Y.findOne(rt, this._element);const e = this._getItemIndex(this._activeElement);if ( t > this._items.length - 1 || t < 0 ) return;if ( this._isSliding ) return void $.one(this._element, nt, (() => this.to(t)));if ( e === t ) return this.pause(), void this.cycle();const i = t > e ? J : tt;this._slide(i, this._items[t]);
}_getConfig (t) {
return t = { ...G, ...X.getDataAttributes(this._element), ...'object' == typeof t ? t : {} }, h(Q, t, Z), t;
}_handleSwipe () {
const t = Math.abs(this.touchDeltaX);if ( t <= 40 ) return;const e = t / this.touchDeltaX;this.touchDeltaX = 0, e && this._slide(e > 0 ? it : et);
}_addEventListeners () {
this._config.keyboard && $.on(this._element, 'keydown.bs.carousel', (t => this._keydown(t))), 'hover' === this._config.pause && ($.on(this._element, 'mouseenter.bs.carousel', (t => this.pause(t))), $.on(this._element, 'mouseleave.bs.carousel', (t => this.cycle(t)))), this._config.touch && this._touchSupported && this._addTouchEventListeners();
}_addTouchEventListeners () {
const t = t => this._pointerEvent && ('pen' === t.pointerType || 'touch' === t.pointerType), e = e => {
t(e) ? this.touchStartX = e.clientX : this._pointerEvent || (this.touchStartX = e.touches[0].clientX);
}, i = t => {
this.touchDeltaX = t.touches && t.touches.length > 1 ? 0 : t.touches[0].clientX - this.touchStartX;
}, s = e => {
t(e) && (this.touchDeltaX = e.clientX - this.touchStartX), this._handleSwipe(), 'hover' === this._config.pause && (this.pause(), this.touchTimeout && clearTimeout(this.touchTimeout), this.touchTimeout = setTimeout((t => this.cycle(t)), 500 + this._config.interval));
};Y.find('.carousel-item img', this._element).forEach((t => {
$.on(t, 'dragstart.bs.carousel', (t => t.preventDefault()));
})), this._pointerEvent ? ($.on(this._element, 'pointerdown.bs.carousel', (t => e(t))), $.on(this._element, 'pointerup.bs.carousel', (t => s(t))), this._element.classList.add('pointer-event')) : ($.on(this._element, 'touchstart.bs.carousel', (t => e(t))), $.on(this._element, 'touchmove.bs.carousel', (t => i(t))), $.on(this._element, 'touchend.bs.carousel', (t => s(t))));
}_keydown (t) {
if ( /input|textarea/i.test(t.target.tagName) ) return;const e = st[t.key];e && (t.preventDefault(), this._slide(e));
}_getItemIndex (t) {
return this._items = t && t.parentNode ? Y.find('.carousel-item', t.parentNode) : [], this._items.indexOf(t);
}_getItemByOrder (t, e) {
const i = t === J;return w(this._items, e, i, this._config.wrap);
}_triggerSlideEvent (t, e) {
const i = this._getItemIndex(t), s = this._getItemIndex(Y.findOne(rt, this._element));return $.trigger(this._element, 'slide.bs.carousel', { relatedTarget: t, direction: e, from: s, to: i });
}_setActiveIndicatorElement (t) {
if ( this._indicatorsElement ) {
const e = Y.findOne('.active', this._indicatorsElement);e.classList.remove(ot), e.removeAttribute('aria-current');const i = Y.find('[data-bs-target]', this._indicatorsElement);for ( let e = 0;e < i.length;e++ ) {
if ( Number.parseInt(i[e].getAttribute('data-bs-slide-to'), 10) === this._getItemIndex(t) ) {
i[e].classList.add(ot), i[e].setAttribute('aria-current', 'true');break;
}
}
}
}_updateInterval () {
const t = this._activeElement || Y.findOne(rt, this._element);if ( ! t ) return;const e = Number.parseInt(t.getAttribute('data-bs-interval'), 10);e ? (this._config.defaultInterval = this._config.defaultInterval || this._config.interval, this._config.interval = e) : this._config.interval = this._config.defaultInterval || this._config.interval;
}_slide (t, e) {
const i = this._directionToOrder(t), s = Y.findOne(rt, this._element), n = this._getItemIndex(s), o = e || this._getItemByOrder(i, s), r = this._getItemIndex(o), a = Boolean(this._interval), l = i === J, c = l ? 'carousel-item-start' : 'carousel-item-end', h = l ? 'carousel-item-next' : 'carousel-item-prev', d = this._orderToDirection(i);if ( o && o.classList.contains(ot) ) return void (this._isSliding = !1);if ( this._isSliding ) return;if ( this._triggerSlideEvent(o, d).defaultPrevented ) return;if ( !s || !o ) return;this._isSliding = !0, a && this.pause(), this._setActiveIndicatorElement(o), this._activeElement = o;const u = () => {
$.trigger(this._element, nt, { relatedTarget: o, direction: d, from: n, to: r });
};if ( this._element.classList.contains('slide') ) {
o.classList.add(h), f(o), s.classList.add(c), o.classList.add(c);const t = () => {
o.classList.remove(c, h), o.classList.add(ot), s.classList.remove(ot, h, c), this._isSliding = !1, setTimeout(u, 0);
};this._queueCallback(t, s, !0);
} else s.classList.remove(ot), o.classList.add(ot), this._isSliding = !1, u();a && this.cycle();
}_directionToOrder (t) {
return [it, et].includes(t) ? b() ? t === et ? tt : J : t === et ? J : tt : t;
}_orderToDirection (t) {
return [J, tt].includes(t) ? b() ? t === tt ? et : it : t === tt ? it : et : t;
} static carouselInterface (t, e) {
const i = at.getOrCreateInstance(t, e);let { _config: s } = i;'object' == typeof e && (s = { ...s, ...e });const n = 'string' == typeof e ? e : s.slide;if ( 'number' == typeof e )i.to(e);else if ( 'string' == typeof n ) {
if ( void 0 === i[n] ) throw new TypeError(`No method named "${n}"`);i[n]();
} else s.interval && s.ride && (i.pause(), i.cycle());
} static jQueryInterface (t) {
return this.each((function () {
at.carouselInterface(this, t);
}));
} static dataApiClickHandler (t) {
const e = r(this);if ( !e || !e.classList.contains('carousel') ) return;const i = { ...X.getDataAttributes(e), ...X.getDataAttributes(this) }, s = this.getAttribute('data-bs-slide-to');s && (i.interval = !1), at.carouselInterface(e, i), s && at.getInstance(e).to(s), t.preventDefault();
}
}$.on(document, 'click.bs.carousel.data-api', '[data-bs-slide], [data-bs-slide-to]', at.dataApiClickHandler), $.on(window, 'load.bs.carousel.data-api', (() => {
const t = Y.find('[data-bs-ride="carousel"]');for ( let e = 0, i = t.length;e < i;e++ )at.carouselInterface(t[e], at.getInstance(t[e]));
})), v(at);const lt = 'collapse', ct = { toggle: !0, parent: null }, ht = { toggle: 'boolean', parent: '(null|element)' }, dt = 'show', ut = 'collapse', gt = 'collapsing', _t = 'collapsed', ft = ':scope .collapse .collapse', pt = '[data-bs-toggle="collapse"]';class mt extends R {
constructor (t, e) {
super(t), this._isTransitioning = !1, this._config = this._getConfig(e), this._triggerArray = [];const i = Y.find(pt);for ( let t = 0, e = i.length;t < e;t++ ) {
const e = i[t], s = o(e), n = Y.find(s).filter((t => t === this._element));null !== s && n.length && (this._selector = s, this._triggerArray.push(e));
} this._initializeChildren(), this._config.parent || this._addAriaAndCollapsedClass(this._triggerArray, this._isShown()), this._config.toggle && this.toggle();
} static get Default () {
return ct;
} static get NAME () {
return lt;
}toggle () {
this._isShown() ? this.hide() : this.show();
}show () {
if ( this._isTransitioning || this._isShown() ) return;let t, e = [];if ( this._config.parent ) {
const t = Y.find(ft, this._config.parent);e = Y.find('.collapse.show, .collapse.collapsing', this._config.parent).filter((e => !t.includes(e)));
} const i = Y.findOne(this._selector);if ( e.length ) {
const s = e.find((t => i !== t));if ( t = s ? mt.getInstance(s) : null, t && t._isTransitioning ) return;
} if ( $.trigger(this._element, 'show.bs.collapse').defaultPrevented ) return;e.forEach((e => {
i !== e && mt.getOrCreateInstance(e, { toggle: !1 }).hide(), t || z.set(e, 'bs.collapse', null);
}));const s = this._getDimension();this._element.classList.remove(ut), this._element.classList.add(gt), this._element.style[s] = 0, this._addAriaAndCollapsedClass(this._triggerArray, !0), this._isTransitioning = !0;const n = `scroll${s[0].toUpperCase() + s.slice(1)}`;this._queueCallback((() => {
this._isTransitioning = !1, this._element.classList.remove(gt), this._element.classList.add(ut, dt), this._element.style[s] = '', $.trigger(this._element, 'shown.bs.collapse');
}), this._element, !0), this._element.style[s] = `${this._element[n]}px`;
}hide () {
if ( this._isTransitioning || !this._isShown() ) return;if ( $.trigger(this._element, 'hide.bs.collapse').defaultPrevented ) return;const t = this._getDimension();this._element.style[t] = `${this._element.getBoundingClientRect()[t]}px`, f(this._element), this._element.classList.add(gt), this._element.classList.remove(ut, dt);const e = this._triggerArray.length;for ( let t = 0;t < e;t++ ) {
const e = this._triggerArray[t], i = r(e);i && !this._isShown(i) && this._addAriaAndCollapsedClass([e], !1);
} this._isTransitioning = !0, this._element.style[t] = '', this._queueCallback((() => {
this._isTransitioning = !1, this._element.classList.remove(gt), this._element.classList.add(ut), $.trigger(this._element, 'hidden.bs.collapse');
}), this._element, !0);
}_isShown (t = this._element) {
return t.classList.contains(dt);
}_getConfig (t) {
return (t = { ...ct, ...X.getDataAttributes(this._element), ...t }).toggle = Boolean(t.toggle), t.parent = c(t.parent), h(lt, t, ht), t;
}_getDimension () {
return this._element.classList.contains('collapse-horizontal') ? 'width' : 'height';
}_initializeChildren () {
if ( ! this._config.parent ) return;const t = Y.find(ft, this._config.parent);Y.find(pt, this._config.parent).filter((e => !t.includes(e))).forEach((t => {
const e = r(t);e && this._addAriaAndCollapsedClass([t], this._isShown(e));
}));
}_addAriaAndCollapsedClass (t, e) {
t.length && t.forEach((t => {
e ? t.classList.remove(_t) : t.classList.add(_t), t.setAttribute('aria-expanded', e);
}));
} static jQueryInterface (t) {
return this.each((function () {
const e = {};'string' == typeof t && /show|hide/.test(t) && (e.toggle = !1);const i = mt.getOrCreateInstance(this, e);if ( 'string' == typeof t ) {
if ( void 0 === i[t] ) throw new TypeError(`No method named "${t}"`);i[t]();
}
}));
}
}$.on(document, 'click.bs.collapse.data-api', pt, (function (t) {
('A' === t.target.tagName || t.delegateTarget && 'A' === t.delegateTarget.tagName) && t.preventDefault();const e = o(this);Y.find(e).forEach((t => {
mt.getOrCreateInstance(t, { toggle: !1 }).toggle();
}));
})), v(mt);const bt = 'dropdown', vt = 'Escape', yt = 'Space', Et = 'ArrowUp', wt = 'ArrowDown', At = new RegExp('ArrowUp|ArrowDown|Escape'), Tt = 'click.bs.dropdown.data-api', Ct = 'keydown.bs.dropdown.data-api', kt = 'show', Lt = '[data-bs-toggle="dropdown"]', St = '.dropdown-menu', Ot = b() ? 'top-end' : 'top-start', Nt = b() ? 'top-start' : 'top-end', Dt = b() ? 'bottom-end' : 'bottom-start', It = b() ? 'bottom-start' : 'bottom-end', Pt = b() ? 'left-start' : 'right-start', xt = b() ? 'right-start' : 'left-start', Mt = { offset: [0, 2], boundary: 'clippingParents', reference: 'toggle', display: 'dynamic', popperConfig: null, autoClose: !0 }, jt = { offset: '(array|string|function)', boundary: '(string|element)', reference: '(string|element|object)', display: 'string', popperConfig: '(null|object|function)', autoClose: '(boolean|string)' };class Ht extends R {
constructor (t, e) {
super(t), this._popper = null, this._config = this._getConfig(e), this._menu = this._getMenuElement(), this._inNavbar = this._detectNavbar();
} static get Default () {
return Mt;
} static get DefaultType () {
return jt;
} static get NAME () {
return bt;
}toggle () {
return this._isShown() ? this.hide() : this.show();
}show () {
if ( u(this._element) || this._isShown(this._menu) ) return;const t = { relatedTarget: this._element };if ( $.trigger(this._element, 'show.bs.dropdown', t).defaultPrevented ) return;const e = Ht.getParentFromElement(this._element);this._inNavbar ? X.setDataAttribute(this._menu, 'popper', 'none') : this._createPopper(e), 'ontouchstart' in document.documentElement && !e.closest('.navbar-nav') && [].concat(...document.body.children).forEach((t => $.on(t, 'mouseover', _))), this._element.focus(), this._element.setAttribute('aria-expanded', !0), this._menu.classList.add(kt), this._element.classList.add(kt), $.trigger(this._element, 'shown.bs.dropdown', t);
}hide () {
if ( u(this._element) || !this._isShown(this._menu) ) return;const t = { relatedTarget: this._element };this._completeHide(t);
}dispose () {
this._popper && this._popper.destroy(), super.dispose();
}update () {
this._inNavbar = this._detectNavbar(), this._popper && this._popper.update();
}_completeHide (t) {
$.trigger(this._element, 'hide.bs.dropdown', t).defaultPrevented || ('ontouchstart' in document.documentElement && [].concat(...document.body.children).forEach((t => $.off(t, 'mouseover', _))), this._popper && this._popper.destroy(), this._menu.classList.remove(kt), this._element.classList.remove(kt), this._element.setAttribute('aria-expanded', 'false'), X.removeDataAttribute(this._menu, 'popper'), $.trigger(this._element, 'hidden.bs.dropdown', t));
}_getConfig (t) {
if ( t = { ...this.constructor.Default, ...X.getDataAttributes(this._element), ...t }, h(bt, t, this.constructor.DefaultType), 'object' == typeof t.reference && !l(t.reference) && 'function' != typeof t.reference.getBoundingClientRect ) throw new TypeError(`${bt.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t;
}_createPopper (t) {
if ( void 0 === i ) throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e = this._element;'parent' === this._config.reference ? e = t : l(this._config.reference) ? e = c(this._config.reference) : 'object' == typeof this._config.reference && (e = this._config.reference);const s = this._getPopperConfig(), n = s.modifiers.find((t => 'applyStyles' === t.name && !1 === t.enabled));this._popper = i.createPopper(e, this._menu, s), n && X.setDataAttribute(this._menu, 'popper', 'static');
}_isShown (t = this._element) {
return t.classList.contains(kt);
}_getMenuElement () {
return Y.next(this._element, St)[0];
}_getPlacement () {
const t = this._element.parentNode;if ( t.classList.contains('dropend') ) return Pt;if ( t.classList.contains('dropstart') ) return xt;const e = 'end' === getComputedStyle(this._menu).getPropertyValue('--bs-position').trim();return t.classList.contains('dropup') ? e ? Nt : Ot : e ? It : Dt;
}_detectNavbar () {
return null !== this._element.closest('.navbar');
}_getOffset () {
const { offset: t } = this._config;return 'string' == typeof t ? t.split(',').map((t => Number.parseInt(t, 10))) : 'function' == typeof t ? e => t(e, this._element) : t;
}_getPopperConfig () {
const t = { placement: this._getPlacement(), modifiers: [{ name: 'preventOverflow', options: { boundary: this._config.boundary } }, { name: 'offset', options: { offset: this._getOffset() } }] };return 'static' === this._config.display && (t.modifiers = [{ name: 'applyStyles', enabled: !1 }]), { ...t, ...'function' == typeof this._config.popperConfig ? this._config.popperConfig(t) : this._config.popperConfig };
}_selectMenuItem ({ key: t, target: e }) {
const i = Y.find('.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)', this._menu).filter(d);i.length && w(i, e, t === wt, !i.includes(e)).focus();
} static jQueryInterface (t) {
return this.each((function () {
const e = Ht.getOrCreateInstance(this, t);if ( 'string' == typeof t ) {
if ( void 0 === e[t] ) throw new TypeError(`No method named "${t}"`);e[t]();
}
}));
} static clearMenus (t) {
if ( t && (2 === t.button || 'keyup' === t.type && 'Tab' !== t.key) ) return;const e = Y.find(Lt);for ( let i = 0, s = e.length;i < s;i++ ) {
const s = Ht.getInstance(e[i]);if ( !s || !1 === s._config.autoClose ) continue;if ( ! s._isShown() ) continue;const n = { relatedTarget: s._element };if (t) {
const e = t.composedPath(), i = e.includes(s._menu);if ( e.includes(s._element) || 'inside' === s._config.autoClose && !i || 'outside' === s._config.autoClose && i ) continue;if ( s._menu.contains(t.target) && ('keyup' === t.type && 'Tab' === t.key || /input|select|option|textarea|form/i.test(t.target.tagName)) ) continue;'click' === t.type && (n.clickEvent = t);
}s._completeHide(n);
}
} static getParentFromElement (t) {
return r(t) || t.parentNode;
} static dataApiKeydownHandler (t) {
if ( /input|textarea/i.test(t.target.tagName) ? t.key === yt || t.key !== vt && (t.key !== wt && t.key !== Et || t.target.closest(St)) : !At.test(t.key) ) return;const e = this.classList.contains(kt);if ( !e && t.key === vt ) return;if ( t.preventDefault(), t.stopPropagation(), u(this) ) return;const i = this.matches(Lt) ? this : Y.prev(this, Lt)[0], s = Ht.getOrCreateInstance(i);if ( t.key !== vt ) return t.key === Et || t.key === wt ? (e || s.show(), void s._selectMenuItem(t)) : void (e && t.key !== yt || Ht.clearMenus());s.hide();
}
}$.on(document, Ct, Lt, Ht.dataApiKeydownHandler), $.on(document, Ct, St, Ht.dataApiKeydownHandler), $.on(document, Tt, Ht.clearMenus), $.on(document, 'keyup.bs.dropdown.data-api', Ht.clearMenus), $.on(document, Tt, Lt, (function (t) {
t.preventDefault(), Ht.getOrCreateInstance(this).toggle();
})), v(Ht);const $t = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top', Bt = '.sticky-top';class zt {
constructor () {
this._element = document.body;
}getWidth () {
const t = document.documentElement.clientWidth;return Math.abs(window.innerWidth - t);
}hide () {
const t = this.getWidth();this._disableOverFlow(), this._setElementAttributes(this._element, 'paddingRight', (e => e + t)), this._setElementAttributes($t, 'paddingRight', (e => e + t)), this._setElementAttributes(Bt, 'marginRight', (e => e - t));
}_disableOverFlow () {
this._saveInitialAttribute(this._element, 'overflow'), this._element.style.overflow = 'hidden';
}_setElementAttributes (t, e, i) {
const s = this.getWidth();this._applyManipulationCallback(t, (t => {
if ( t !== this._element && window.innerWidth > t.clientWidth + s ) return;this._saveInitialAttribute(t, e);const n = window.getComputedStyle(t)[e];t.style[e] = `${i(Number.parseFloat(n))}px`;
}));
}reset () {
this._resetElementAttributes(this._element, 'overflow'), this._resetElementAttributes(this._element, 'paddingRight'), this._resetElementAttributes($t, 'paddingRight'), this._resetElementAttributes(Bt, 'marginRight');
}_saveInitialAttribute (t, e) {
const i = t.style[e];i && X.setDataAttribute(t, e, i);
}_resetElementAttributes (t, e) {
this._applyManipulationCallback(t, (t => {
const i = X.getDataAttribute(t, e);void 0 === i ? t.style.removeProperty(e) : (X.removeDataAttribute(t, e), t.style[e] = i);
}));
}_applyManipulationCallback (t, e) {
l(t) ? e(t) : Y.find(t, this._element).forEach(e);
}isOverflowing () {
return this.getWidth() > 0;
}
} const Rt = { className: 'modal-backdrop', isVisible: !0, isAnimated: !1, rootElement: 'body', clickCallback: null }, Ft = { className: 'string', isVisible: 'boolean', isAnimated: 'boolean', rootElement: '(element|string)', clickCallback: '(function|null)' }, qt = 'show', Wt = 'mousedown.bs.backdrop';class Ut {
constructor (t) {
this._config = this._getConfig(t), this._isAppended = !1, this._element = null;
}show (t) {
this._config.isVisible ? (this._append(), this._config.isAnimated && f(this._getElement()), this._getElement().classList.add(qt), this._emulateAnimation((() => {
y(t);
}))) : y(t);
}hide (t) {
this._config.isVisible ? (this._getElement().classList.remove(qt), this._emulateAnimation((() => {
this.dispose(), y(t);
}))) : y(t);
}_getElement () {
if ( ! this._element ) {
const t = document.createElement('div');t.className = this._config.className, this._config.isAnimated && t.classList.add('fade'), this._element = t;
} return this._element;
}_getConfig (t) {
return (t = { ...Rt, ...'object' == typeof t ? t : {} }).rootElement = c(t.rootElement), h('backdrop', t, Ft), t;
}_append () {
this._isAppended || (this._config.rootElement.append(this._getElement()), $.on(this._getElement(), Wt, (() => {
y(this._config.clickCallback);
})), this._isAppended = !0);
}dispose () {
this._isAppended && ($.off(this._element, Wt), this._element.remove(), this._isAppended = !1);
}_emulateAnimation (t) {
E(t, this._getElement(), this._config.isAnimated);
}
} const Kt = { trapElement: null, autofocus: !0 }, Vt = { trapElement: 'element', autofocus: 'boolean' }, Xt = '.bs.focustrap', Yt = 'backward';class Qt {
constructor (t) {
this._config = this._getConfig(t), this._isActive = !1, this._lastTabNavDirection = null;
}activate () {
const { trapElement: t, autofocus: e } = this._config;this._isActive || (e && t.focus(), $.off(document, Xt), $.on(document, 'focusin.bs.focustrap', (t => this._handleFocusin(t))), $.on(document, 'keydown.tab.bs.focustrap', (t => this._handleKeydown(t))), this._isActive = !0);
}deactivate () {
this._isActive && (this._isActive = !1, $.off(document, Xt));
}_handleFocusin (t) {
const { target: e } = t, { trapElement: i } = this._config;if ( e === document || e === i || i.contains(e) ) return;const s = Y.focusableChildren(i);0 === s.length ? i.focus() : this._lastTabNavDirection === Yt ? s[s.length - 1].focus() : s[0].focus();
}_handleKeydown (t) {
'Tab' === t.key && (this._lastTabNavDirection = t.shiftKey ? Yt : 'forward');
}_getConfig (t) {
return t = { ...Kt, ...'object' == typeof t ? t : {} }, h('focustrap', t, Vt), t;
}
} const Gt = 'modal', Zt = 'Escape', Jt = { backdrop: !0, keyboard: !0, focus: !0 }, te = { backdrop: '(boolean|string)', keyboard: 'boolean', focus: 'boolean' }, ee = 'hidden.bs.modal', ie = 'show.bs.modal', se = 'resize.bs.modal', ne = 'click.dismiss.bs.modal', oe = 'keydown.dismiss.bs.modal', re = 'mousedown.dismiss.bs.modal', ae = 'modal-open', le = 'show', ce = 'modal-static';class he extends R {
constructor (t, e) {
super(t), this._config = this._getConfig(e), this._dialog = Y.findOne('.modal-dialog', this._element), this._backdrop = this._initializeBackDrop(), this._focustrap = this._initializeFocusTrap(), this._isShown = !1, this._ignoreBackdropClick = !1, this._isTransitioning = !1, this._scrollBar = new zt;
} static get Default () {
return Jt;
} static get NAME () {
return Gt;
}toggle (t) {
return this._isShown ? this.hide() : this.show(t);
}show (t) {
this._isShown || this._isTransitioning || $.trigger(this._element, ie, { relatedTarget: t }).defaultPrevented || (this._isShown = !0, this._isAnimated() && (this._isTransitioning = !0), this._scrollBar.hide(), document.body.classList.add(ae), this._adjustDialog(), this._setEscapeEvent(), this._setResizeEvent(), $.on(this._dialog, re, (() => {
$.one(this._element, 'mouseup.dismiss.bs.modal', (t => {
t.target === this._element && (this._ignoreBackdropClick = !0);
}));
})), this._showBackdrop((() => this._showElement(t))));
}hide () {
if ( !this._isShown || this._isTransitioning ) return;if ( $.trigger(this._element, 'hide.bs.modal').defaultPrevented ) return;this._isShown = !1;const t = this._isAnimated();t && (this._isTransitioning = !0), this._setEscapeEvent(), this._setResizeEvent(), this._focustrap.deactivate(), this._element.classList.remove(le), $.off(this._element, ne), $.off(this._dialog, re), this._queueCallback((() => this._hideModal()), this._element, t);
}dispose () {
[window, this._dialog].forEach((t => $.off(t, '.bs.modal'))), this._backdrop.dispose(), this._focustrap.deactivate(), super.dispose();
}handleUpdate () {
this._adjustDialog();
}_initializeBackDrop () {
return new Ut({ isVisible: Boolean(this._config.backdrop), isAnimated: this._isAnimated() });
}_initializeFocusTrap () {
return new Qt({ trapElement: this._element });
}_getConfig (t) {
return t = { ...Jt, ...X.getDataAttributes(this._element), ...'object' == typeof t ? t : {} }, h(Gt, t, te), t;
}_showElement (t) {
const e = this._isAnimated(), i = Y.findOne('.modal-body', this._dialog);this._element.parentNode && this._element.parentNode.nodeType === Node.ELEMENT_NODE || document.body.append(this._element), this._element.style.display = 'block', this._element.removeAttribute('aria-hidden'), this._element.setAttribute('aria-modal', !0), this._element.setAttribute('role', 'dialog'), this._element.scrollTop = 0, i && (i.scrollTop = 0), e && f(this._element), this._element.classList.add(le), this._queueCallback((() => {
this._config.focus && this._focustrap.activate(), this._isTransitioning = !1, $.trigger(this._element, 'shown.bs.modal', { relatedTarget: t });
}), this._dialog, e);
}_setEscapeEvent () {
this._isShown ? $.on(this._element, oe, (t => {
this._config.keyboard && t.key === Zt ? (t.preventDefault(), this.hide()) : this._config.keyboard || t.key !== Zt || this._triggerBackdropTransition();
})) : $.off(this._element, oe);
}_setResizeEvent () {
this._isShown ? $.on(window, se, (() => this._adjustDialog())) : $.off(window, se);
}_hideModal () {
this._element.style.display = 'none', this._element.setAttribute('aria-hidden', !0), this._element.removeAttribute('aria-modal'), this._element.removeAttribute('role'), this._isTransitioning = !1, this._backdrop.hide((() => {
document.body.classList.remove(ae), this._resetAdjustments(), this._scrollBar.reset(), $.trigger(this._element, ee);
}));
}_showBackdrop (t) {
$.on(this._element, ne, (t => {
this._ignoreBackdropClick ? this._ignoreBackdropClick = !1 : t.target === t.currentTarget && (!0 === this._config.backdrop ? this.hide() : 'static' === this._config.backdrop && this._triggerBackdropTransition());
})), this._backdrop.show(t);
}_isAnimated () {
return this._element.classList.contains('fade');
}_triggerBackdropTransition () {
if ( $.trigger(this._element, 'hidePrevented.bs.modal').defaultPrevented ) return;const { classList: t, scrollHeight: e, style: i } = this._element, s = e > document.documentElement.clientHeight;!s && 'hidden' === i.overflowY || t.contains(ce) || (s || (i.overflowY = 'hidden'), t.add(ce), this._queueCallback((() => {
t.remove(ce), s || this._queueCallback((() => {
i.overflowY = '';
}), this._dialog);
}), this._dialog), this._element.focus());
}_adjustDialog () {
const t = this._element.scrollHeight > document.documentElement.clientHeight, e = this._scrollBar.getWidth(), i = e > 0;(!i && t && !b() || i && !t && b()) && (this._element.style.paddingLeft = `${e}px`), (i && !t && !b() || !i && t && b()) && (this._element.style.paddingRight = `${e}px`);
}_resetAdjustments () {
this._element.style.paddingLeft = '', this._element.style.paddingRight = '';
} static jQueryInterface (t, e) {
return this.each((function () {
const i = he.getOrCreateInstance(this, t);if ( 'string' == typeof t ) {
if ( void 0 === i[t] ) throw new TypeError(`No method named "${t}"`);i[t](e);
}
}));
}
}$.on(document, 'click.bs.modal.data-api', '[data-bs-toggle="modal"]', (function (t) {
const e = r(this);['A', 'AREA'].includes(this.tagName) && t.preventDefault(), $.one(e, ie, (t => {
t.defaultPrevented || $.one(e, ee, (() => {
d(this) && this.focus();
}));
}));const i = Y.findOne('.modal.show');i && he.getInstance(i).hide(), he.getOrCreateInstance(e).toggle(this);
})), F(he), v(he);const de = 'offcanvas', ue = { backdrop: !0, keyboard: !0, scroll: !1 }, ge = { backdrop: 'boolean', keyboard: 'boolean', scroll: 'boolean' }, _e = 'show', fe = '.offcanvas.show', pe = 'hidden.bs.offcanvas';class me extends R {
constructor (t, e) {
super(t), this._config = this._getConfig(e), this._isShown = !1, this._backdrop = this._initializeBackDrop(), this._focustrap = this._initializeFocusTrap(), this._addEventListeners();
} static get NAME () {
return de;
} static get Default () {
return ue;
}toggle (t) {
return this._isShown ? this.hide() : this.show(t);
}show (t) {
this._isShown || $.trigger(this._element, 'show.bs.offcanvas', { relatedTarget: t }).defaultPrevented || (this._isShown = !0, this._element.style.visibility = 'visible', this._backdrop.show(), this._config.scroll || (new zt).hide(), this._element.removeAttribute('aria-hidden'), this._element.setAttribute('aria-modal', !0), this._element.setAttribute('role', 'dialog'), this._element.classList.add(_e), this._queueCallback((() => {
this._config.scroll || this._focustrap.activate(), $.trigger(this._element, 'shown.bs.offcanvas', { relatedTarget: t });
}), this._element, !0));
}hide () {
this._isShown && ($.trigger(this._element, 'hide.bs.offcanvas').defaultPrevented || (this._focustrap.deactivate(), this._element.blur(), this._isShown = !1, this._element.classList.remove(_e), this._backdrop.hide(), this._queueCallback((() => {
this._element.setAttribute('aria-hidden', !0), this._element.removeAttribute('aria-modal'), this._element.removeAttribute('role'), this._element.style.visibility = 'hidden', this._config.scroll || (new zt).reset(), $.trigger(this._element, pe);
}), this._element, !0)));
}dispose () {
this._backdrop.dispose(), this._focustrap.deactivate(), super.dispose();
}_getConfig (t) {
return t = { ...ue, ...X.getDataAttributes(this._element), ...'object' == typeof t ? t : {} }, h(de, t, ge), t;
}_initializeBackDrop () {
return new Ut({ className: 'offcanvas-backdrop', isVisible: this._config.backdrop, isAnimated: !0, rootElement: this._element.parentNode, clickCallback: () => this.hide() });
}_initializeFocusTrap () {
return new Qt({ trapElement: this._element });
}_addEventListeners () {
$.on(this._element, 'keydown.dismiss.bs.offcanvas', (t => {
this._config.keyboard && 'Escape' === t.key && this.hide();
}));
} static jQueryInterface (t) {
return this.each((function () {
const e = me.getOrCreateInstance(this, t);if ( 'string' == typeof t ) {
if ( void 0 === e[t] || t.startsWith('_') || 'constructor' === t ) throw new TypeError(`No method named "${t}"`);e[t](this);
}
}));
}
}$.on(document, 'click.bs.offcanvas.data-api', '[data-bs-toggle="offcanvas"]', (function (t) {
const e = r(this);if ( ['A', 'AREA'].includes(this.tagName) && t.preventDefault(), u(this) ) return;$.one(e, pe, (() => {
d(this) && this.focus();
}));const i = Y.findOne(fe);i && i !== e && me.getInstance(i).hide(), me.getOrCreateInstance(e).toggle(this);
})), $.on(window, 'load.bs.offcanvas.data-api', (() => Y.find(fe).forEach((t => me.getOrCreateInstance(t).show())))), F(me), v(me);const be = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']), ve = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i, ye = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i, Ee = (t, e) => {
const i = t.nodeName.toLowerCase();if ( e.includes(i) ) return !be.has(i) || Boolean(ve.test(t.nodeValue) || ye.test(t.nodeValue));const s = e.filter((t => t instanceof RegExp));for ( let t = 0, e = s.length;t < e;t++ ) if ( s[t].test(i) ) return !0;return !1;
};function we (t, e, i) {
if ( ! t.length ) return t;if ( i && 'function' == typeof i ) return i(t);const s = (new window.DOMParser).parseFromString(t, 'text/html'), n = [].concat(...s.body.querySelectorAll('*'));for ( let t = 0, i = n.length;t < i;t++ ) {
const i = n[t], s = i.nodeName.toLowerCase();if ( ! Object.keys(e).includes(s) ) {
i.remove();continue;
} const o = [].concat(...i.attributes), r = [].concat(e['*'] || [], e[s] || []);o.forEach((t => {
Ee(t, r) || i.removeAttribute(t.nodeName);
}));
} return s.body.innerHTML;
} const Ae = 'tooltip', Te = new Set(['sanitize', 'allowList', 'sanitizeFn']), Ce = { animation: 'boolean', template: 'string', title: '(string|element|function)', trigger: 'string', delay: '(number|object)', html: 'boolean', selector: '(string|boolean)', placement: '(string|function)', offset: '(array|string|function)', container: '(string|element|boolean)', fallbackPlacements: 'array', boundary: '(string|element)', customClass: '(string|function)', sanitize: 'boolean', sanitizeFn: '(null|function)', allowList: 'object', popperConfig: '(null|object|function)' }, ke = { AUTO: 'auto', TOP: 'top', RIGHT: b() ? 'left' : 'right', BOTTOM: 'bottom', LEFT: b() ? 'right' : 'left' }, Le = { animation: !0, template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>', trigger: 'hover focus', title: '', delay: 0, html: !1, selector: !1, placement: 'top', offset: [0, 0], container: !1, fallbackPlacements: ['top', 'right', 'bottom', 'left'], boundary: 'clippingParents', customClass: '', sanitize: !0, sanitizeFn: null, allowList: { '*': ['class', 'dir', 'id', 'lang', 'role', /^aria-[\w-]*$/i], a: ['target', 'href', 'title', 'rel'], area: [], b: [], br: [], col: [], code: [], div: [], em: [], hr: [], h1: [], h2: [], h3: [], h4: [], h5: [], h6: [], i: [], img: ['src', 'srcset', 'alt', 'title', 'width', 'height'], li: [], ol: [], p: [], pre: [], s: [], small: [], span: [], sub: [], sup: [], strong: [], u: [], ul: [] }, popperConfig: null }, Se = { HIDE: 'hide.bs.tooltip', HIDDEN: 'hidden.bs.tooltip', SHOW: 'show.bs.tooltip', SHOWN: 'shown.bs.tooltip', INSERTED: 'inserted.bs.tooltip', CLICK: 'click.bs.tooltip', FOCUSIN: 'focusin.bs.tooltip', FOCUSOUT: 'focusout.bs.tooltip', MOUSEENTER: 'mouseenter.bs.tooltip', MOUSELEAVE: 'mouseleave.bs.tooltip' }, Oe = 'fade', Ne = 'show', De = 'show', Ie = 'out', Pe = '.tooltip-inner', xe = '.modal', Me = 'hide.bs.modal', je = 'hover', He = 'focus';class $e extends R {
constructor (t, e) {
if ( void 0 === i ) throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t), this._isEnabled = !0, this._timeout = 0, this._hoverState = '', this._activeTrigger = {}, this._popper = null, this._config = this._getConfig(e), this.tip = null, this._setListeners();
} static get Default () {
return Le;
} static get NAME () {
return Ae;
} static get Event () {
return Se;
} static get DefaultType () {
return Ce;
}enable () {
this._isEnabled = !0;
}disable () {
this._isEnabled = !1;
}toggleEnabled () {
this._isEnabled = !this._isEnabled;
}toggle (t) {
if ( this._isEnabled ) {
if (t) {
const e = this._initializeOnDelegatedTarget(t);e._activeTrigger.click = !e._activeTrigger.click, e._isWithActiveTrigger() ? e._enter(null, e) : e._leave(null, e);
} else {
if ( this.getTipElement().classList.contains(Ne) ) return void this._leave(null, this);this._enter(null, this);
}
}
}dispose () {
clearTimeout(this._timeout), $.off(this._element.closest(xe), Me, this._hideModalHandler), this.tip && this.tip.remove(), this._disposePopper(), super.dispose();
}show () {
if ( 'none' === this._element.style.display ) throw new Error('Please use show on visible elements');if ( !this.isWithContent() || !this._isEnabled ) return;const t = $.trigger(this._element, this.constructor.Event.SHOW), e = g(this._element), s = null === e ? this._element.ownerDocument.documentElement.contains(this._element) : e.contains(this._element);if ( t.defaultPrevented || !s ) return;'tooltip' === this.constructor.NAME && this.tip && this.getTitle() !== this.tip.querySelector(Pe).innerHTML && (this._disposePopper(), this.tip.remove(), this.tip = null);const n = this.getTipElement(), o = (t => {
do {
t += Math.floor(1e6 * Math.random());
} while ( document.getElementById(t) );return t;
})(this.constructor.NAME);n.setAttribute('id', o), this._element.setAttribute('aria-describedby', o), this._config.animation && n.classList.add(Oe);const r = 'function' == typeof this._config.placement ? this._config.placement.call(this, n, this._element) : this._config.placement, a = this._getAttachment(r);this._addAttachmentClass(a);const { container: l } = this._config;z.set(n, this.constructor.DATA_KEY, this), this._element.ownerDocument.documentElement.contains(this.tip) || (l.append(n), $.trigger(this._element, this.constructor.Event.INSERTED)), this._popper ? this._popper.update() : this._popper = i.createPopper(this._element, n, this._getPopperConfig(a)), n.classList.add(Ne);const c = this._resolvePossibleFunction(this._config.customClass);c && n.classList.add(...c.split(' ')), 'ontouchstart' in document.documentElement && [].concat(...document.body.children).forEach((t => {
$.on(t, 'mouseover', _);
}));const h = this.tip.classList.contains(Oe);this._queueCallback((() => {
const t = this._hoverState;this._hoverState = null, $.trigger(this._element, this.constructor.Event.SHOWN), t === Ie && this._leave(null, this);
}), this.tip, h);
}hide () {
if ( ! this._popper ) return;const t = this.getTipElement();if ( $.trigger(this._element, this.constructor.Event.HIDE).defaultPrevented ) return;t.classList.remove(Ne), 'ontouchstart' in document.documentElement && [].concat(...document.body.children).forEach((t => $.off(t, 'mouseover', _))), this._activeTrigger.click = !1, this._activeTrigger.focus = !1, this._activeTrigger.hover = !1;const e = this.tip.classList.contains(Oe);this._queueCallback((() => {
this._isWithActiveTrigger() || (this._hoverState !== De && t.remove(), this._cleanTipClass(), this._element.removeAttribute('aria-describedby'), $.trigger(this._element, this.constructor.Event.HIDDEN), this._disposePopper());
}), this.tip, e), this._hoverState = '';
}update () {
null !== this._popper && this._popper.update();
}isWithContent () {
return Boolean(this.getTitle());
}getTipElement () {
if ( this.tip ) return this.tip;const t = document.createElement('div');t.innerHTML = this._config.template;const e = t.children[0];return this.setContent(e), e.classList.remove(Oe, Ne), this.tip = e, this.tip;
}setContent (t) {
this._sanitizeAndSetContent(t, this.getTitle(), Pe);
}_sanitizeAndSetContent (t, e, i) {
const s = Y.findOne(i, t);e || !s ? this.setElementContent(s, e) : s.remove();
}setElementContent (t, e) {
if ( null !== t ) return l(e) ? (e = c(e), void (this._config.html ? e.parentNode !== t && (t.innerHTML = '', t.append(e)) : t.textContent = e.textContent)) : void (this._config.html ? (this._config.sanitize && (e = we(e, this._config.allowList, this._config.sanitizeFn)), t.innerHTML = e) : t.textContent = e);
}getTitle () {
const t = this._element.getAttribute('data-bs-original-title') || this._config.title;return this._resolvePossibleFunction(t);
}updateAttachment (t) {
return 'right' === t ? 'end' : 'left' === t ? 'start' : t;
}_initializeOnDelegatedTarget (t, e) {
return e || this.constructor.getOrCreateInstance(t.delegateTarget, this._getDelegateConfig());
}_getOffset () {
const { offset: t } = this._config;return 'string' == typeof t ? t.split(',').map((t => Number.parseInt(t, 10))) : 'function' == typeof t ? e => t(e, this._element) : t;
}_resolvePossibleFunction (t) {
return 'function' == typeof t ? t.call(this._element) : t;
}_getPopperConfig (t) {
const e = { placement: t,
modifiers: [{ name: 'flip', options: { fallbackPlacements: this._config.fallbackPlacements } }, { name: 'offset', options: { offset: this._getOffset() } }, { name: 'preventOverflow', options: { boundary: this._config.boundary } }, { name: 'arrow', options: { element: `.${this.constructor.NAME}-arrow` } }, { name: 'onChange', enabled: !0, phase: 'afterWrite', fn: t => this._handlePopperPlacementChange(t) }],
onFirstUpdate: t => {
t.options.placement !== t.placement && this._handlePopperPlacementChange(t);
} };return { ...e, ...'function' == typeof this._config.popperConfig ? this._config.popperConfig(e) : this._config.popperConfig };
}_addAttachmentClass (t) {
this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(t)}`);
}_getAttachment (t) {
return ke[t.toUpperCase()];
}_setListeners () {
this._config.trigger.split(' ').forEach((t => {
if ( 'click' === t )$.on(this._element, this.constructor.Event.CLICK, this._config.selector, (t => this.toggle(t)));else if ( 'manual' !== t ) {
const e = t === je ? this.constructor.Event.MOUSEENTER : this.constructor.Event.FOCUSIN, i = t === je ? this.constructor.Event.MOUSELEAVE : this.constructor.Event.FOCUSOUT;$.on(this._element, e, this._config.selector, (t => this._enter(t))), $.on(this._element, i, this._config.selector, (t => this._leave(t)));
}
})), this._hideModalHandler = () => {
this._element && this.hide();
}, $.on(this._element.closest(xe), Me, this._hideModalHandler), this._config.selector ? this._config = { ...this._config, trigger: 'manual', selector: '' } : this._fixTitle();
}_fixTitle () {
const t = this._element.getAttribute('title'), e = typeof this._element.getAttribute('data-bs-original-title');(t || 'string' !== e) && (this._element.setAttribute('data-bs-original-title', t || ''), !t || this._element.getAttribute('aria-label') || this._element.textContent || this._element.setAttribute('aria-label', t), this._element.setAttribute('title', ''));
}_enter (t, e) {
e = this._initializeOnDelegatedTarget(t, e), t && (e._activeTrigger['focusin' === t.type ? He : je] = !0), e.getTipElement().classList.contains(Ne) || e._hoverState === De ? e._hoverState = De : (clearTimeout(e._timeout), e._hoverState = De, e._config.delay && e._config.delay.show ? e._timeout = setTimeout((() => {
e._hoverState === De && e.show();
}), e._config.delay.show) : e.show());
}_leave (t, e) {
e = this._initializeOnDelegatedTarget(t, e), t && (e._activeTrigger['focusout' === t.type ? He : je] = e._element.contains(t.relatedTarget)), e._isWithActiveTrigger() || (clearTimeout(e._timeout), e._hoverState = Ie, e._config.delay && e._config.delay.hide ? e._timeout = setTimeout((() => {
e._hoverState === Ie && e.hide();
}), e._config.delay.hide) : e.hide());
}_isWithActiveTrigger () {
for ( const t in this._activeTrigger ) if ( this._activeTrigger[t] ) return !0;return !1;
}_getConfig (t) {
const e = X.getDataAttributes(this._element);return Object.keys(e).forEach((t => {
Te.has(t) && delete e[t];
})), (t = { ...this.constructor.Default, ...e, ...'object' == typeof t && t ? t : {} }).container = !1 === t.container ? document.body : c(t.container), 'number' == typeof t.delay && (t.delay = { show: t.delay, hide: t.delay }), 'number' == typeof t.title && (t.title = t.title.toString()), 'number' == typeof t.content && (t.content = t.content.toString()), h(Ae, t, this.constructor.DefaultType), t.sanitize && (t.template = we(t.template, t.allowList, t.sanitizeFn)), t;
}_getDelegateConfig () {
const t = {};for ( const e in this._config ) this.constructor.Default[e] !== this._config[e] && (t[e] = this._config[e]);return t;
}_cleanTipClass () {
const t = this.getTipElement(), e = new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`, 'g'), i = t.getAttribute('class').match(e);null !== i && i.length > 0 && i.map((t => t.trim())).forEach((e => t.classList.remove(e)));
}_getBasicClassPrefix () {
return 'bs-tooltip';
}_handlePopperPlacementChange (t) {
const { state: e } = t;e && (this.tip = e.elements.popper, this._cleanTipClass(), this._addAttachmentClass(this._getAttachment(e.placement)));
}_disposePopper () {
this._popper && (this._popper.destroy(), this._popper = null);
} static jQueryInterface (t) {
return this.each((function () {
const e = $e.getOrCreateInstance(this, t);if ( 'string' == typeof t ) {
if ( void 0 === e[t] ) throw new TypeError(`No method named "${t}"`);e[t]();
}
}));
}
}v($e);const Be = { ...$e.Default, placement: 'right', offset: [0, 8], trigger: 'click', content: '', template: '<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>' }, ze = { ...$e.DefaultType, content: '(string|element|function)' }, Re = { HIDE: 'hide.bs.popover', HIDDEN: 'hidden.bs.popover', SHOW: 'show.bs.popover', SHOWN: 'shown.bs.popover', INSERTED: 'inserted.bs.popover', CLICK: 'click.bs.popover', FOCUSIN: 'focusin.bs.popover', FOCUSOUT: 'focusout.bs.popover', MOUSEENTER: 'mouseenter.bs.popover', MOUSELEAVE: 'mouseleave.bs.popover' };class Fe extends $e {
static get Default () {
return Be;
} static get NAME () {
return 'popover';
} static get Event () {
return Re;
} static get DefaultType () {
return ze;
}isWithContent () {
return this.getTitle() || this._getContent();
}setContent (t) {
this._sanitizeAndSetContent(t, this.getTitle(), '.popover-header'), this._sanitizeAndSetContent(t, this._getContent(), '.popover-body');
}_getContent () {
return this._resolvePossibleFunction(this._config.content);
}_getBasicClassPrefix () {
return 'bs-popover';
} static jQueryInterface (t) {
return this.each((function () {
const e = Fe.getOrCreateInstance(this, t);if ( 'string' == typeof t ) {
if ( void 0 === e[t] ) throw new TypeError(`No method named "${t}"`);e[t]();
}
}));
}
}v(Fe);const qe = 'scrollspy', We = { offset: 10, method: 'auto', target: '' }, Ue = { offset: 'number', method: 'string', target: '(string|element)' }, Ke = 'active', Ve = '.nav-link, .list-group-item, .dropdown-item', Xe = 'position';class Ye extends R {
constructor (t, e) {
super(t), this._scrollElement = 'BODY' === this._element.tagName ? window : this._element, this._config = this._getConfig(e), this._offsets = [], this._targets = [], this._activeTarget = null, this._scrollHeight = 0, $.on(this._scrollElement, 'scroll.bs.scrollspy', (() => this._process())), this.refresh(), this._process();
} static get Default () {
return We;
} static get NAME () {
return qe;
}refresh () {
const t = this._scrollElement === this._scrollElement.window ? 'offset' : Xe, e = 'auto' === this._config.method ? t : this._config.method, i = e === Xe ? this._getScrollTop() : 0;this._offsets = [], this._targets = [], this._scrollHeight = this._getScrollHeight(), Y.find(Ve, this._config.target).map((t => {
const s = o(t), n = s ? Y.findOne(s) : null;if (n) {
const t = n.getBoundingClientRect();if ( t.width || t.height ) return [X[e](n).top + i, s];
} return null;
})).filter((t => t)).sort(((t, e) => t[0] - e[0])).forEach((t => {
this._offsets.push(t[0]), this._targets.push(t[1]);
}));
}dispose () {
$.off(this._scrollElement, '.bs.scrollspy'), super.dispose();
}_getConfig (t) {
return (t = { ...We, ...X.getDataAttributes(this._element), ...'object' == typeof t && t ? t : {} }).target = c(t.target) || document.documentElement, h(qe, t, Ue), t;
}_getScrollTop () {
return this._scrollElement === window ? this._scrollElement.pageYOffset : this._scrollElement.scrollTop;
}_getScrollHeight () {
return this._scrollElement.scrollHeight || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
}_getOffsetHeight () {
return this._scrollElement === window ? window.innerHeight : this._scrollElement.getBoundingClientRect().height;
}_process () {
const t = this._getScrollTop() + this._config.offset, e = this._getScrollHeight(), i = this._config.offset + e - this._getOffsetHeight();if ( this._scrollHeight !== e && this.refresh(), t >= i ) {
const t = this._targets[this._targets.length - 1];this._activeTarget !== t && this._activate(t);
} else {
if ( this._activeTarget && t < this._offsets[0] && this._offsets[0] > 0 ) return this._activeTarget = null, void this._clear();for ( let e = this._offsets.length;e--; ) this._activeTarget !== this._targets[e] && t >= this._offsets[e] && (void 0 === this._offsets[e + 1] || t < this._offsets[e + 1]) && this._activate(this._targets[e]);
}
}_activate (t) {
this._activeTarget = t, this._clear();const e = Ve.split(',').map((e => `${e}[data-bs-target="${t}"],${e}[href="${t}"]`)), i = Y.findOne(e.join(','), this._config.target);i.classList.add(Ke), i.classList.contains('dropdown-item') ? Y.findOne('.dropdown-toggle', i.closest('.dropdown')).classList.add(Ke) : Y.parents(i, '.nav, .list-group').forEach((t => {
Y.prev(t, '.nav-link, .list-group-item').forEach((t => t.classList.add(Ke))), Y.prev(t, '.nav-item').forEach((t => {
Y.children(t, '.nav-link').forEach((t => t.classList.add(Ke)));
}));
})), $.trigger(this._scrollElement, 'activate.bs.scrollspy', { relatedTarget: t });
}_clear () {
Y.find(Ve, this._config.target).filter((t => t.classList.contains(Ke))).forEach((t => t.classList.remove(Ke)));
} static jQueryInterface (t) {
return this.each((function () {
const e = Ye.getOrCreateInstance(this, t);if ( 'string' == typeof t ) {
if ( void 0 === e[t] ) throw new TypeError(`No method named "${t}"`);e[t]();
}
}));
}
}$.on(window, 'load.bs.scrollspy.data-api', (() => {
Y.find('[data-bs-spy="scroll"]').forEach((t => new Ye(t)));
})), v(Ye);const Qe = 'active', Ge = 'fade', Ze = 'show', Je = '.active', ti = ':scope > li > .active';class ei extends R {
static get NAME () {
return 'tab';
}show () {
if ( this._element.parentNode && this._element.parentNode.nodeType === Node.ELEMENT_NODE && this._element.classList.contains(Qe) ) return;let t;const e = r(this._element), i = this._element.closest('.nav, .list-group');if (i) {
const e = 'UL' === i.nodeName || 'OL' === i.nodeName ? ti : Je;t = Y.find(e, i), t = t[t.length - 1];
} const s = t ? $.trigger(t, 'hide.bs.tab', { relatedTarget: this._element }) : null;if ( $.trigger(this._element, 'show.bs.tab', { relatedTarget: t }).defaultPrevented || null !== s && s.defaultPrevented ) return;this._activate(this._element, i);const n = () => {
$.trigger(t, 'hidden.bs.tab', { relatedTarget: this._element }), $.trigger(this._element, 'shown.bs.tab', { relatedTarget: t });
};e ? this._activate(e, e.parentNode, n) : n();
}_activate (t, e, i) {
const s = (!e || 'UL' !== e.nodeName && 'OL' !== e.nodeName ? Y.children(e, Je) : Y.find(ti, e))[0], n = i && s && s.classList.contains(Ge), o = () => this._transitionComplete(t, s, i);s && n ? (s.classList.remove(Ze), this._queueCallback(o, t, !0)) : o();
}_transitionComplete (t, e, i) {
if (e) {
e.classList.remove(Qe);const t = Y.findOne(':scope > .dropdown-menu .active', e.parentNode);t && t.classList.remove(Qe), 'tab' === e.getAttribute('role') && e.setAttribute('aria-selected', !1);
}t.classList.add(Qe), 'tab' === t.getAttribute('role') && t.setAttribute('aria-selected', !0), f(t), t.classList.contains(Ge) && t.classList.add(Ze);let s = t.parentNode;if ( s && 'LI' === s.nodeName && (s = s.parentNode), s && s.classList.contains('dropdown-menu') ) {
const e = t.closest('.dropdown');e && Y.find('.dropdown-toggle', e).forEach((t => t.classList.add(Qe))), t.setAttribute('aria-expanded', !0);
}i && i();
} static jQueryInterface (t) {
return this.each((function () {
const e = ei.getOrCreateInstance(this);if ( 'string' == typeof t ) {
if ( void 0 === e[t] ) throw new TypeError(`No method named "${t}"`);e[t]();
}
}));
}
}$.on(document, 'click.bs.tab.data-api', '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]', (function (t) {
['A', 'AREA'].includes(this.tagName) && t.preventDefault(), u(this) || ei.getOrCreateInstance(this).show();
})), v(ei);const ii = 'toast', si = 'hide', ni = 'show', oi = 'showing', ri = { animation: 'boolean', autohide: 'boolean', delay: 'number' }, ai = { animation: !0, autohide: !0, delay: 5e3 };class li extends R {
constructor (t, e) {
super(t), this._config = this._getConfig(e), this._timeout = null, this._hasMouseInteraction = !1, this._hasKeyboardInteraction = !1, this._setListeners();
} static get DefaultType () {
return ri;
} static get Default () {
return ai;
} static get NAME () {
return ii;
}show () {
$.trigger(this._element, 'show.bs.toast').defaultPrevented || (this._clearTimeout(), this._config.animation && this._element.classList.add('fade'), this._element.classList.remove(si), f(this._element), this._element.classList.add(ni), this._element.classList.add(oi), this._queueCallback((() => {
this._element.classList.remove(oi), $.trigger(this._element, 'shown.bs.toast'), this._maybeScheduleHide();
}), this._element, this._config.animation));
}hide () {
this._element.classList.contains(ni) && ($.trigger(this._element, 'hide.bs.toast').defaultPrevented || (this._element.classList.add(oi), this._queueCallback((() => {
this._element.classList.add(si), this._element.classList.remove(oi), this._element.classList.remove(ni), $.trigger(this._element, 'hidden.bs.toast');
}), this._element, this._config.animation)));
}dispose () {
this._clearTimeout(), this._element.classList.contains(ni) && this._element.classList.remove(ni), super.dispose();
}_getConfig (t) {
return t = { ...ai, ...X.getDataAttributes(this._element), ...'object' == typeof t && t ? t : {} }, h(ii, t, this.constructor.DefaultType), t;
}_maybeScheduleHide () {
this._config.autohide && (this._hasMouseInteraction || this._hasKeyboardInteraction || (this._timeout = setTimeout((() => {
this.hide();
}), this._config.delay)));
}_onInteraction (t, e) {
switch ( t.type ) {
case 'mouseover':case 'mouseout':this._hasMouseInteraction = e;break;case 'focusin':case 'focusout':this._hasKeyboardInteraction = e;
} if (e) return void this._clearTimeout();const i = t.relatedTarget;this._element === i || this._element.contains(i) || this._maybeScheduleHide();
}_setListeners () {
$.on(this._element, 'mouseover.bs.toast', (t => this._onInteraction(t, !0))), $.on(this._element, 'mouseout.bs.toast', (t => this._onInteraction(t, !1))), $.on(this._element, 'focusin.bs.toast', (t => this._onInteraction(t, !0))), $.on(this._element, 'focusout.bs.toast', (t => this._onInteraction(t, !1)));
}_clearTimeout () {
clearTimeout(this._timeout), this._timeout = null;
} static jQueryInterface (t) {
return this.each((function () {
const e = li.getOrCreateInstance(this, t);if ( 'string' == typeof t ) {
if ( void 0 === e[t] ) throw new TypeError(`No method named "${t}"`);e[t](this);
}
}));
}
} return F(li), v(li), { Alert: q, Button: U, Carousel: at, Collapse: mt, Dropdown: Ht, Modal: he, Offcanvas: me, Popover: Fe, ScrollSpy: Ye, Tab: ei, Toast: li, Tooltip: $e };
}));
//# sourceMappingURL=bootstrap.min.js.map
@@ -1,62 +0,0 @@
h1{
border-bottom: 2px solid #CCC;
padding-bottom: 10px;
margin-bottom: 30px;
font-size: 25px;
}
h1 .bi-caret-right-fill{
color: rgb(210, 210, 210);
font-size: 25px;
}
h1 a, h1 a:visited{
color: #000;
text-decoration: none;
}
h1 a:hover{
text-decoration: underline;
}
/* ------------------------------------ */
/* Admin
/* ------------------------------------ */
.admin-sidebar{
height: 100%;
width: 260px;
position: fixed;
top: 0;
left: 0;
background-color: #eee;
overflow-x: hidden;
padding-top: 20px;
}
.admin-main{
margin-left: 270px;
padding: 0px 10px;
overflow: hidden;
}
.sidebar-item{
display: block;
padding: 10px;
margin:10px;
text-decoration: none;
color: #000;
border-radius: 5px;
background-color: #dee1e8;
}
.sidebar-item.active{
background-color: #a2abba;
color:white;
}
td{
white-space: nowrap;
}
.count{
float:right;
font-size: 13px;
font-weight: bold;
line-height: 25px;
display: block;
}
-226
View File
@@ -1,226 +0,0 @@
html, body {
font-family: 'Roboto', HelveticaNeue, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#html-login, #body-login, #html-signup, #body-signup, #html-password-recovery, #body-password-recovery,
#html-set-new-password, #body-set-new-password {
height: 100%;
}
#html-login h1, #html-signup h1, #html-password-recovery h1, #html-set-new-password h1{
color: #5a667a;
text-shadow: 1px 1px white;
font-size:23px;
}
#body-legal{
margin-top: 40px;
margin-bottom: 100px;
}
#body-legal h1 {
margin-top: 50px;
text-align: center;
text-transform: uppercase;
font-size: 35px;
}
#body-legal h2{
font-size: 25px;;
margin-top: 50px;
}
#body-legal h3{
font-size:20px;
}
#body-legal h4 {
margin-top: 40px;
margin-bottom: 10px;
}
#body-legal ol > h3{
font-size: 18px;
margin-top: 20px;
margin-left: -25px;
}
#body-legal ul li{
margin-bottom: 10px;
}
.tos-li-head {
font-weight: bold;
display: block;
margin-bottom: 10px;
margin-top: 30px;
}
#body-login, #body-signup, #body-password-recovery, #body-set-new-password {
display: flex;
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
text-align: center;
}
#body-index {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: auto;
}
.form-signin .form-floating:focus-within {
z-index: 2;
}
#login-error-msg, .error-msg, .error {
display: none;
color: red;
border: 1px solid red;
border-radius: 4px;
padding: 9px;
margin-bottom: 15px;
text-align: center;
font-size: 13px;
}
.error{
display: block;
}
.success-msg{
display: none;
color: green;
border: 1px solid green;
border-radius: 4px;
padding: 9px;
margin-bottom: 15px;
text-align: center;
font-size: 13px;
}
@media (min-width: 992px) {
.rounded-lg-3 {
border-radius: .3rem;
}
}
#signup-error-msg {
display: none;
color: red;
border: 1px solid red;
border-radius: 4px;
padding: 9px;
margin-bottom: 15px;
text-align: center;
font-size: 13px;
}
.logo {
border-radius: 3px;
}
.signup-c2a, .login-c2a {
color: #656f7a;
text-align: center;
margin: 0;
font-size: 14px;
text-shadow: 1px 1px #ffffffe3;
}
.signup-c2a a, .login-c2a a, .pass-reco-link {
text-decoration: none;
}
.signup-c2a a:hover, .login-c2a a:hover, .pass-reco-link:hover {
text-decoration: underline;
}
.pass-reco-link{
font-size:14px;
}
.c2a-wrapper {
display: block;
text-align: center;
font-size: 18px;
padding-top: 15px;
padding-bottom: 15px;
border: 1px solid #bfc3cb;
color: #949AA8;
margin-bottom: 0;
border-radius: 6px;
margin-top: 20px;
}
.social-media-icon {
width: 30px;
float: right;
margin-left: 20px;
}
.hero-browser {
margin: 0 auto;
background-color: #c5c7cd;
overflow: hidden;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
box-shadow: 0 0 10px #8b8b8b7a;
}
.hero-browser-buttons {
border-radius: 100%;
width: 10px;
height: 9px;
background-color: #EEE;
float: left;
margin-top: 11px;
margin-right: 8px;
}
.hero-browser-url {
background-color: white;
width: 100%;
text-align: left;
border-radius: 20px;
padding-left: 20px;
margin-left: 15px;
padding: 5px 5px 5px 20px;
font-size: 16px;
font-weight: bold;
color: #3b5f6c;
}
.hero-browser-url-lock {
width: 15px;
height: 15px;
opacity: 0.2;
margin-top: -4px;
margin-right: 10px;
}
#p102xyzname {
display: none;
}
.feature-icon {
width: 50px;
margin-bottom: 20px;
}
.pass-recovery-email-sent{
display:none;
border: 1px solid #00c300;
padding: 20px 15px;
border-radius: 3px;
color: darkgreen;
background: #e5ffe5;
margin-bottom: 20px;
}
.green-1{
background-color: rgb(227, 255, 236);
}
.green-2{
background-color: rgb(139, 228, 168);
}
.green-3{
background-color: rgb(49, 202, 97);
}
@@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>ic_apps_24px</title>
<g fill="#212121" class="nc-icon-wrapper">
<path d="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z"></path>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 333 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24"><title>drag</title><g fill="#212121" class="nc-icon-wrapper"><polygon data-color="color-2" points="2,5 0,5 0,0 5,0 5,2 2,2 "></polygon> <polygon data-color="color-2" points="5,20 0,20 0,15 2,15 2,18 5,18 "></polygon> <polygon data-color="color-2" points="20,5 18,5 18,2 15,2 15,0 20,0 "></polygon> <rect data-color="color-2" x="8" width="4" height="2"></rect> <rect data-color="color-2" x="8" y="18" width="4" height="2"></rect> <rect data-color="color-2" x="18" y="8" width="2" height="4"></rect> <rect data-color="color-2" y="8" width="2" height="4"></rect> <polygon fill="#212121" points="24,16 13,13 16,24 18,20 22,24 24,22 20,18 "></polygon></g></svg>

Before

Width:  |  Height:  |  Size: 739 B

@@ -1,11 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
<title>lock</title>
<g fill="#212121" class="nc-icon-wrapper">
<path
d="M19,10H5a3,3,0,0,0-3,3v8a3,3,0,0,0,3,3H19a3,3,0,0,0,3-3V13A3,3,0,0,0,19,10Zm-7,9a2,2,0,1,1,2-2A2,2,0,0,1,12,19Z"
fill="#212121"></path>
<path data-color="color-2"
d="M18,8H16V6a3.958,3.958,0,0,0-3.911-4h-.042A3.978,3.978,0,0,0,8,5.911V8H6V5.9A5.961,5.961,0,0,1,11.949,0h.061A5.979,5.979,0,0,1,18,6.01Z">
</path>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24"><title>world</title><g fill="#212121" class="nc-icon-wrapper"><path d="M12,0A12,12,0,1,0,24,12,12.013,12.013,0,0,0,12,0Zm0,22a9.965,9.965,0,0,1-7.132-3l1.455-1.222a1,1,0,0,0,.318-1.04l-.7-2.472a1.015,1.015,0,0,0-.231-.409L4.474,12.536l-.187-.319.64-.547,2.487-1.258a1,1,0,0,0,.538-.746l.4-2.74A1,1,0,0,0,8.1,6.1L6.451,4.34a.877.877,0,0,0-.094-.089L6.039,3.99a9.929,9.929,0,0,1,5.54-1.969l.448.745.586,1.713a1.012,1.012,0,0,0,.319.455l2.284,1.837a1,1,0,0,0,.627.221.934.934,0,0,0,.118-.007l3-.356a1.011,1.011,0,0,0,.375-.123l.718-.408A9.981,9.981,0,0,1,12,22Z" fill="#212121"></path><path data-color="color-2" d="M19.894,12.441l-1.135-2.178a1,1,0,0,0-.655-.511l-2.357-.564a.994.994,0,0,0-.564.03L12.956,10a1,1,0,0,0-.451.319l-1.338,1.973a1.25,1.25,0,0,0-.073.927l.812,2.062L11.406,17a.985.985,0,0,0,.188.844l.657,1.735a1,1,0,0,0,.772.364l.064,0,2.339-.15a1,1,0,0,0,.618-.267l1.762-1.641a1,1,0,0,0,.233-.325l1.882-4.25A1,1,0,0,0,19.894,12.441Z"></path></g></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

-183
View File
@@ -1,183 +0,0 @@
$(document).ready(function () {
if ( page === 'login' )
{
$('#email_or_username').focus();
}
else if ( page === 'password-recovery' )
{
$('#email_or_username').focus();
}
else if ( page === 'set-new-password' )
{
$('#password').focus();
}
});
window.is_email = (email) => {
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
};
$('#login-submit-btn').on('click', function () {
const email_username = $('#email_or_username').val();
const password = $('#password').val();
let data;
if ( is_email(email_username) ) {
data = JSON.stringify({
email: email_username,
password: password,
});
} else {
data = JSON.stringify({
username: email_username,
password: password,
});
}
$('#login-error-msg').hide();
$.ajax({
url: '/login',
type: 'POST',
async: false,
contentType: 'application/json',
data: data,
success: function (data) {
localStorage.setItem('auth_token', data.token);
localStorage.setItem('auth_username', data.user.username);
window.location.replace('/');
},
error: function (err) {
$('#login-error-msg').html(err.responseText);
$('#login-error-msg').fadeIn();
},
});
});
$('#pass-recovery-submit-btn').on('click', function (e) {
const email_username = $('#email_or_username').val();
let data;
if ( is_email(email_username) ) {
data = JSON.stringify({
email: email_username,
});
} else {
data = JSON.stringify({
username: email_username,
});
}
$('#login-error-msg').hide();
$.ajax({
url: '/send-pass-recovery-email',
type: 'POST',
async: false,
contentType: 'application/json',
data: data,
success: function (data) {
$('#email_or_username').val('');
$('.pass-recovery-email-sent').html(data);
$('.pass-recovery-email-sent').fadeIn();
},
error: function (err) {
$('#login-error-msg').html(err.responseText);
$('#login-error-msg').fadeIn();
},
});
});
$('.signup-btn').on('click', function (e) {
let urlquery = new URLSearchParams(window.location.search);
let tok;
if ( urlquery.has('tok') )
{
tok = urlquery.get('tok');
}
// todo do some basic validation client-side
//Username
let username = $('#username').val();
//Email
let email = $('#email').val();
//Password
let password = $('#password').val();
//xyzname
let p102xyzname = $('#p102xyzname').val();
// disable 'Create Account' button
$('.signup-btn').prop('disabled', true);
$.ajax({
url: '/signup',
type: 'POST',
async: true,
contentType: 'application/json',
data: JSON.stringify({
username: username,
email: email,
password: password,
uuid: tok,
p102xyzname: p102xyzname,
}),
success: function (data) {
localStorage.setItem('auth_token', data.token);
localStorage.setItem('auth_username', data.user.username);
window.location.replace('/');
},
error: function (err) {
$('#signup-error-msg').html(err.responseText);
$('#signup-error-msg').fadeIn();
// re-enable 'Create Account' button
$('.signup-btn').prop('disabled', false);
},
});
});
$('.signup-form, .login-form, .pass-recovery-form, .set-password-form').on('submit', function (e) {
e.preventDefault();
e.stopPropagation();
return false;
});
$('#set-new-pass-submit-btn').on('click', function (e) {
// todo do some basic validation client-side
//Password
let password = $('#password').val();
let token = $('#token').val();
let user_id = $('#user_id').val();
// disable submit button
$('#set-new-pass-submit-btn').prop('disabled', true);
$.ajax({
url: '/set-pass-using-token',
type: 'POST',
async: true,
contentType: 'application/json',
data: JSON.stringify({
password: password,
token: token,
user_id: user_id,
}),
success: function (data) {
$('.success-msg').html('Password updated. <a href="/login"><strong>Log in</strong></a>.');
$('.error-msg').hide();
$('.success-msg').fadeIn();
$('#password').val('');
},
error: function (err) {
$('.error-msg').html(err.responseText);
$('.error-msg').fadeIn();
// re-enable 'Create Account' button
$('#set-new-pass-submit-btn').prop('disabled', false);
},
});
});
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
-37
View File
@@ -1,37 +0,0 @@
!function (e, t) {
'object' == typeof exports && 'undefined' != typeof module ? t(exports) : 'function' == typeof define && define.amd ? define(['exports'], t) : t((e = e || self).timeago = {});
}(this, function (e) {
'use strict';var r = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'];var a = ['秒', '分钟', '小时', '天', '周', '个月', '年'];function t (e, t) {
n[e] = t;
} function i (e) {
return n[e] || n.en_US;
} var n = {}, f = [60, 60, 24, 7, 365 / 7 / 12, 12];function o (e) {
return e instanceof Date ? e : !isNaN(e) || /^\d+$/.test(e) ? new Date(parseInt(e)) : (e = (e || '').trim().replace(/\.\d+/, '').replace(/-/, '/').replace(/-/, '/').replace(/(\d)T(\d)/, '$1 $2').replace(/Z/, ' UTC').replace(/([+-]\d\d):?(\d\d)/, ' $1$2'), new Date(e));
} function d (e, t) {
for ( var n = e < 0 ? 1 : 0, r = e = Math.abs(e), a = 0;e >= f[a] && a < f.length;a++ )e /= f[a];return (0 === (a *= 2) ? 9 : 1) < (e = Math.floor(e)) && (a += 1), t(e, a, r)[n].replace('%s', e.toString());
} function l (e, t) {
return ((t ? o(t) : new Date) - o(e)) / 1e3;
} var s = 'timeago-id';function h (e) {
return parseInt(e.getAttribute(s));
} var p = {}, v = function (e) {
clearTimeout(e), delete p[e];
};function m (e, t, n, r) {
v(h(e));var a = r.relativeDate, i = r.minInterval, o = l(t, a);e.innerText = d(o, n);var u, c = setTimeout(function () {
m(e, t, n, r);
}, Math.min(1e3 * Math.max(function (e) {
for ( var t = 1, n = 0, r = Math.abs(e);e >= f[n] && n < f.length;n++ )e /= f[n], t *= f[n];return r = (r %= t) ? t - r : t, Math.ceil(r);
}(o), i || 1), 2147483647));p[c] = 0, u = c, e.setAttribute(s, u);
}t('en_US', function (e, t) {
if ( 0 === t ) return ['just now', 'right now'];var n = r[Math.floor(t / 2)];return 1 < e && (n += 's'), [`${e} ${n} ago`, `in ${ e } ${ n}`];
}), t('zh_CN', function (e, t) {
if ( 0 === t ) return ['刚刚', '片刻后'];var n = a[~~(t / 2)];return [`${e} ${n}`, `${e } ${ n }`];
}), e.cancel = function (e) {
e ? v(h(e)) : Object.keys(p).forEach(v);
}, e.format = function (e, t, n) {
return d(l(e, n && n.relativeDate), i(t));
}, e.register = t, e.render = function (e, t, n) {
var r = e.length ? e : [e];return r.forEach(function (e) {
m(e, e.getAttribute('datetime'), i(t), n || {});
}), r;
}, Object.defineProperty(e, '__esModule', { value: !0 });
});
@@ -1,49 +0,0 @@
const { createTransformedValues, DO_NOT_DEFINE } = require('../util/objutil');
const BaseService = require('./BaseService');
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');
class AWSSecretsPopulator extends BaseService {
async _run_as_early_as_possible () {
const secret_name = 'puter-secrets';
const client = new SecretsManagerClient({
region: 'us-west-2',
});
let response;
try {
response = await client.send(new GetSecretValueCommand({
SecretId: secret_name,
VersionStage: 'AWSCURRENT', // VersionStage defaults to AWSCURRENT if unspecified
}));
const secretOverlay = (JSON.parse(response.SecretString));
const config = this.global_config;
config.__set_config_object__(createTransformedValues(this.global_config, {
mutateValue: (value, { state }) => {
const path = state.keys.join('.'); // or jq
if ( value === '$__AWS_SECRET__' ) {
if ( ! secretOverlay[path] ) {
throw new Error('Value wants an AWS Secrets key value, but no such value is in AWS secrets!');
}
return secretOverlay[path];
} else {
return DO_NOT_DEFINE;
}
},
doNotProcessArrays: true,
}));
} catch ( error ) {
// Just dont do anything
}
}
}
module.exports = {
AWSSecretsPopulator,
};
@@ -1,87 +0,0 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const BaseService = require('./BaseService');
// Symbol used to indicate a denial of service instruction in anomaly handling.
const DENY_SERVICE_INSTRUCTION = Symbol('DENY_SERVICE_INSTRUCTION');
/**
* @class AnomalyService
* @extends BaseService
* @description The AnomalyService class is responsible for managing and processing anomaly detection types and configurations.
* It allows the registration of different types with associated handlers, enabling the detection of anomalies based on specified criteria.
*/
class AnomalyService extends BaseService {
/**
* AnomalyService class that extends BaseService and provides methods
* for registering anomaly types and handling incoming data for those anomalies.
*
* The register method allows the registration of different anomaly types
* and their respective configurations, including custom handlers for data
* evaluation. It supports two modes of operation: a direct handler or
* a threshold-based evaluation.
*/
_construct () {
this.types = {};
}
/**
* Registers a new type with the service, including its configuration and handler.
*
* @param {string} type - The name of the type to register.
* @param {Object} config - The configuration object for the type.
* @param {Function} [config.handler] - An optional handler function for the type.
* @param {number} [config.high] - An optional threshold value; triggers the handler if exceeded.
*
* @returns {void}
*/
register (type, config) {
const type_instance = {
config,
};
if ( config.handler ) {
type_instance.handler = config.handler;
} else if ( config.high ) {
type_instance.handler = data => {
if ( data.value > config.high ) {
return new Set([DENY_SERVICE_INSTRUCTION]);
}
};
}
this.types[type] = type_instance;
}
/**
* Creates a note of the specified type with the provided data.
* See `groups_user_hour` in GroupService for an example.
*
* @param {*} id - The identifier of the type to create a note for.
* @param {*} data - The data to process with the type's handler.
* @returns
*/
async note (id, data) {
const type = this.types[id];
if ( ! type ) return;
return type.handler(data);
}
}
module.exports = {
AnomalyService,
DENY_SERVICE_INSTRUCTION,
};
@@ -1,133 +0,0 @@
import { describe, expect, it, vi } from 'vitest';
import { createTestKernel } from '../../tools/test.mjs';
import { AnomalyService, DENY_SERVICE_INSTRUCTION } from './AnomalyService';
describe('AnomalyService', async () => {
const testKernel = await createTestKernel({
serviceMap: {
'anomaly': AnomalyService,
},
initLevelString: 'init',
});
const anomalyService = testKernel.services!.get('anomaly') as any;
it('should be instantiated', () => {
expect(anomalyService).toBeInstanceOf(AnomalyService);
});
it('should have types object', () => {
expect(anomalyService.types).toBeDefined();
expect(typeof anomalyService.types).toBe('object');
});
it('should register a type with handler', () => {
const handler = vi.fn();
anomalyService.register('test-type', { handler });
expect(anomalyService.types['test-type']).toBeDefined();
expect(anomalyService.types['test-type'].handler).toBe(handler);
});
it('should register a type with threshold', () => {
anomalyService.register('threshold-type', { high: 100 });
expect(anomalyService.types['threshold-type']).toBeDefined();
expect(anomalyService.types['threshold-type'].handler).toBeDefined();
expect(typeof anomalyService.types['threshold-type'].handler).toBe('function');
});
it('should call handler when noting anomaly', async () => {
const handler = vi.fn().mockReturnValue('result');
anomalyService.register('callable-type', { handler });
const data = { test: 'data' };
const result = await anomalyService.note('callable-type', data);
expect(handler).toHaveBeenCalledWith(data);
expect(result).toBe('result');
});
it('should return undefined for unregistered type', async () => {
const result = await anomalyService.note('non-existent-type', {});
expect(result).toBeUndefined();
});
it('should trigger threshold handler when value exceeds high', async () => {
anomalyService.register('high-threshold', { high: 50 });
const result = await anomalyService.note('high-threshold', { value: 75 });
expect(result).toBeDefined();
expect(result).toBeInstanceOf(Set);
expect(result.has(DENY_SERVICE_INSTRUCTION)).toBe(true);
});
it('should not trigger threshold handler when value is below high', async () => {
anomalyService.register('low-threshold', { high: 100 });
const result = await anomalyService.note('low-threshold', { value: 50 });
expect(result).toBeUndefined();
});
it('should handle multiple type registrations', () => {
anomalyService.register('type1', { handler: () => {} });
anomalyService.register('type2', { high: 100 });
anomalyService.register('type3', { handler: () => {} });
expect(anomalyService.types['type1']).toBeDefined();
expect(anomalyService.types['type2']).toBeDefined();
expect(anomalyService.types['type3']).toBeDefined();
});
it('should store config in type instance', () => {
const config = { high: 200, custom: 'value' };
anomalyService.register('config-type', config);
expect(anomalyService.types['config-type'].config).toBe(config);
});
it('should handle exact threshold value', async () => {
anomalyService.register('exact-threshold', { high: 100 });
const result = await anomalyService.note('exact-threshold', { value: 100 });
// Threshold uses > not >=, so equal should not trigger
expect(result).toBeUndefined();
});
it('should handle value just over threshold', async () => {
anomalyService.register('just-over', { high: 100 });
const result = await anomalyService.note('just-over', { value: 100.1 });
expect(result).toBeDefined();
expect(result).toBeInstanceOf(Set);
expect(result.has(DENY_SERVICE_INSTRUCTION)).toBe(true);
});
it('should allow custom handler to return any value', async () => {
const customResult = { custom: 'result', data: [1, 2, 3] };
anomalyService.register('custom-return', {
handler: () => customResult
});
const result = await anomalyService.note('custom-return', {});
expect(result).toBe(customResult);
});
});
describe('DENY_SERVICE_INSTRUCTION', () => {
it('should be a symbol', () => {
expect(typeof DENY_SERVICE_INSTRUCTION).toBe('symbol');
});
it('should be unique', () => {
const anotherSymbol = Symbol('DENY_SERVICE_INSTRUCTION');
expect(DENY_SERVICE_INSTRUCTION).not.toBe(anotherSymbol);
});
});
@@ -67,8 +67,8 @@ class BootScriptService extends BaseService {
const scope = {
runner: 'boot-script',
'end-puter-process': ({ args }) => {
const svc_shutdown = this.services.get('shutdown');
svc_shutdown.shutdown(args[0]);
console.log('shutting down puter: BootScriptService');
process.exit(0);
},
};
@@ -1,59 +0,0 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const BaseService = require('./BaseService');
/**
* @class HelloWorldService
* @extends BaseService
* @description This class extends the BaseService and provides methods to get the version
* of the service and to generate a greeting message. The greeting message can be personalized
* based on the input subject.
*/
class HelloWorldService extends BaseService {
static IMPLEMENTS = {
'version': {
/**
* Returns the current version of the service.
*
* @returns {string} The version string.
*/
get_version () {
return 'v1.0.0';
},
},
'hello-world': {
/**
* Greets the user with a customizable message.
*
* @param {Object} options - The options object.
* @param {string} [options.subject] - The subject of the greeting. If not provided, defaults to "World".
* @returns {string} The greeting message.
*/
async greet ({ subject }) {
if ( subject ) {
return `Hello, ${subject}!`;
}
return 'Hello, World!';
},
},
};
}
module.exports = { HelloWorldService };
@@ -1,42 +0,0 @@
import { describe, expect, it } from 'vitest';
import { createTestKernel } from '../../tools/test.mjs';
import { HelloWorldService } from './HelloWorldService';
describe('HelloWorldService', async () => {
const testKernel = await createTestKernel({
serviceMap: {
'hello-world': HelloWorldService,
},
initLevelString: 'init',
});
const helloWorldService = testKernel.services!.get('hello-world') as any;
it('should be instantiated', () => {
expect(helloWorldService).toBeInstanceOf(HelloWorldService);
});
it('should return version', () => {
const version = helloWorldService.as('version').get_version();
expect(version).toBe('v1.0.0');
});
it('should greet without subject', async () => {
const greeting = await helloWorldService.as('hello-world').greet({});
expect(greeting).toBe('Hello, World!');
});
it('should greet with subject', async () => {
const greeting = await helloWorldService.as('hello-world').greet({ subject: 'Alice' });
expect(greeting).toBe('Hello, Alice!');
});
it('should greet with different subjects', async () => {
const greeting1 = await helloWorldService.as('hello-world').greet({ subject: 'Bob' });
const greeting2 = await helloWorldService.as('hello-world').greet({ subject: 'Charlie' });
expect(greeting1).toBe('Hello, Bob!');
expect(greeting2).toBe('Hello, Charlie!');
});
});
@@ -209,18 +209,9 @@ class NotificationService extends BaseService {
);
for ( const n of notifications ) {
n.value = this.db.case({
mysql: () => n.value,
/**
* Adjusts the value of a notification based on the database type.
*
* This method modifies the value of a notification to be JSON parsed
* if the database is not MySQL.
*
* @returns {Object} The adjusted notification value.
*/
otherwise: () => JSON.parse(n.value ?? '{}'),
})();
if ( !n.value || typeof (n.value) === 'string' ) {
n.value = JSON.parse(n.value || '{}');
}
}
const client_safe_notifications = [];
@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { APIError } = require('openai');
const APIError = require('../api/APIError');
const eggspress = require('../api/eggspress');
const configurable_auth = require('../middleware/configurable_auth');
const BaseService = require('./BaseService');
+6 -9
View File
@@ -238,15 +238,12 @@ class SessionService extends BaseService {
);
if ( ! session ) return;
session.last_store = Date.now();
session.meta = this.db.case({
mysql: () => session.meta,
/**
* Parses session metadata based on the database type.
* @param {Object} session - The session object from the database.
* @returns {Object} The parsed session metadata.
*/
otherwise: () => JSON.parse(session.meta ?? '{}'),
})();
// MariaDB and SQLite store JSON as string.
if ( typeof session.meta === 'string' ) {
session.meta = JSON.parse(session.meta ?? '{}');
}
const user = await get_user({ id: session.user_id });
session.user_uid = user?.uuid;
return session;
+9 -15
View File
@@ -58,11 +58,9 @@ class ShareService extends BaseService {
);
for ( const share of relevant_shares ) {
share.data = this.db.case({
mysql: () => share.data,
otherwise: () =>
JSON.parse(share.data ?? '{}'),
})();
if ( !share.data || typeof (share.data) === 'string' ) {
share.data = JSON.parse(share.data || '{}');
}
const issuer_user = await get_user({
id: share.issuer_user_id,
@@ -172,11 +170,9 @@ class ShareService extends BaseService {
throw APIError.create('share_expired');
}
share.data = this.db.case({
mysql: () => share.data,
otherwise: () =>
JSON.parse(share.data ?? '{}'),
})();
if ( !share.data || typeof (share.data) === 'string' ) {
share.data = JSON.parse(share.data || '{}');
}
const actor = Actor.adapt(req.actor ?? req.user);
if ( ! actor ) {
@@ -240,11 +236,9 @@ class ShareService extends BaseService {
throw APIError.create('share_expired');
}
share.data = this.db.case({
mysql: () => share.data,
otherwise: () =>
JSON.parse(share.data ?? '{}'),
})();
if ( !share.data || typeof (share.data) === 'string' ) {
share.data = JSON.parse(share.data || '{}');
}
const actor = Actor.adapt(req.actor ?? req.user);
if ( ! actor ) {
@@ -1,37 +0,0 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const BaseService = require('./BaseService');
/**
* Service responsible for handling graceful system shutdown operations.
* Extends BaseService to provide shutdown functionality with optional reason and exit code.
* Ensures proper cleanup and logging when the application needs to terminate.
* @class ShutdownService
* @extends BaseService
*/
class ShutdownService extends BaseService {
shutdown ({ reason, code } = {}) {
this.log.info(`Puter is shutting down: ${reason ?? 'no reason provided'}`);
process.stdout.write('\x1B[0m\r\n');
process.exit(code ?? 0);
}
}
module.exports = { ShutdownService };
@@ -1,80 +0,0 @@
import { describe, expect, it, vi } from 'vitest';
import { createTestKernel } from '../../tools/test.mjs';
import { ShutdownService } from './ShutdownService';
describe('ShutdownService', async () => {
const testKernel = await createTestKernel({
serviceMap: {
shutdown: ShutdownService,
},
initLevelString: 'construct',
});
const shutdownService = testKernel.services!.get('shutdown') as ShutdownService;
// Mock the logger for the service
shutdownService.log = {
info: vi.fn(),
error: vi.fn(),
warn: vi.fn(),
debug: vi.fn(),
};
it('should be instantiated', () => {
expect(shutdownService).toBeInstanceOf(ShutdownService);
});
it('should have shutdown method', () => {
expect(typeof shutdownService.shutdown).toBe('function');
});
it('should call process.exit when shutdown is called', () => {
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation((() => {}) as any);
shutdownService.shutdown({ reason: 'test shutdown', code: 0 });
expect(exitSpy).toHaveBeenCalledWith(0);
expect(stdoutSpy).toHaveBeenCalled();
exitSpy.mockRestore();
stdoutSpy.mockRestore();
});
it('should use default exit code when not provided', () => {
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation((() => {}) as any);
shutdownService.shutdown({ reason: 'test' });
expect(exitSpy).toHaveBeenCalledWith(0);
exitSpy.mockRestore();
stdoutSpy.mockRestore();
});
it('should use custom exit code when provided', () => {
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation((() => {}) as any);
shutdownService.shutdown({ reason: 'error', code: 1 });
expect(exitSpy).toHaveBeenCalledWith(1);
exitSpy.mockRestore();
stdoutSpy.mockRestore();
});
it('should work without any parameters', () => {
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation((() => {}) as any);
shutdownService.shutdown();
expect(exitSpy).toHaveBeenCalledWith(0);
exitSpy.mockRestore();
stdoutSpy.mockRestore();
});
});
@@ -16,7 +16,6 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { TechnicalError } = require('../errors/TechnicalError');
const { quot } = require('@heyputer/putility').libs.string;
/**
@@ -30,7 +29,7 @@ class StrategizedService {
const key = args.strategy_key;
if ( !args.default_strategy && !my_config.hasOwnProperty(key) ) {
this.initError = new TechnicalError(`Must specify ${quot(key)} for service ${quot(name)}.`);
this.initError = new Error(`Must specify ${quot(key)} for service ${quot(name)}.`);
return;
}
@@ -40,7 +39,7 @@ class StrategizedService {
const strategy_key = my_config[key] ?? args.default_strategy;
if ( ! args.strategies.hasOwnProperty(strategy_key) ) {
this.initError = new TechnicalError(`Invalid ${key} ${quot(strategy_key)} for service ${quot(name)}.`);
this.initError = new Error(`Invalid ${key} ${quot(strategy_key)} for service ${quot(name)}.`);
return;
}
const [cls, cls_args] = args.strategies[strategy_key];
+6 -8
View File
@@ -1,11 +1,9 @@
# PuterAIModule
# AI Services
PuterAIModule class extends AdvancedBase to manage and register various AI services.
This module handles the initialization and registration of multiple AI-related services
including text processing, speech synthesis, chat completion, and image generation.
Services are conditionally registered based on configuration settings, allowing for
flexible deployment with different AI providers like AWS, OpenAI, Claude, Together AI,
Mistral, Groq, and XAI.
CoreModule registers the backend AI services directly.
These services cover chat, image generation, video generation, speech, and OCR.
Some providers are only registered when the corresponding configuration is present,
including AWS, OpenAI, and ElevenLabs integrations.
## Services
@@ -275,4 +273,4 @@ Returns the default model identifier for the XAI service
This module has external relative imports. When these are
removed it may become possible to move this module to an
extension.
extension.
+4 -10
View File
@@ -1131,16 +1131,10 @@ class AuthService extends BaseService {
if ( seen.has(session.uuid) ) {
continue;
}
session.meta = this.db.case({
mysql: () => session.meta,
/**
* This method is responsible for authenticating a user or app using a token. It decodes the token and checks if it's valid, then returns an appropriate actor object based on the token type.
*
* @param {string} token - The user or app access token.
* @returns {Actor} - Actor object representing the authenticated user or app.
*/
otherwise: () => JSON.parse(session.meta ?? '{}'),
})();
if ( !session.meta || typeof (session.meta) === 'string' ) {
session.meta = JSON.parse(session.meta || '{}');
}
sessions.push(session);
};
+54 -51
View File
@@ -20,11 +20,33 @@ const APIError = require('../../api/APIError');
const { redisClient } = require('../../clients/redis/redisSingleton');
const { setRedisCacheValue } = require('../../clients/redis/cacheUpdate.js');
const { GroupRedisCacheSpace } = require('./GroupRedisCacheSpace.js');
const Group = require('../../entities/Group');
const { DENY_SERVICE_INSTRUCTION } = require('../AnomalyService');
const BaseService = require('../BaseService');
const { DB_WRITE } = require('../database/consts');
const { v4: uuidv4 } = require('uuid');
const create_group_entity = (svc_group, values) => ({
values,
async fetch_members () {
if ( Object.prototype.hasOwnProperty.call(this.values, 'members') ) {
return this.values.members;
}
const members = await svc_group.list_members({ uid: this.values.uid });
this.values.members = members;
return members;
},
async get_client_value (options = {}) {
if ( options.members ) {
await this.fetch_members();
}
return {
uid: this.values.uid,
metadata: this.values.metadata,
...(options.members ? { members: this.values.members } : {}),
};
},
});
/**
* The GroupService class provides functionality for managing groups within the Puter application.
* It extends the BaseService to handle group-related operations such as creation, retrieval,
@@ -36,7 +58,6 @@ class GroupService extends BaseService {
/**
* Initializes the GroupService by setting up the database connection and registering
* with the anomaly service for monitoring group creation rates.
*
* @memberof GroupService
* @instance
@@ -44,11 +65,6 @@ class GroupService extends BaseService {
_init () {
this.db = this.services.get('database').get(DB_WRITE, 'permissions');
this.kvkey = uuidv4();
const svc_anomaly = this.services.get('anomaly');
svc_anomaly.register('groups-user-hour', {
high: 20,
});
}
/**
@@ -68,14 +84,13 @@ class GroupService extends BaseService {
const [group] =
await this.db.read('SELECT * FROM `group` WHERE uid=?', [uid]);
if ( ! group ) return;
group.extra = this.db.case({
mysql: () => group.extra,
otherwise: () => JSON.parse(group.extra),
})();
group.metadata = this.db.case({
mysql: () => group.metadata,
otherwise: () => JSON.parse(group.metadata),
})();
if ( !group.extra || typeof (group.extra) === 'string' ) {
group.extra = JSON.parse(group.extra || '{}');
}
if ( !group.metadata || typeof (group.metadata) === 'string' ) {
group.metadata = JSON.parse(group.metadata || '{}');
}
return group;
}
@@ -107,13 +122,7 @@ class GroupService extends BaseService {
[owner_user_id],
);
const svc_anomaly = this.services.get('anomaly');
const anomaly = await svc_anomaly.note('groups-user-hour', {
value: n_groups,
user_id: owner_user_id,
});
if ( anomaly && anomaly.has(DENY_SERVICE_INSTRUCTION) ) {
if ( Number(n_groups) > 20 ) {
throw APIError.create('too_many_requests');
}
@@ -147,16 +156,14 @@ class GroupService extends BaseService {
[owner_user_id],
);
for ( const group of groups ) {
group.extra = this.db.case({
mysql: () => group.extra,
otherwise: () => JSON.parse(group.extra),
})();
group.metadata = this.db.case({
mysql: () => group.metadata,
otherwise: () => JSON.parse(group.metadata),
})();
if ( !group.extra || typeof (group.extra) === 'string' ) {
group.extra = JSON.parse(group.extra || '{}');
}
if ( !group.metadata || typeof (group.metadata) === 'string' ) {
group.metadata = JSON.parse(group.metadata || '{}');
}
}
return groups.map(g => Group(g));
return groups.map(g => create_group_entity(this, g));
}
/**
@@ -173,16 +180,14 @@ class GroupService extends BaseService {
[user_id],
);
for ( const group of groups ) {
group.extra = this.db.case({
mysql: () => group.extra,
otherwise: () => JSON.parse(group.extra),
})();
group.metadata = this.db.case({
mysql: () => group.metadata,
otherwise: () => JSON.parse(group.metadata),
})();
if ( !group.extra || typeof (group.extra) === 'string' ) {
group.extra = JSON.parse(group.extra || '{}');
}
if ( !group.metadata || typeof (group.metadata) === 'string' ) {
group.metadata = JSON.parse(group.metadata || '{}');
}
}
return groups.map(g => Group(g));
return groups.map(g => create_group_entity(this, g));
}
/**
@@ -198,7 +203,7 @@ class GroupService extends BaseService {
const cached_groups = await redisClient.get(cacheKey);
if ( cached_groups ) {
try {
return JSON.parse(cached_groups).map(g => Group(g));
return JSON.parse(cached_groups).map(g => create_group_entity(this, g));
} catch (e) {
// no op cache is in an invalid state
}
@@ -211,16 +216,14 @@ class GroupService extends BaseService {
public_group_uids,
);
for ( const group of groups ) {
group.extra = this.db.case({
mysql: () => group.extra,
otherwise: () => JSON.parse(group.extra),
})();
group.metadata = this.db.case({
mysql: () => group.metadata,
otherwise: () => JSON.parse(group.metadata),
})();
if ( !group.metadata || typeof (group.metadata) === 'string' ) {
group.metadata = JSON.parse(group.metadata || '{}');
}
if ( !group.extra || typeof (group.extra) === 'string' ) {
group.extra = JSON.parse(group.extra || '{}');
}
}
const group_entities = groups.map(g => Group(g));
const group_entities = groups.map(g => create_group_entity(this, g));
await setRedisCacheValue(cacheKey, JSON.stringify(groups), {
ttlSeconds: 60,
eventData: groups,
@@ -401,10 +401,9 @@ class PermissionService extends BaseService {
// Return the first matching permission where the
// issuer also has the permission granted
for ( const row of rows ) {
row.extra = this.db.case({
mysql: () => row.extra,
otherwise: () => JSON.parse(row.extra ?? '{}'),
})();
if ( !row.extra || typeof (row.extra) === 'string' ) {
row.extra = JSON.parse(row.extra || '{}');
}
const issuer_actor = new Actor({
type: new UserActorType({
@@ -1,182 +0,0 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { bench, describe } from 'vitest';
const { FileTracker } = require('./FileTracker');
// Helper to create a tracker with some access history
const createTrackerWithHistory = (accessCount) => {
const tracker = new FileTracker({ key: 'test-key', size: 1024 });
for ( let i = 0; i < accessCount; i++ ) {
tracker.touch();
}
return tracker;
};
describe('FileTracker - Construction', () => {
bench('create new FileTracker', () => {
new FileTracker({ key: `test-key-${ Math.random()}`, size: 1024 });
});
bench('create multiple FileTrackers', () => {
for ( let i = 0; i < 100; i++ ) {
new FileTracker({ key: `key-${i}`, size: i * 100 });
}
});
});
describe('FileTracker - touch() operation', () => {
bench('touch() on new tracker', () => {
const tracker = new FileTracker({ key: 'test', size: 1024 });
for ( let i = 0; i < 1000; i++ ) {
tracker.touch();
}
});
bench('touch() with EWMA calculation', () => {
const tracker = new FileTracker({ key: 'test', size: 1024 });
// Pre-warm with some touches
for ( let i = 0; i < 10; i++ ) {
tracker.touch();
}
// Benchmark steady-state touches
for ( let i = 0; i < 1000; i++ ) {
tracker.touch();
}
});
});
describe('FileTracker - score calculation', () => {
bench('score on fresh tracker', () => {
const tracker = new FileTracker({ key: 'test', size: 1024 });
tracker.touch(); // Need at least one touch for meaningful score
for ( let i = 0; i < 1000; i++ ) {
void tracker.score;
}
});
bench('score on tracker with history (10 accesses)', () => {
const tracker = createTrackerWithHistory(10);
for ( let i = 0; i < 1000; i++ ) {
void tracker.score;
}
});
bench('score on tracker with history (100 accesses)', () => {
const tracker = createTrackerWithHistory(100);
for ( let i = 0; i < 1000; i++ ) {
void tracker.score;
}
});
});
describe('FileTracker - age calculation', () => {
bench('age getter', () => {
const tracker = new FileTracker({ key: 'test', size: 1024 });
for ( let i = 0; i < 10000; i++ ) {
void tracker.age;
}
});
});
describe('FileTracker - Cache eviction simulation', () => {
bench('compare scores of multiple trackers', () => {
// Simulate cache with 100 items
const trackers = [];
for ( let i = 0; i < 100; i++ ) {
const tracker = new FileTracker({ key: `file-${i}`, size: i * 100 });
// Simulate varying access patterns
const accessCount = Math.floor(Math.random() * 20);
for ( let j = 0; j < accessCount; j++ ) {
tracker.touch();
}
trackers.push(tracker);
}
// Find lowest score (eviction candidate)
for ( let i = 0; i < 100; i++ ) {
let minScore = Infinity;
let evictCandidate = null;
for ( const tracker of trackers ) {
const score = tracker.score;
if ( score < minScore ) {
minScore = score;
evictCandidate = tracker;
}
}
}
});
bench('sort trackers by score (eviction ordering)', () => {
const trackers = [];
for ( let i = 0; i < 50; i++ ) {
const tracker = new FileTracker({ key: `file-${i}`, size: i * 100 });
for ( let j = 0; j < i % 10; j++ ) {
tracker.touch();
}
trackers.push(tracker);
}
// Sort by score
for ( let i = 0; i < 10; i++ ) {
[...trackers].sort((a, b) => a.score - b.score);
}
});
});
describe('FileTracker - Real-world access patterns', () => {
bench('hot file pattern (frequent access)', () => {
const tracker = new FileTracker({ key: 'hot-file', size: 1024 });
for ( let i = 0; i < 1000; i++ ) {
tracker.touch();
if ( i % 10 === 0 ) {
void tracker.score;
}
}
});
bench('cold file pattern (rare access)', () => {
const tracker = new FileTracker({ key: 'cold-file', size: 1024 });
tracker.touch();
for ( let i = 0; i < 1000; i++ ) {
void tracker.score;
void tracker.age;
}
});
bench('mixed access with score checks', () => {
const trackers = [];
for ( let i = 0; i < 20; i++ ) {
trackers.push(new FileTracker({ key: `file-${i}`, size: 1024 }));
}
for ( let i = 0; i < 500; i++ ) {
// Random access
const idx = Math.floor(Math.random() * trackers.length);
trackers[idx].touch();
// Periodic eviction check
if ( i % 50 === 0 ) {
for ( const t of trackers ) {
void t.score;
}
}
}
});
});
@@ -1,106 +0,0 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* FileTracker
*
* Tracks information about cached files for LRU and LFU eviction.
*/
const { EWMA, normalize } = require('../../util/opmath');
/**
* @class FileTracker
* @description A class that manages and tracks metadata for cached files, including their lifecycle phases,
* access patterns, and timing information. Used for implementing cache eviction strategies like LRU (Least
* Recently Used) and LFU (Least Frequently Used). Maintains state about file size, access count, last access
* time, and creation time to help determine which files should be evicted from cache when necessary.
*/
class FileTracker {
static PHASE_PENDING = { label: 'pending' };
static PHASE_PRECACHE = { label: 'precache' };
static PHASE_DISK = { label: 'disk' };
static PHASE_GONE = { label: 'gone' };
constructor ({ key, size }) {
this.phase = this.constructor.PHASE_PENDING;
this.avg_access_delta = new EWMA({
initial: 1000,
alpha: 0.2,
});
this.access_count = 0;
this.last_access = 0;
this.size = size;
this.key = key;
this.birth = Date.now();
}
/**
* Calculates a score for cache eviction prioritization
* Combines access frequency and recency using weighted formula
* Higher scores indicate files that should be kept in cache
*
* @returns {number} Eviction score - higher values mean higher priority to keep
*/
get score () {
const weight_LFU = 0.5;
const weight_LRU = 0.5;
const access_freq = 1 / this.avg_access_delta.get();
const n_access_freq = normalize({
// "once a second" is a high value
high_value: 0.001,
}, access_freq);
const recency = Date.now() - this.last_access;
const n_recency = normalize({
// "20 seconds ago" is pretty recent
high_value: 0.00005,
}, 1 / recency);
return 0 +
(weight_LFU * n_access_freq) +
(weight_LRU * n_recency);
}
/**
* Gets the age of the file in milliseconds since creation
* @returns {number} Time in milliseconds since this tracker was created
*/
get age () {
return Date.now() - this.birth;
}
/**
* Updates the access count and timestamp for this file
* Increments access_count and sets last_access to current time
* Used to track file usage for cache eviction scoring
*/
touch () {
const last_last_access = this.last_access;
this.access_count++;
this.last_access = Date.now();
const access_delta = this.last_access - last_last_access;
this.avg_access_delta.put(access_delta);
}
}
module.exports = {
FileTracker,
};
@@ -280,10 +280,9 @@ export const processSharesSequence = new Sequence({
});
}
app.metadata = db.case({
mysql: () => app.metadata,
otherwise: () => JSON.parse(app.metadata ?? '{}'),
})();
if ( !app.metadata || typeof (app.metadata) === 'string' ) {
app.metadata = JSON.parse(app.metadata || '{}');
}
item.app = app;
}
@@ -111,12 +111,14 @@ const PERMISSION_SCANNERS = [
const has_terminal = reading_has_terminal({ reading: issuer_reading });
const db = a.iget('db');
const rows = await db.read('SELECT * FROM `access_token_permissions` ' +
const rows = await db.read(
'SELECT * FROM `access_token_permissions` ' +
'WHERE `token_uid` = ? AND `permission` = ?',
[
token,
permission,
]);
[
token,
permission,
],
);
// Token must have permission
if ( ! rows[0] ) continue;
@@ -222,19 +224,20 @@ const PERMISSION_SCANNERS = [
if ( permission_options.length > 1 ) {
sql_perm = `(${sql_perm})`;
}
const rows = await db.read('SELECT p.permission, p.user_id, p.group_id, p.extra FROM `user_to_group_permissions` p ' +
const rows = await db.read(
'SELECT p.permission, p.user_id, p.group_id, p.extra FROM `user_to_group_permissions` p ' +
'JOIN `jct_user_group` ug ON p.group_id = ug.group_id ' +
`WHERE ug.user_id = ? AND ${sql_perm}`,
[
actor.type.user.id,
...permission_options,
]);
[
actor.type.user.id,
...permission_options,
],
);
for ( const row of rows ) {
row.extra = db.case({
mysql: () => row.extra,
otherwise: () => JSON.parse(row.extra ?? '{}'),
})();
if ( !row.extra || typeof (row.extra) === 'string' ) {
row.extra = JSON.parse(row.extra || '{}');
}
const issuer_actor = new Actor({
type: new UserActorType({
@@ -369,21 +372,22 @@ const PERMISSION_SCANNERS = [
if ( permission_options.length > 1 ) sql_perm = `(${sql_perm})`;
// SELECT permission
const rows = await db.read('SELECT * FROM `user_to_app_permissions` ' +
const rows = await db.read(
'SELECT * FROM `user_to_app_permissions` ' +
`WHERE \`user_id\` = ? AND \`app_id\` = ? AND ${
sql_perm}`,
[
actor.type.user.id,
actor.type.app.id,
...permission_options,
]);
[
actor.type.user.id,
actor.type.app.id,
...permission_options,
],
);
if ( rows[0] ) {
const row = rows[0];
row.extra = db.case({
mysql: () => row.extra,
otherwise: () => JSON.parse(row.extra ?? '{}'),
})();
if ( !row.extra || typeof (row.extra) === 'string' ) {
row.extra = JSON.parse(row.extra || '{}');
}
const issuer_actor = actor.get_related_actor(UserActorType);
const issuer_reading = await a.icall('scan', issuer_actor, row.permission);
const has_terminal = reading_has_terminal({ reading: issuer_reading });
@@ -418,20 +422,21 @@ const PERMISSION_SCANNERS = [
if ( permission_options.length > 1 ) sql_perm = `(${sql_perm})`;
// SELECT permission
const rows = await db.read('SELECT * FROM `dev_to_app_permissions` ' +
const rows = await db.read(
'SELECT * FROM `dev_to_app_permissions` ' +
`WHERE \`app_id\` = ? AND ${
sql_perm}`,
[
actor.type.app.id,
...permission_options,
]);
[
actor.type.app.id,
...permission_options,
],
);
if ( rows[0] ) {
const row = rows[0];
row.extra = db.case({
mysql: () => row.extra,
otherwise: () => JSON.parse(row.extra ?? '{}'),
})();
if ( !row.extra || typeof (row.extra) === 'string' ) {
row.extra = JSON.parse(row.extra || '{}');
}
const issuer_user = await get_user({ id: row.user_id });
const issuer_actor = Actor.adapt(issuer_user);
const issuer_reading = await a.icall('scan', issuer_actor, row.permission);
-8
View File
@@ -42,10 +42,7 @@ const main = async () => {
BroadcastModule,
TestDriversModule,
TestConfigModule,
PuterAIModule,
InternetModule,
DevelopmentModule,
DNSModule,
} = (await import('@heyputer/backend')).default;
const k = new Kernel({
@@ -60,12 +57,7 @@ const main = async () => {
k.add_module(new BroadcastModule());
k.add_module(new TestDriversModule());
k.add_module(new TestConfigModule());
k.add_module(new PuterAIModule());
k.add_module(new InternetModule());
k.add_module(new DNSModule());
if ( process.env.UNSAFE_PUTER_DEV ) {
k.add_module(new DevelopmentModule());
}
k.boot();
};