hotfix-feat(dataset): implement file upload with validation and error handling

- Implemented file upload functionality for datasets using multipart requests.
- Added file size and type validation using VineJS.
- Added file name length validation.
- Added file scan to remove infected files.
- Implemented aggregated upload limit to prevent exceeding the server's capacity.
- Added error handling for file upload failures, including temporary file cleanup.
- Updated the `DatasetController` to handle file uploads, validation, and database transactions.
- Updated the `bodyparser.ts` config to process the file upload manually.
- Updated the `api.ts` routes to fetch the statistic data.
- Updated the `main.ts` store to fetch the statistic data.
- Updated the `Dashboard.vue` to display the submitters only for administrator role.
- Updated the `CardBoxWidget.vue` to display the submitters.
- Updated the `ServerError.vue` to use the LayoutGuest.vue.
- Updated the `AuthController.ts` and `start/routes.ts` to handle the database connection errors.
- Updated the `app/exceptions/handler.ts` to handle the database connection errors.
- Updated the `package.json` to use the correct version of the `@adonisjs/bodyparser`.
This commit is contained in:
Kaimbacher 2025-03-26 14:19:06 +01:00
parent a25f8bf6f7
commit b93e46207f
15 changed files with 637 additions and 200 deletions

View file

@ -5,7 +5,7 @@ import BackupCode from '#models/backup_code';
// import InvalidCredentialException from 'App/Exceptions/InvalidCredentialException';
import { authValidator } from '#validators/auth';
import hash from '@adonisjs/core/services/hash';
import db from '@adonisjs/lucid/services/db';
import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider';
// import { Authenticator } from '@adonisjs/auth';
// import { LoginState } from 'Contracts/enums';
@ -29,6 +29,10 @@ export default class AuthController {
const { email, password } = request.only(['email', 'password']);
try {
await db.connection().rawQuery('SELECT 1')
// // attempt to verify credential and login user
// await auth.use('web').attempt(email, plainPassword);
@ -51,6 +55,9 @@ export default class AuthController {
await auth.use('web').login(user);
} catch (error) {
if (error.code === 'ECONNREFUSED') {
throw error
}
// if login fails, return vague form message and redirect back
session.flash('message', 'Your username, email, or password is incorrect');
return response.redirect().back();

View file

@ -40,12 +40,28 @@ import path from 'path';
import { Exception } from '@adonisjs/core/exceptions';
import { MultipartFile } from '@adonisjs/core/types/bodyparser';
import * as crypto from 'crypto';
// import MimeType from '#models/mime_type';
import { pipeline } from 'node:stream/promises';
import { createWriteStream } from 'node:fs';
import type { Multipart } from '@adonisjs/bodyparser';
import * as fs from 'fs';
import { join, isAbsolute } from 'node:path';
import type { BodyParserConfig } from '#models/types';
import { createId } from '@paralleldrive/cuid2';
import { tmpdir } from 'node:os';
import config from '@adonisjs/core/services/config';
interface Dictionary {
[index: string]: string;
}
import vine, { SimpleMessagesProvider, errors } from '@vinejs/vine';
export default class DatasetController {
/**
* Bodyparser config
*/
config: BodyParserConfig = config.get('bodyparser');
public async index({ auth, request, inertia }: HttpContext) {
const user = (await User.find(auth.user?.id)) as User;
const page = request.input('page', 1);
@ -401,22 +417,140 @@ export default class DatasetController {
return response.redirect().back();
}
/**
* Returns the tmp path for storing the files temporarly
*/
private getTmpPath(config: BodyParserConfig['multipart']): string {
if (typeof config.tmpFileName === 'function') {
const tmpPath = config.tmpFileName();
return isAbsolute(tmpPath) ? tmpPath : join(tmpdir(), tmpPath);
}
return join(tmpdir(), createId());
}
/**
* Returns config for a given type
*/
private getConfigFor<K extends keyof BodyParserConfig>(type: K): BodyParserConfig[K] {
const config = this.config[type];
return config;
}
private parseBytesSize(size: string): number {
const units = {
kb: 1024,
mb: 1024 * 1024,
gb: 1024 * 1024 * 1024,
tb: 1024 * 1024 * 1024 * 1024,
};
const match = size.match(/^(\d+)(kb|mb|gb|tb)$/i); // Regex to match size format
if (!match) {
throw new Error('Invalid size format');
}
const [, value, unit] = match;
return parseInt(value) * units[unit.toLowerCase()];
}
public async store({ auth, request, response, session }: HttpContext) {
// node ace make:validator CreateDataset
// At the top of the store() method, declare an array to hold temporary file paths
const uploadedTmpFiles: string[] = [];
// Aggregated limit example (adjust as needed)
const multipartConfig = this.getConfigFor('multipart');
const aggregatedLimit = multipartConfig.limit ? this.parseBytesSize(multipartConfig.limit) : 100 * 1024 * 1024;
// const aggregatedLimit = 200 * 1024 * 1024;
let totalUploadedSize = 0;
// Helper function to format bytes as human-readable text
function formatBytes(bytes: number): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// const enabledExtensions = await this.getEnabledExtensions();
const multipart: Multipart = request.multipart;
multipart.onFile('files', { deferValidations: true }, async (part) => {
// Attach an individual file size accumulator if needed
let fileUploadedSize = 0;
// Simply accumulate the size in on('data') without performing the expensive check per chunk
part.on('data', (chunk) => {
// reporter(chunk);
// Increase counters using the chunk length
fileUploadedSize += chunk.length;
});
// After the file is completely read, update the global counter and check aggregated limit
part.on('end', () => {
totalUploadedSize += fileUploadedSize;
part.file.size = fileUploadedSize;
// Record the temporary file path
if (part.file.tmpPath) {
uploadedTmpFiles.push(part.file.tmpPath);
}
if (totalUploadedSize > aggregatedLimit) {
// Clean up all temporary files if aggregate limit is exceeded
uploadedTmpFiles.forEach((tmpPath) => {
try {
fs.unlinkSync(tmpPath);
} catch (cleanupError) {
console.error('Error cleaning up temporary file:', cleanupError);
}
});
const error = new errors.E_VALIDATION_ERROR({
'upload error': `Aggregated upload limit of ${formatBytes(aggregatedLimit)} exceeded. The total size of files being uploaded would exceed the limit.`,
});
request.multipart.abort(error);
}
});
part.on('error', (error) => {
// fileUploadError = error;
request.multipart.abort(error);
});
// await pipeline(part, createWriteStream(filePath));
// return { filePath };
// Process file with error handling
try {
// Extract extension from the client file name, e.g. "Tethys 5 - Ampflwang_dataset.zip"
const ext = path.extname(part.file.clientName).replace('.', '');
// Attach the extracted extension to the file object for later use
part.file.extname = ext;
const tmpPath = this.getTmpPath(multipartConfig);
(part.file as any).tmpPath = tmpPath;
const writeStream = createWriteStream(tmpPath);
await pipeline(part, writeStream);
} catch (error) {
request.multipart.abort(new errors.E_VALIDATION_ERROR({ 'upload error': error.message }));
}
});
try {
// Step 2 - Validate request body against the schema
// await request.validate({ schema: newDatasetSchema, messages: this.messages });
// await request.validate(CreateDatasetValidator);
await request.validateUsing(createDatasetValidator);
// console.log({ payload });
await multipart.process();
// // Instead of letting an error abort the controller, check if any error occurred
// if (fileUploadError) {
// // Flash the error and return an inertia view that shows the error message.
// session.flash('errors', { 'upload error': [fileUploadError.message] });
// return response.redirect().back();
// }
} catch (error) {
// Step 3 - Handle errors
// return response.badRequest(error.messages);
throw error;
// This is where you'd expect to catch any errors.
session.flash('errors', error.messages);
return response.redirect().back();
}
let trx: TransactionClientContract | null = null;
try {
await request.validateUsing(createDatasetValidator);
trx = await db.transaction();
const user = (await User.find(auth.user?.id)) as User;
@ -425,6 +559,14 @@ export default class DatasetController {
await trx.commit();
console.log('Dataset and related models created successfully');
} catch (error) {
// Clean up temporary files if validation or later steps fail
uploadedTmpFiles.forEach((tmpPath) => {
try {
fs.unlinkSync(tmpPath);
} catch (cleanupError) {
console.error('Error cleaning up temporary file:', cleanupError);
}
});
if (trx !== null) {
await trx.rollback();
}
@ -437,8 +579,12 @@ export default class DatasetController {
return response.redirect().toRoute('dataset.list');
// return response.redirect().back();
}
private async createDatasetAndAssociations(user: User, request: HttpContext['request'], trx: TransactionClientContract) {
private async createDatasetAndAssociations(
user: User,
request: HttpContext['request'],
trx: TransactionClientContract,
// uploadedFiles: Array<MultipartFile>,
) {
// Create a new instance of the Dataset model:
const dataset = new Dataset();
dataset.type = request.input('type');

View file

@ -0,0 +1,43 @@
// import { Exception } from '@adonisjs/core/exceptions'
import { HttpContext, ExceptionHandler } from '@adonisjs/core/http';
export default class DbHandlerException extends ExceptionHandler {
// constructor() {
// super(Logger)
// }
async handle(error: any, ctx: HttpContext) {
// Check for AggregateError type
if (error.type === 'AggregateError' && error.aggregateErrors) {
const dbErrors = error.aggregateErrors.some((err: any) => err.code === 'ECONNREFUSED' && err.port === 5432);
if (dbErrors) {
return ctx.response.status(503).json({
status: 'error',
message: 'PostgreSQL database connection failed. Please ensure the database service is running.',
details: {
code: error.code,
type: error.type,
ports: error.aggregateErrors.map((err: any) => ({
port: err.port,
address: err.address,
})),
},
});
}
}
// Handle simple ECONNREFUSED errors
if (error.code === 'ECONNREFUSED') {
return ctx.response.status(503).json({
status: 'error',
message: 'Database connection failed. Please ensure PostgreSQL is running.',
code: error.code,
});
}
return super.handle(error, ctx);
}
static status = 500;
}

View file

@ -46,6 +46,7 @@ export default class HttpExceptionHandler extends ExceptionHandler {
// return view.render('./errors/server-error', { error });
// },
// };
protected statusPages: Record<StatusPageRange, StatusPageRenderer> = {
'404': (error, { inertia }) => {
return inertia.render('Errors/ServerError', {
@ -60,7 +61,45 @@ export default class HttpExceptionHandler extends ExceptionHandler {
code: error.status,
});
},
'500..599': (error, { inertia }) => inertia.render('Errors/ServerError', { error: error.message, code: error.status }),
// '500': (error, { inertia }) => {
// return inertia.render('Errors/postgres_error', {
// status: 'error',
// message: 'PostgreSQL database connection failed. Please ensure the database service is running.',
// details: {
// code: error.code,
// type: error.status,
// ports: error.errors.map((err: any) => ({
// port: err.port,
// address: err.address,
// })),
// },
// });
// },
'500..599': (error, { inertia }) => {
if (error.code === 'ECONNREFUSED') {
const dbErrors = error.errors.some((err: any) => err.code === 'ECONNREFUSED' && err.port === 5432);
if (dbErrors) {
return inertia.render('Errors/postgres_error', {
status: 'error',
message: 'PostgreSQL database connection failed. Please ensure the database service is running.',
details: {
code: error.code,
type: error.status,
ports: error.errors.map((err: any) => ({
port: err.port,
address: err.address,
})),
},
});
}
} else {
return inertia.render('Errors/ServerError', {
error: error.message,
code: error.status,
});
}
},
};
// constructor() {
@ -68,7 +107,7 @@ export default class HttpExceptionHandler extends ExceptionHandler {
// }
public async handle(error: any, ctx: HttpContext) {
const { response, request, session } = ctx;
const { response, request, session, inertia } = ctx;
/**
* Handle failed authentication attempt
@ -82,6 +121,47 @@ export default class HttpExceptionHandler extends ExceptionHandler {
// return response.redirect('/dashboard');
// }
// Handle Axios errors
if (error.code === 'ECONNREFUSED') {
const dbErrors = error.errors.some((err: any) => err.code === 'ECONNREFUSED' && err.port === 5432);
if (dbErrors) {
// return ctx.response.status(503).json({
// status: 'error',
// message: 'PostgreSQL database connection failed. Please ensure the database service is running.',
// details: {
// code: error.code,
// type: error.status,
// ports: error.errors.map((err: any) => ({
// port: err.port,
// address: err.address,
// })),
// },
// });
// return inertia.render('Errors/postgres_error', {
// status: 'error',
// message: 'PostgreSQL database connection failed. Please ensure the database service is running.',
// details: {
// code: error.code,
// type: error.status,
// ports: error.errors.map((err: any) => ({
// port: err.port,
// address: err.address,
// })),
// },
// });
}
}
// Handle simple ECONNREFUSED errors
// if (error.code === 'ECONNREFUSED') {
// return ctx.response.status(503).json({
// status: 'error',
// message: 'Database connection failed. Please ensure PostgreSQL is running.',
// code: error.code,
// });
// }
// https://github.com/inertiajs/inertia-laravel/issues/56
// let test = response.getStatus(); //200
// let header = request.header('X-Inertia'); // true
@ -98,12 +178,21 @@ export default class HttpExceptionHandler extends ExceptionHandler {
// ->toResponse($request)
// ->setStatusCode($response->status());
}
// Handle simple ECONNREFUSED errors
// if (error.code === 'ECONNREFUSED') {
// return ctx.response.status(503).json({
// status: 'error',
// message: 'Database connection failed. Please ensure PostgreSQL is running.',
// code: error.code,
// });
// }
// Dynamically change the error templates based on the absence of X-Inertia header
// if (!ctx.request.header('X-Inertia')) {
// this.statusPages = {
// '401..403': (error, { view }) => view.render('./errors/unauthorized', { error }),
// '404': (error, { view }) => view.render('./errors/not-found', { error }),
// '500..599': (error, { view }) => view.render('./errors/server-error', { error }),
// '401..403': (error, { inertia }) => inertia.render('Errors/ServerError', { error: error.message, code: error.status }),
// '404': (error, { inertia }) => inertia.render('Errors/ServerError', { error: error.message, code: error.status }),
// '500..599': (error, { inertia }) => inertia.render('Errors/ServerError', { error: error.message, code: error.status }),
// };
// }

57
app/models/types.ts Normal file
View file

@ -0,0 +1,57 @@
/**
* Qs module config
*/
type QueryStringConfig = {
depth?: number
allowPrototypes?: boolean
plainObjects?: boolean
parameterLimit?: number
arrayLimit?: number
ignoreQueryPrefix?: boolean
delimiter?: RegExp | string
allowDots?: boolean
charset?: 'utf-8' | 'iso-8859-1' | undefined
charsetSentinel?: boolean
interpretNumericEntities?: boolean
parseArrays?: boolean
comma?: boolean
}
/**
* Base config used by all types
*/
type BodyParserBaseConfig = {
encoding: string
limit: string | number
types: string[]
}
/**
* Body parser config for parsing JSON requests
*/
export type BodyParserJSONConfig = BodyParserBaseConfig & {
strict: boolean
convertEmptyStringsToNull: boolean
}
/**
* Parser config for parsing form data
*/
export type BodyParserFormConfig = BodyParserBaseConfig & {
queryString: QueryStringConfig
convertEmptyStringsToNull: boolean
}
/**
* Parser config for parsing raw body (untouched)
*/
export type BodyParserRawConfig = BodyParserBaseConfig
/**
* Body parser config for all supported form types
*/
export type BodyParserConfig = {
allowedMethods: string[]
json: BodyParserJSONConfig
form: BodyParserFormConfig
raw: BodyParserRawConfig
multipart: BodyParserMultipartConfig
}

View file

@ -128,7 +128,7 @@ allowedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'],
| projects/:id/file
| ```
*/
processManually: [],
processManually: ['/submitter/dataset/submit'],
/*
|--------------------------------------------------------------------------
@ -185,8 +185,8 @@ allowedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'],
| and fields data.
|
*/
// limit: '20mb',
limit: env.get('UPLOAD_LIMIT', '513mb'),
limit: '513mb',
//limit: env.get('UPLOAD_LIMIT', '513mb'),
/*
|--------------------------------------------------------------------------

280
package-lock.json generated
View file

@ -9,7 +9,7 @@
"version": "1.0.0",
"dependencies": {
"@adonisjs/auth": "^9.2.4",
"@adonisjs/bodyparser": "^10.0.3",
"@adonisjs/bodyparser": "^10.0.1",
"@adonisjs/core": "^6.17.0",
"@adonisjs/cors": "^2.2.1",
"@adonisjs/drive": "^3.2.0",
@ -1912,9 +1912,9 @@
"license": "BSD-3-Clause"
},
"node_modules/@humanwhocodes/retry": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
"integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
"integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=18.18"
@ -2541,9 +2541,9 @@
}
},
"node_modules/@pkgr/core": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
"integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.0.tgz",
"integrity": "sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==",
"dev": true,
"license": "MIT",
"engines": {
@ -2836,9 +2836,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.36.0.tgz",
"integrity": "sha512-jgrXjjcEwN6XpZXL0HUeOVGfjXhPyxAbbhD0BlXUB+abTOpbPiN5Wb3kOT7yb+uEtATNYF5x5gIfwutmuBA26w==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.37.0.tgz",
"integrity": "sha512-l7StVw6WAa8l3vA1ov80jyetOAEo1FtHvZDbzXDO/02Sq/QVvqlHkYoFwDJPIMj0GKiistsBudfx5tGFnwYWDQ==",
"cpu": [
"arm"
],
@ -2849,9 +2849,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.36.0.tgz",
"integrity": "sha512-NyfuLvdPdNUfUNeYKUwPwKsE5SXa2J6bCt2LdB/N+AxShnkpiczi3tcLJrm5mA+eqpy0HmaIY9F6XCa32N5yzg==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.37.0.tgz",
"integrity": "sha512-6U3SlVyMxezt8Y+/iEBcbp945uZjJwjZimu76xoG7tO1av9VO691z8PkhzQ85ith2I8R2RddEPeSfcbyPfD4hA==",
"cpu": [
"arm64"
],
@ -2862,9 +2862,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.36.0.tgz",
"integrity": "sha512-JQ1Jk5G4bGrD4pWJQzWsD8I1n1mgPXq33+/vP4sk8j/z/C2siRuxZtaUA7yMTf71TCZTZl/4e1bfzwUmFb3+rw==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.37.0.tgz",
"integrity": "sha512-+iTQ5YHuGmPt10NTzEyMPbayiNTcOZDWsbxZYR1ZnmLnZxG17ivrPSWFO9j6GalY0+gV3Jtwrrs12DBscxnlYA==",
"cpu": [
"arm64"
],
@ -2875,9 +2875,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.36.0.tgz",
"integrity": "sha512-6c6wMZa1lrtiRsbDziCmjE53YbTkxMYhhnWnSW8R/yqsM7a6mSJ3uAVT0t8Y/DGt7gxUWYuFM4bwWk9XCJrFKA==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.37.0.tgz",
"integrity": "sha512-m8W2UbxLDcmRKVjgl5J/k4B8d7qX2EcJve3Sut7YGrQoPtCIQGPH5AMzuFvYRWZi0FVS0zEY4c8uttPfX6bwYQ==",
"cpu": [
"x64"
],
@ -2888,9 +2888,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.36.0.tgz",
"integrity": "sha512-KXVsijKeJXOl8QzXTsA+sHVDsFOmMCdBRgFmBb+mfEb/7geR7+C8ypAml4fquUt14ZyVXaw2o1FWhqAfOvA4sg==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.37.0.tgz",
"integrity": "sha512-FOMXGmH15OmtQWEt174v9P1JqqhlgYge/bUjIbiVD1nI1NeJ30HYT9SJlZMqdo1uQFyt9cz748F1BHghWaDnVA==",
"cpu": [
"arm64"
],
@ -2901,9 +2901,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.36.0.tgz",
"integrity": "sha512-dVeWq1ebbvByI+ndz4IJcD4a09RJgRYmLccwlQ8bPd4olz3Y213uf1iwvc7ZaxNn2ab7bjc08PrtBgMu6nb4pQ==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.37.0.tgz",
"integrity": "sha512-SZMxNttjPKvV14Hjck5t70xS3l63sbVwl98g3FlVVx2YIDmfUIy29jQrsw06ewEYQ8lQSuY9mpAPlmgRD2iSsA==",
"cpu": [
"x64"
],
@ -2914,9 +2914,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.36.0.tgz",
"integrity": "sha512-bvXVU42mOVcF4le6XSjscdXjqx8okv4n5vmwgzcmtvFdifQ5U4dXFYaCB87namDRKlUL9ybVtLQ9ztnawaSzvg==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.37.0.tgz",
"integrity": "sha512-hhAALKJPidCwZcj+g+iN+38SIOkhK2a9bqtJR+EtyxrKKSt1ynCBeqrQy31z0oWU6thRZzdx53hVgEbRkuI19w==",
"cpu": [
"arm"
],
@ -2927,9 +2927,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.36.0.tgz",
"integrity": "sha512-JFIQrDJYrxOnyDQGYkqnNBtjDwTgbasdbUiQvcU8JmGDfValfH1lNpng+4FWlhaVIR4KPkeddYjsVVbmJYvDcg==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.37.0.tgz",
"integrity": "sha512-jUb/kmn/Gd8epbHKEqkRAxq5c2EwRt0DqhSGWjPFxLeFvldFdHQs/n8lQ9x85oAeVb6bHcS8irhTJX2FCOd8Ag==",
"cpu": [
"arm"
],
@ -2940,9 +2940,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.36.0.tgz",
"integrity": "sha512-KqjYVh3oM1bj//5X7k79PSCZ6CvaVzb7Qs7VMWS+SlWB5M8p3FqufLP9VNp4CazJ0CsPDLwVD9r3vX7Ci4J56A==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.37.0.tgz",
"integrity": "sha512-oNrJxcQT9IcbcmKlkF+Yz2tmOxZgG9D9GRq+1OE6XCQwCVwxixYAa38Z8qqPzQvzt1FCfmrHX03E0pWoXm1DqA==",
"cpu": [
"arm64"
],
@ -2953,9 +2953,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.36.0.tgz",
"integrity": "sha512-QiGnhScND+mAAtfHqeT+cB1S9yFnNQ/EwCg5yE3MzoaZZnIV0RV9O5alJAoJKX/sBONVKeZdMfO8QSaWEygMhw==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.37.0.tgz",
"integrity": "sha512-pfxLBMls+28Ey2enpX3JvjEjaJMBX5XlPCZNGxj4kdJyHduPBXtxYeb8alo0a7bqOoWZW2uKynhHxF/MWoHaGQ==",
"cpu": [
"arm64"
],
@ -2966,9 +2966,9 @@
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.36.0.tgz",
"integrity": "sha512-1ZPyEDWF8phd4FQtTzMh8FQwqzvIjLsl6/84gzUxnMNFBtExBtpL51H67mV9xipuxl1AEAerRBgBwFNpkw8+Lg==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.37.0.tgz",
"integrity": "sha512-yCE0NnutTC/7IGUq/PUHmoeZbIwq3KRh02e9SfFh7Vmc1Z7atuJRYWhRME5fKgT8aS20mwi1RyChA23qSyRGpA==",
"cpu": [
"loong64"
],
@ -2979,9 +2979,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.36.0.tgz",
"integrity": "sha512-VMPMEIUpPFKpPI9GZMhJrtu8rxnp6mJR3ZzQPykq4xc2GmdHj3Q4cA+7avMyegXy4n1v+Qynr9fR88BmyO74tg==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.37.0.tgz",
"integrity": "sha512-NxcICptHk06E2Lh3a4Pu+2PEdZ6ahNHuK7o6Np9zcWkrBMuv21j10SQDJW3C9Yf/A/P7cutWoC/DptNLVsZ0VQ==",
"cpu": [
"ppc64"
],
@ -2992,9 +2992,22 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.36.0.tgz",
"integrity": "sha512-ttE6ayb/kHwNRJGYLpuAvB7SMtOeQnVXEIpMtAvx3kepFQeowVED0n1K9nAdraHUPJ5hydEMxBpIR7o4nrm8uA==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.37.0.tgz",
"integrity": "sha512-PpWwHMPCVpFZLTfLq7EWJWvrmEuLdGn1GMYcm5MV7PaRgwCEYJAwiN94uBuZev0/J/hFIIJCsYw4nLmXA9J7Pw==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.37.0.tgz",
"integrity": "sha512-DTNwl6a3CfhGTAOYZ4KtYbdS8b+275LSLqJVJIrPa5/JuIufWWZ/QFvkxp52gpmguN95eujrM68ZG+zVxa8zHA==",
"cpu": [
"riscv64"
],
@ -3005,9 +3018,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.36.0.tgz",
"integrity": "sha512-4a5gf2jpS0AIe7uBjxDeUMNcFmaRTbNv7NxI5xOCs4lhzsVyGR/0qBXduPnoWf6dGC365saTiwag8hP1imTgag==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.37.0.tgz",
"integrity": "sha512-hZDDU5fgWvDdHFuExN1gBOhCuzo/8TMpidfOR+1cPZJflcEzXdCy1LjnklQdW8/Et9sryOPJAKAQRw8Jq7Tg+A==",
"cpu": [
"s390x"
],
@ -3018,9 +3031,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.36.0.tgz",
"integrity": "sha512-5KtoW8UWmwFKQ96aQL3LlRXX16IMwyzMq/jSSVIIyAANiE1doaQsx/KRyhAvpHlPjPiSU/AYX/8m+lQ9VToxFQ==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.37.0.tgz",
"integrity": "sha512-pKivGpgJM5g8dwj0ywBwe/HeVAUSuVVJhUTa/URXjxvoyTT/AxsLTAbkHkDHG7qQxLoW2s3apEIl26uUe08LVQ==",
"cpu": [
"x64"
],
@ -3031,9 +3044,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.36.0.tgz",
"integrity": "sha512-sycrYZPrv2ag4OCvaN5js+f01eoZ2U+RmT5as8vhxiFz+kxwlHrsxOwKPSA8WyS+Wc6Epid9QeI/IkQ9NkgYyQ==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.37.0.tgz",
"integrity": "sha512-E2lPrLKE8sQbY/2bEkVTGDEk4/49UYRVWgj90MY8yPjpnGBQ+Xi1Qnr7b7UIWw1NOggdFQFOLZ8+5CzCiz143w==",
"cpu": [
"x64"
],
@ -3044,9 +3057,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.36.0.tgz",
"integrity": "sha512-qbqt4N7tokFwwSVlWDsjfoHgviS3n/vZ8LK0h1uLG9TYIRuUTJC88E1xb3LM2iqZ/WTqNQjYrtmtGmrmmawB6A==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.37.0.tgz",
"integrity": "sha512-Jm7biMazjNzTU4PrQtr7VS8ibeys9Pn29/1bm4ph7CP2kf21950LgN+BaE2mJ1QujnvOc6p54eWWiVvn05SOBg==",
"cpu": [
"arm64"
],
@ -3057,9 +3070,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.36.0.tgz",
"integrity": "sha512-t+RY0JuRamIocMuQcfwYSOkmdX9dtkr1PbhKW42AMvaDQa+jOdpUYysroTF/nuPpAaQMWp7ye+ndlmmthieJrQ==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.37.0.tgz",
"integrity": "sha512-e3/1SFm1OjefWICB2Ucstg2dxYDkDTZGDYgwufcbsxTHyqQps1UQf33dFEChBNmeSsTOyrjw2JJq0zbG5GF6RA==",
"cpu": [
"ia32"
],
@ -3070,9 +3083,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.36.0.tgz",
"integrity": "sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.37.0.tgz",
"integrity": "sha512-LWbXUBwn/bcLx2sSsqy7pK5o+Nr+VCoRoAohfJ5C/aBio9nfJmGQqHAhU6pwxV/RmyTk5AqdySma7uwWGlmeuA==",
"cpu": [
"x64"
],
@ -3128,9 +3141,9 @@
"license": "CC0-1.0"
},
"node_modules/@swc/wasm": {
"version": "1.11.11",
"resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.11.11.tgz",
"integrity": "sha512-mPwKhESXb/SHtKGEeTM+TwA7zrpn1tF2cf7GnUakJh07SeUdJeTGP+zadotUzqqAWzYNHHWcMcM3C8Wef1ObjQ==",
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.11.13.tgz",
"integrity": "sha512-R3cpgAX/mxoPH9zNCwmWGOqtvQ9obRpf97Kw8FJu0HUsEcEG766ozxFgfzE9p4IwK7pKOvzYR+szDtIXlOQwdQ==",
"dev": true,
"license": "Apache-2.0"
},
@ -3536,9 +3549,9 @@
}
},
"node_modules/@types/leaflet": {
"version": "1.9.16",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.16.tgz",
"integrity": "sha512-wzZoyySUxkgMZ0ihJ7IaUIblG8Rdc8AbbZKLneyn+QjYsj5q1QU7TEKYqwTr10BGSzY5LI7tJk9Ifo+mEjdFRw==",
"version": "1.9.17",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.17.tgz",
"integrity": "sha512-IJ4K6t7I3Fh5qXbQ1uwL3CFVbCi6haW9+53oLWgdKlLP7EaS21byWFJxxqOx9y8I0AP0actXSJLVMbyvxhkUTA==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -3567,9 +3580,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
"version": "22.13.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.12.tgz",
"integrity": "sha512-ixiWrCSRi33uqBMRuICcKECW7rtgY43TbsHDpM2XK7lXispd48opW+0IXrBVxv9NMhaz/Ue9kyj6r3NTVyXm8A==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
@ -4075,9 +4088,9 @@
}
},
"node_modules/@vavite/multibuild/node_modules/@types/node": {
"version": "18.19.80",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz",
"integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==",
"version": "18.19.82",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.82.tgz",
"integrity": "sha512-s6RBC3H0JGG5Xm2IOP2R0KKNZL2s46UGZZ1r21EF3+qL377EwJ+Bnf9PMatFnYtmYMNnzVLodD6EN7FZw0Vbxg==",
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
@ -5189,9 +5202,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001706",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001706.tgz",
"integrity": "sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==",
"version": "1.0.30001707",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz",
"integrity": "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==",
"dev": true,
"funding": [
{
@ -6354,9 +6367,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.122",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.122.tgz",
"integrity": "sha512-EML1wnwkY5MFh/xUnCvY8FrhUuKzdYhowuZExZOfwJo+Zu9OsNCI23Cgl5y7awy7HrUHSwB1Z8pZX5TI34lsUg==",
"version": "1.5.123",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.123.tgz",
"integrity": "sha512-refir3NlutEZqlKaBLK0tzlVLe5P2wDKS7UQt/3SpibizgsRAPOsqQC3ffw1nlv3ze5gjRQZYHoPymgVZkplFA==",
"dev": true,
"license": "ISC"
},
@ -6696,14 +6709,14 @@
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz",
"integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==",
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.4.tgz",
"integrity": "sha512-SFtuYmnhwYCtuCDTKPoK+CEzCnEgKTU2qTLwoCxvrC0MFBTIXo1i6hDYOI4cwHaE5GZtlWmTN3YfucYi7KJwPw==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
"synckit": "^0.9.1"
"synckit": "^0.10.2"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@ -7390,13 +7403,13 @@
}
},
"node_modules/flydrive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/flydrive/-/flydrive-1.1.0.tgz",
"integrity": "sha512-acV6Pl2T+jjOURyvE57c79HP2KZmeI6sgPO6n5fZnExre4y8SCcLz5Fzttz79Snrpp6HbsA1q255OdVDf5Vrfg==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/flydrive/-/flydrive-1.1.1.tgz",
"integrity": "sha512-bcMD0eOoveOlPTvnPtjaOjq+7FidhiqldP6gSg9SQUqQc9pKSEQ/D3Xq0BDEElOLddaFKoaTUSgV2aX6kF+Ihw==",
"license": "MIT",
"dependencies": {
"@humanwhocodes/retry": "^0.3.0",
"@poppinss/utils": "^6.7.3",
"@humanwhocodes/retry": "^0.4.2",
"@poppinss/utils": "^6.9.2",
"etag": "^1.8.1",
"mime-types": "^2.1.35"
},
@ -8152,9 +8165,9 @@
}
},
"node_modules/html-entities": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz",
"integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==",
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.3.tgz",
"integrity": "sha512-D3AfvN7SjhTgBSA8L1BN4FpPzuEd06uy4lHwSoRWr0lndi9BKaNzPLKGOWZ2ocSGguozr08TTb2jhCLHaemruw==",
"funding": [
{
"type": "github",
@ -8438,9 +8451,9 @@
}
},
"node_modules/index-to-position": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-0.1.2.tgz",
"integrity": "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.0.0.tgz",
"integrity": "sha512-sCO7uaLVhRJ25vz1o8s9IFM3nVS4DkuQnyjMwiQPKvQuBYBDmb8H7zx8ki7nVh4HJQOdVWebyvLE0qt+clruxA==",
"dev": true,
"license": "MIT",
"engines": {
@ -11230,15 +11243,15 @@
}
},
"node_modules/read-pkg/node_modules/parse-json": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz",
"integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==",
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.2.0.tgz",
"integrity": "sha512-eONBZy4hm2AgxjNFd8a4nyDJnzUAH0g34xSQAwWEVGCjdZ4ZL7dKZBfq267GWP/JaS9zW62Xs2FeAdDvpHHJGQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.22.13",
"index-to-position": "^0.1.2",
"type-fest": "^4.7.1"
"@babel/code-frame": "^7.26.2",
"index-to-position": "^1.0.0",
"type-fest": "^4.37.0"
},
"engines": {
"node": ">=18"
@ -11495,9 +11508,9 @@
"license": "MIT"
},
"node_modules/rollup": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.36.0.tgz",
"integrity": "sha512-zwATAXNQxUcd40zgtQG0ZafcRK4g004WtEl7kbuhTWPvf07PsfohXl39jVUvPF7jvNAIkKPQ2XrsDlWuxBd++Q==",
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.37.0.tgz",
"integrity": "sha512-iAtQy/L4QFU+rTJ1YUjXqJOJzuwEghqWzCEYD2FEghT7Gsy1VdABntrO4CLopA5IkflTyqNiLNwPcOJ3S7UKLg==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.6"
@ -11510,25 +11523,26 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.36.0",
"@rollup/rollup-android-arm64": "4.36.0",
"@rollup/rollup-darwin-arm64": "4.36.0",
"@rollup/rollup-darwin-x64": "4.36.0",
"@rollup/rollup-freebsd-arm64": "4.36.0",
"@rollup/rollup-freebsd-x64": "4.36.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.36.0",
"@rollup/rollup-linux-arm-musleabihf": "4.36.0",
"@rollup/rollup-linux-arm64-gnu": "4.36.0",
"@rollup/rollup-linux-arm64-musl": "4.36.0",
"@rollup/rollup-linux-loongarch64-gnu": "4.36.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.36.0",
"@rollup/rollup-linux-riscv64-gnu": "4.36.0",
"@rollup/rollup-linux-s390x-gnu": "4.36.0",
"@rollup/rollup-linux-x64-gnu": "4.36.0",
"@rollup/rollup-linux-x64-musl": "4.36.0",
"@rollup/rollup-win32-arm64-msvc": "4.36.0",
"@rollup/rollup-win32-ia32-msvc": "4.36.0",
"@rollup/rollup-win32-x64-msvc": "4.36.0",
"@rollup/rollup-android-arm-eabi": "4.37.0",
"@rollup/rollup-android-arm64": "4.37.0",
"@rollup/rollup-darwin-arm64": "4.37.0",
"@rollup/rollup-darwin-x64": "4.37.0",
"@rollup/rollup-freebsd-arm64": "4.37.0",
"@rollup/rollup-freebsd-x64": "4.37.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.37.0",
"@rollup/rollup-linux-arm-musleabihf": "4.37.0",
"@rollup/rollup-linux-arm64-gnu": "4.37.0",
"@rollup/rollup-linux-arm64-musl": "4.37.0",
"@rollup/rollup-linux-loongarch64-gnu": "4.37.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.37.0",
"@rollup/rollup-linux-riscv64-gnu": "4.37.0",
"@rollup/rollup-linux-riscv64-musl": "4.37.0",
"@rollup/rollup-linux-s390x-gnu": "4.37.0",
"@rollup/rollup-linux-x64-gnu": "4.37.0",
"@rollup/rollup-linux-x64-musl": "4.37.0",
"@rollup/rollup-win32-arm64-msvc": "4.37.0",
"@rollup/rollup-win32-ia32-msvc": "4.37.0",
"@rollup/rollup-win32-x64-msvc": "4.37.0",
"fsevents": "~2.3.2"
}
},
@ -12606,14 +12620,14 @@
}
},
"node_modules/synckit": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
"integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.10.2.tgz",
"integrity": "sha512-cSGiaCPhFzeFIQY8KKEacv46LclENY4d60jgkwCrKomvRkIjtMyss1dPkHLp/62c1leuOjEedB1+lWcwqTJSvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.1.0",
"tslib": "^2.6.2"
"@pkgr/core": "^0.2.0",
"tslib": "^2.8.1"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@ -14352,9 +14366,9 @@
}
},
"node_modules/yocto-queue": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.0.tgz",
"integrity": "sha512-KHBC7z61OJeaMGnF3wqNZj+GGNXOyypZviiKpQeiHirG5Ib1ImwcLBH70rbMSkKfSmUNBsdf2PwaEJtKvgmkNw==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz",
"integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==",
"license": "MIT",
"engines": {
"node": ">=12.20"

View file

@ -76,7 +76,7 @@
},
"dependencies": {
"@adonisjs/auth": "^9.2.4",
"@adonisjs/bodyparser": "^10.0.3",
"@adonisjs/bodyparser": "^10.0.1",
"@adonisjs/core": "^6.17.0",
"@adonisjs/cors": "^2.2.1",
"@adonisjs/drive": "^3.2.0",

View file

@ -1,4 +1,4 @@
<script setup>
<script lang="ts" setup>
import { mdiCog } from '@mdi/js';
import CardBox from '@/Components/CardBox.vue';
import NumberDynamic from '@/Components/NumberDynamic.vue';
@ -49,6 +49,9 @@ defineProps({
<PillTagTrend :trend="trend" :trend-type="trendType" small />
<BaseButton :icon="mdiCog" icon-w="w-4" icon-h="h-4" color="white" small />
</BaseLevel>
<BaseLevel v-else class="mb-3" mobile>
<BaseIcon v-if="icon" :path="icon" size="48" w="w-4" h="h-4" :class="color" />
</BaseLevel>
<BaseLevel mobile>
<div>
<h3 class="text-lg leading-tight text-gray-500 dark:text-slate-400">

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import { computed, onMounted } from 'vue';
import { Head, usePage } from '@inertiajs/vue3';
import { computed } from 'vue';
import { MainService } from '@/Stores/main';
import {
mdiAccountMultiple,
@ -9,7 +9,6 @@ import {
mdiFinance,
mdiMonitorCellphone,
mdiReload,
mdiGithub,
mdiChartPie,
} from '@mdi/js';
import LineChart from '@/Components/Charts/LineChart.vue';
@ -23,14 +22,15 @@ import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
// import SectionBannerStarOnGitHub from '@/Components/SectionBannerStarOnGitea.vue';
import CardBoxDataset from '@/Components/CardBoxDataset.vue';
import type { User } from '@/Dataset';
const mainService = MainService()
// const chartData = ref();
// const fillChartData = async () => {
// await mainService.fetchChartData("2022");
// // chartData.value = chartConfig.sampleChartData();
// // chartData.value = mainService.graphData;
// };
const fillChartData = async () => {
await mainService.fetchChartData();
// chartData.value = chartConfig.sampleChartData();
// chartData.value = mainService.graphData;
};
const chartData = computed(() => mainService.graphData);
// onMounted(async () => {
// await mainService.fetchChartData("2022");
@ -49,9 +49,14 @@ const authorBarItems = computed(() => mainService.authors.slice(0, 5));
const authors = computed(() => mainService.authors);
const datasets = computed(() => mainService.datasets);
const datasetBarItems = computed(() => mainService.datasets.slice(0, 5));
// let test = datasets.value;
// console.log(test);
const submitters = computed(() => mainService.clients);
const user = computed(() => {
return usePage().props.authUser as User;
});
const userHasRoles = (roleNames: Array<string>): boolean => {
return user.value.roles.some(role => roleNames.includes(role.name));
};
</script>
<template>
@ -80,21 +85,21 @@ const datasetBarItems = computed(() => mainService.datasets.slice(0, 5));
:number="authors.length"
label="Authors"
/>
<!-- trend="193" -->
<CardBoxWidget
trend="193"
trend-type="info"
color="text-blue-500"
:icon="mdiDatabaseOutline"
:number="datasets.length"
label="Publications"
/>
<!-- trend="+25%" -->
<CardBoxWidget
trend="+25%"
trend-type="up"
color="text-purple-500"
:icon="mdiChartTimelineVariant"
:number="52"
label="Citations"
:number="submitters.length"
label="Submitters"
/>
</div>
@ -128,11 +133,9 @@ const datasetBarItems = computed(() => mainService.datasets.slice(0, 5));
</div>
</CardBox>
<SectionTitleLineWithButton :icon="mdiAccountMultiple" title="Submitters" />
<SectionTitleLineWithButton v-if="userHasRoles(['administrator'])" :icon="mdiAccountMultiple" title="Submitters" />
<!-- <NotificationBar color="info" :icon="mdiMonitorCellphone"> <b>Responsive table.</b> Collapses on mobile </NotificationBar> -->
<CardBox :icon="mdiMonitorCellphone" title="Responsive table" has-table>
<CardBox v-if="userHasRoles(['administrator'])" :icon="mdiMonitorCellphone" title="Responsive table" has-table>
<TableSampleClients />
</CardBox>
</SectionMain>

View file

@ -20,11 +20,13 @@ import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.
import BaseButton from '@/Components/BaseButton.vue';
import { mdiLightbulbAlert, mdiArrowLeftBoldOutline } from '@mdi/js';
import { stardust } from '@eidellev/adonis-stardust/client';
import LayoutGuest from '@/Layouts/LayoutGuest.vue';
@Component({
// options: {
// layout: DefaultLayout,
// },
@Component({
options: {
layout: LayoutGuest,
},
name: 'AppComponent',
components: {

View file

@ -0,0 +1,71 @@
<template>
<div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full p-6 bg-white rounded-md shadow-md">
<h1 class="text-2xl font-bold text-red-500 mb-4">{{ status }}</h1>
<p class="text-gray-700 mb-4">{{ message }}</p>
<div class="text-sm text-gray-500 mb-4">
<p>Error Code: {{ details.code }}</p>
<p>Type: {{ details.type }}</p>
<div v-for="(port, index) in details.ports" :key="index">
<p>Connection attempt {{ index + 1 }}: {{ port.address }}:{{ port.port }}</p>
</div>
</div>
<SectionTitleLineWithButton :icon="mdiLightbulbAlert" :title="'Database Error'" :main="true">
<BaseButton @click.prevent="handleAction" :icon="mdiArrowLeftBoldOutline" label="Dashboard"
color="white" rounded-full small />
</SectionTitleLineWithButton>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-facing-decorator';
import { Link, router } from '@inertiajs/vue3';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
import BaseButton from '@/Components/BaseButton.vue';
import { mdiLightbulbAlert, mdiArrowLeftBoldOutline } from '@mdi/js';
import { stardust } from '@eidellev/adonis-stardust/client';
import LayoutGuest from '@/Layouts/LayoutGuest.vue';
@Component({
options: {
layout: LayoutGuest,
},
name: 'PostgresError',
components: {
Link,
BaseButton,
SectionTitleLineWithButton,
},
})
export default class AppComponent extends Vue {
@Prop({
type: String,
default: '',
})
status: string;
@Prop({
type: String,
default: '',
})
message: string;
@Prop({
type: Object,
default: () => ({}),
})
details: {
code: string;
type: string;
ports: Array<{ port: number; address: string }>;
};
mdiLightbulbAlert = mdiLightbulbAlert;
mdiArrowLeftBoldOutline = mdiArrowLeftBoldOutline;
public async handleAction() {
await router.get(stardust.route('dashboard'));
}
}
</script>

View file

@ -198,7 +198,8 @@ export const MainService = defineStore('main', {
}
})
.catch((error) => {
alert(error.message);
// alert(error.message);
throw error;
});
},
@ -236,17 +237,18 @@ export const MainService = defineStore('main', {
this.totpState = state;
},
fetchChartData(year: string) {
fetchChartData() {
// sampleDataKey= authors or datasets
axios
.get(`/api/statistic/${year}`)
.get(`/api/statistic`)
.then((r) => {
if (r.data) {
this.graphData = r.data;
}
})
.catch((error) => {
alert(error.message);
// alert(error.message);
throw error;
});
},

View file

@ -106,7 +106,14 @@ router
// Auth routes
router
.get('/app/login', ({ inertia }: HttpContext) => {
.get('/app/login', async({ inertia }: HttpContext) => {
try {
await db.connection().rawQuery('SELECT 1');
} catch (error) {
if (error.code === 'ECONNREFUSED') {
throw error;
}
}
return inertia.render('Auth/Login');
})
.as('app.login.show');
@ -246,18 +253,11 @@ router.get('/settings/user/security', [UserController, 'accountInfo']).as('setti
router.post('/settings/user/store', [UserController, 'accountInfoStore']).as('account.password.store').use(middleware.auth());
router.get('/settings/profile/edit', [UserController, 'profile']).as('settings.profile.edit').use(middleware.auth());
router
.put('/settings/profile/:id/update', [UserController, 'profileUpdate'])
.as('settings.profile.update')
.where('id', router.matchers.number())
.use(middleware.auth());
router
.put('/settings/password/update', [UserController, 'passwordUpdate'])
.as('settings.password.update')
.use(middleware.auth());
.put('/settings/profile/:id/update', [UserController, 'profileUpdate'])
.as('settings.profile.update')
.where('id', router.matchers.number())
.use(middleware.auth());
router.put('/settings/password/update', [UserController, 'passwordUpdate']).as('settings.password.update').use(middleware.auth());
// Submitter routes
router

View file

@ -20,7 +20,7 @@ router
router.get('/dataset/:publish_id', [DatasetController, 'findOne']).as('dataset.findOne');
router.get('/sitelinks/:year', [HomeController, 'findDocumentsPerYear']);
router.get('/years', [HomeController, 'findYears']);
router.get('/statistic/:year', [HomeController, 'findPublicationsPerMonth']);
router.get('/statistic', [HomeController, 'findPublicationsPerMonth']);
router.get('/download/:id', [FileController, 'findOne']).as('file.findOne');