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
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
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 |
@@ -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);
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||