From 3df346289a54a69920f9f3d4bf8151a403d2700c Mon Sep 17 00:00:00 2001 From: undefined Date: Sun, 9 Jun 2024 11:36:10 +0800 Subject: [PATCH 01/41] ui: fix messages connection --- build/prepare.js | 2 +- packages/ui-default/build/config/webpack.ts | 1 - .../components/message/index.page.ts | 2 +- .../ui-default/components/message/worker.ts | 20 +++++++++---------- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/build/prepare.js b/build/prepare.js index 6e00c7c00..2f579a136 100644 --- a/build/prepare.js +++ b/build/prepare.js @@ -89,7 +89,7 @@ const UIConfig = { esModuleInterop: true, resolveJsonModule: true, jsx: 'react', - module: 'commonjs', + module: 'ESNext', skipLibCheck: true, allowSyntheticDefaultImports: true, target: 'es2020', diff --git a/packages/ui-default/build/config/webpack.ts b/packages/ui-default/build/config/webpack.ts index f4ede936d..7781491d8 100644 --- a/packages/ui-default/build/config/webpack.ts +++ b/packages/ui-default/build/config/webpack.ts @@ -77,7 +77,6 @@ export default function (env: { watch?: boolean, production?: boolean, measure?: polyfill: './polyfill.ts', 'default.theme': './theme/default.js', 'service-worker': './service-worker.ts', - 'messages-shared-worker': './components/message/worker.ts', }, cache: { type: 'filesystem', diff --git a/packages/ui-default/components/message/index.page.ts b/packages/ui-default/components/message/index.page.ts index 2707b3737..723b6610c 100644 --- a/packages/ui-default/components/message/index.page.ts +++ b/packages/ui-default/components/message/index.page.ts @@ -61,7 +61,7 @@ const onmessage = (msg) => { const initWorkerMode = (endpoint) => { console.log('Messages: using SharedWorker'); - const worker = new SharedWorker('/messages-shared-worker.js', { name: 'HydroMessagesWorker' }); + const worker = new SharedWorker(new URL('./worker.ts', import.meta.url), { name: 'HydroMessagesWorker' }); worker.port.start(); window.addEventListener('beforeunload', () => { worker.port.postMessage({ type: 'unload' }); diff --git a/packages/ui-default/components/message/worker.ts b/packages/ui-default/components/message/worker.ts index 4e186061b..28d722b75 100644 --- a/packages/ui-default/components/message/worker.ts +++ b/packages/ui-default/components/message/worker.ts @@ -31,12 +31,17 @@ function broadcastMsg(message: any) { function initConn(path: string, port: MessagePort, cookie: any) { if (cookie !== lcookie) conn?.close(); else if (conn && conn.readyState === conn.OPEN) return; + const url = new URL(path, location.origin); + if (cookie) url.searchParams.set('sid', cookie.split('sid=')[1].split(';')[0]); lcookie = cookie; console.log('Init connection for', path); conn?.close(); - conn = new ReconnectingWebsocket(path); + conn = new ReconnectingWebsocket(url.toString()); ports.push(port); - conn.onopen = () => conn.send(cookie); + conn.onopen = () => { + console.log('Connected to', path); + broadcastMsg({ type: 'open' }); + }; conn.onerror = () => broadcastMsg({ type: 'error' }); conn.onclose = (ev) => broadcastMsg({ type: 'close', error: ev.reason }); conn.onmessage = (message) => { @@ -46,14 +51,9 @@ function initConn(path: string, port: MessagePort, cookie: any) { return; } const payload = JSON.parse(message.data); - if (payload.event === 'auth') { - if (['PermissionError', 'PrivilegeError'].includes(payload.error)) { - broadcastMsg({ type: 'close', error: payload.error }); - conn.close(); - } else { - console.log('Connected to', path); - broadcastMsg({ type: 'open' }); - } + if (['PermissionError', 'PrivilegeError'].includes(payload.error)) { + broadcastMsg({ type: 'close', error: payload.error }); + conn.close(); } else { broadcastMsg({ type: 'message', payload }); let acked = false; From ae057fee95b9937085f007650b58e5a9d9230786 Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 10 Jun 2024 21:22:13 +0800 Subject: [PATCH 02/41] server: fix ws import --- framework/framework/package.json | 2 +- framework/framework/server.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/framework/package.json b/framework/framework/package.json index 12ed76220..b6d2f4e62 100644 --- a/framework/framework/package.json +++ b/framework/framework/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/framework", - "version": "0.0.2", + "version": "0.0.3", "main": "index.ts", "peerDependencies": { "cordis": "*" diff --git a/framework/framework/server.ts b/framework/framework/server.ts index 09897f36b..86c35710a 100644 --- a/framework/framework/server.ts +++ b/framework/framework/server.ts @@ -8,7 +8,7 @@ import Koa from 'koa'; import Body from 'koa-body'; import Compress from 'koa-compress'; import { Shorty } from 'shorty.js'; -import WebSocket from 'ws'; +import { WebSocketServer } from 'ws'; import { Counter, errorMessage, isClass, Logger, parseMemoryMB, } from '@hydrooj/utils/lib/utils'; @@ -91,7 +91,7 @@ export const koa = new Koa({ }); export const router = new Router(); export const httpServer = http.createServer(koa.callback()); -export const wsServer = new WebSocket.Server({ server: httpServer }); +export const wsServer = new WebSocketServer({ server: httpServer }); koa.on('error', (error) => { if (error.code !== 'EPIPE' && error.code !== 'ECONNRESET' && !error.message.includes('Parse Error')) { logger.error('Koa app-level error', { error }); From 311caee15426e8fb579c6fbe4dae0a3ad9388ee2 Mon Sep 17 00:00:00 2001 From: panda Date: Tue, 11 Jun 2024 11:34:42 +0800 Subject: [PATCH 03/41] core: fix websocket can not generate url (#825) --- packages/hydrooj/src/service/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index 7657b8445..167ac0276 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -209,7 +209,7 @@ export async function apply(ctx: Context) { ? (!host.includes(this.request.host)) : this.request.host !== host) )) withDomainId ||= domainId; - res = this.context.router.url.call(this.context.router, name, args, { query }).toString(); + res = ctx.server.router.url.call(ctx.server.router, name, args, { query }).toString(); if (anchor) res = `${res}#${anchor}`; if (withDomainId) res = `/d/${withDomainId}${res}`; } catch (e) { From 457d820bb3658994fe7c42bcf0908d9f6f7eb27c Mon Sep 17 00:00:00 2001 From: undefined Date: Tue, 11 Jun 2024 15:12:41 +0800 Subject: [PATCH 04/41] register: allow load sourcemap from file --- packages/register/index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/register/index.js b/packages/register/index.js index 6d70e161e..d7ee5f518 100644 --- a/packages/register/index.js +++ b/packages/register/index.js @@ -50,6 +50,12 @@ require.extensions['.js'] = function loader(module, filename) { } try { const content = fs.readFileSync(filename, 'utf-8'); + const lastLine = content.trim().split('\n').pop(); + if (lastLine.startsWith('//# sourceMappingURL=data:application/json;base64,')) { + const info = lastLine.split('//# sourceMappingURL=data:application/json;base64,')[1]; + const payload = JSON.parse(Buffer.from(info, 'base64').toString()); + map[filename] = payload; + } return module._compile(content, filename); } catch (e) { // ESM return module._compile(transform(filename), filename); From 58d0c759956da3fa4558153164cea24516fe6795 Mon Sep 17 00:00:00 2001 From: undefined Date: Tue, 11 Jun 2024 15:12:56 +0800 Subject: [PATCH 05/41] server: add handlerCtxBase --- framework/framework/server.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/framework/framework/server.ts b/framework/framework/server.ts index 86c35710a..01528bc6f 100644 --- a/framework/framework/server.ts +++ b/framework/framework/server.ts @@ -322,6 +322,7 @@ export class WebService extends Service { HandlerCommon = HandlerCommon; Handler = Handler; ConnectionHandler = ConnectionHandler; + handlerCtxBase = this.ctx; constructor(ctx: Context, public config: WebServiceConfig) { super(ctx, 'server', true); @@ -420,6 +421,9 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); } socket.close(); }); + this.ctx.inject(['server'], (c) => { + this.handlerCtxBase = c; + }); } async listen() { @@ -438,7 +442,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); private async handleHttp(ctx: KoaContext, HandlerClass, checker) { const { args } = ctx.HydroContext; Object.assign(args, ctx.params); - const h = new HandlerClass(ctx, this.ctx); + const h = new HandlerClass(ctx, this.handlerCtxBase); ctx.handler = h; const method = ctx.method.toLowerCase(); try { @@ -511,7 +515,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); private async handleWS(ctx: KoaContext, HandlerClass, checker, conn, layer) { const { args } = ctx.HydroContext; - const h = new HandlerClass(ctx, this.ctx); + const h = new HandlerClass(ctx, this.handlerCtxBase); await this.ctx.parallel('connection/create', h); ctx.handler = h; h.conn = conn; From 0722cc9e794b604731a34b977963ab67711710ed Mon Sep 17 00:00:00 2001 From: undefined Date: Thu, 13 Jun 2024 16:32:10 +0800 Subject: [PATCH 06/41] core: fix problem.getList return type --- packages/hydrooj/src/model/problem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index dee3c7b47..b372c0fad 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -363,7 +363,7 @@ export class ProblemModel { domainId: string, pids: number[], canViewHidden: number | boolean = false, doThrow = true, projection = ProblemModel.PROJECTION_PUBLIC, indexByDocIdOnly = false, ): Promise { - if (!pids?.length) return []; + if (!pids?.length) return {}; const r: Record = {}; const l: Record = {}; const q: any = { docId: { $in: pids } }; From 35de36d80dacb8025e0dff1d24d4a97a37f4c6a1 Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 14 Jun 2024 13:25:44 +0800 Subject: [PATCH 07/41] core: change title order --- packages/hydrooj/src/service/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index 167ac0276..85394d15d 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -255,7 +255,7 @@ export async function apply(ctx: Context) { }, renderTitle(str: string) { const name = this.ctx.setting.get('server.name'); - if (this.UiContext.extraTitleContent) return `${this.translate(str)} - ${this.UiContext.extraTitleContent} - ${name}`; + if (this.UiContext.extraTitleContent) return `${this.UiContext.extraTitleContent} - ${this.translate(str)} - ${name}`; return `${this.translate(str)} - ${name}`; }, }); From 0ab030130c2542789612db535641cc06e8f0c8ea Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 14 Jun 2024 13:45:16 +0800 Subject: [PATCH 08/41] core: deprecate `export =` --- framework/framework/package.json | 2 +- package.json | 2 +- packages/eslint-config/package.json | 6 ++-- packages/hydrooj/package.json | 12 ++++---- packages/hydrooj/src/handler/domain.ts | 5 ++-- packages/hydrooj/src/interface.ts | 5 ++-- packages/hydrooj/src/lib/difficulty.ts | 2 +- packages/hydrooj/src/lib/paginate.ts | 24 --------------- packages/hydrooj/src/lib/rank.ts | 25 ---------------- packages/hydrooj/src/lib/rating.ts | 2 +- packages/hydrooj/src/model/contest.ts | 13 ++++---- packages/hydrooj/src/plugin-api.ts | 6 ++-- packages/hydrooj/src/service/db.ts | 41 +++++++++++++++++++++++++- packages/hydrooj/src/service/server.ts | 4 +-- packages/ui-default/package.json | 6 ++-- packages/utils/package.json | 2 +- 16 files changed, 73 insertions(+), 84 deletions(-) delete mode 100644 packages/hydrooj/src/lib/paginate.ts delete mode 100644 packages/hydrooj/src/lib/rank.ts diff --git a/framework/framework/package.json b/framework/framework/package.json index b6d2f4e62..b17d0cdb9 100644 --- a/framework/framework/package.json +++ b/framework/framework/package.json @@ -26,7 +26,7 @@ "@types/koa": "^2.15.0", "@types/koa-compress": "^4.0.6", "@types/koa-router": "^7.4.8", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.5", "@types/saslprep": "^1.0.3", "@types/ws": "^8.5.10" } diff --git a/package.json b/package.json index 018ce99eb..43f07faca 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "ora": "^8.0.1", "package-json": "^10.0.0", "semver": "^7.6.2", - "simple-git": "^3.24.0", + "simple-git": "^3.25.0", "supertest": "^7.0.0", "typescript": "5.3.3", "wtfnode": "^0.9.2" diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index a5a0e2185..25cc6c4fd 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -5,11 +5,11 @@ "main": "react.yaml", "dependencies": { "@stylistic/eslint-plugin": "^2.1.0", - "@typescript-eslint/eslint-plugin": "^7.12.0", - "@typescript-eslint/parser": "^7.12.0", + "@typescript-eslint/eslint-plugin": "^7.13.0", + "@typescript-eslint/parser": "^7.13.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^18.0.0", - "eslint-plugin-github": "^4.10.2", + "eslint-plugin-github": "^5.0.1", "eslint-plugin-import": "2.29.1", "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-react": "^7.34.2", diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 7725cc6ae..209eecf7d 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -12,10 +12,10 @@ }, "preferUnplugged": true, "dependencies": { - "@aws-sdk/client-s3": "3.591.0", - "@aws-sdk/lib-storage": "3.591.0", - "@aws-sdk/s3-presigned-post": "3.591.0", - "@aws-sdk/s3-request-presigner": "3.591.0", + "@aws-sdk/client-s3": "3.596.0", + "@aws-sdk/lib-storage": "3.596.0", + "@aws-sdk/s3-presigned-post": "3.596.0", + "@aws-sdk/s3-request-presigner": "3.596.0", "@graphql-tools/schema": "^10.0.4", "@hydrooj/framework": "workspace:^", "@hydrooj/utils": "workspace:^", @@ -28,7 +28,7 @@ "emoji-regex": "^10.3.0", "emojis-list": "2.1.0", "fs-extra": "^11.2.0", - "graphql": "^16.8.1", + "graphql": "^16.8.2", "graphql-scalars": "^1.23.0", "js-yaml": "^4.1.0", "koa-proxies": "^0.12.4", @@ -59,7 +59,7 @@ "@types/fs-extra": "^11.0.4", "@types/js-yaml": "^4.0.9", "@types/koa-static-cache": "^5.1.4", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.5", "@types/mime-types": "^2.1.4", "@types/mongodb-uri": "^0.9.4", "@types/nodemailer": "^6.4.15", diff --git a/packages/hydrooj/src/handler/domain.ts b/packages/hydrooj/src/handler/domain.ts index 6bc1adcb5..438ff8629 100644 --- a/packages/hydrooj/src/handler/domain.ts +++ b/packages/hydrooj/src/handler/domain.ts @@ -8,7 +8,6 @@ import { } from '../error'; import type { DomainDoc } from '../interface'; import avatar from '../lib/avatar'; -import paginate from '../lib/paginate'; import { PERM, PERMS_BY_FAMILY, PRIV } from '../model/builtin'; import * as discussion from '../model/discussion'; import domain from '../model/domain'; @@ -24,10 +23,10 @@ import { log2 } from '../utils'; class DomainRankHandler extends Handler { @query('page', Types.PositiveInt, true) async get(domainId: string, page = 1) { - const [dudocs, upcount, ucount] = await paginate( + const [dudocs, upcount, ucount] = await this.paginate( domain.getMultiUserInDomain(domainId, { uid: { $gt: 1 }, rp: { $gt: 0 } }).sort({ rp: -1 }), page, - 100, + 'ranking', ); const udict = await user.getList(domainId, dudocs.map((dudoc) => dudoc.uid)); const udocs = dudocs.map((i) => udict[i.uid]); diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 45e0d3bfc..5ce54e0de 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -777,11 +777,10 @@ export interface ProblemSearchOptions { export type ProblemSearch = (domainId: string, q: string, options?: ProblemSearchOptions) => Promise; export interface Lib extends Record { - difficulty: typeof import('./lib/difficulty'); + difficulty: typeof import('./lib/difficulty').default; buildContent: typeof import('./lib/content').buildContent; mail: typeof import('./lib/mail'); - rank: typeof import('./lib/rank'); - rating: typeof import('./lib/rating'); + rating: typeof import('./lib/rating').default; testdataConfig: typeof import('./lib/testdataConfig'); problemSearch: ProblemSearch; } diff --git a/packages/hydrooj/src/lib/difficulty.ts b/packages/hydrooj/src/lib/difficulty.ts index 87d924d34..231376806 100644 --- a/packages/hydrooj/src/lib/difficulty.ts +++ b/packages/hydrooj/src/lib/difficulty.ts @@ -42,5 +42,5 @@ function difficultyAlgorithm(nSubmit: number, nAccept: number) { return Math.max(ans, 1); } -export = difficultyAlgorithm; +export default difficultyAlgorithm; global.Hydro.lib.difficulty = difficultyAlgorithm; diff --git a/packages/hydrooj/src/lib/paginate.ts b/packages/hydrooj/src/lib/paginate.ts deleted file mode 100644 index 59b3b7f79..000000000 --- a/packages/hydrooj/src/lib/paginate.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { FindCursor } from 'mongodb'; -import { ValidationError } from '../error'; -import db from '../service/db'; - -async function paginate( - cursor: FindCursor, page: number, pageSize: number, -): Promise<[docs: T[], numPages: number, count: number]> { - if (page <= 0) throw new ValidationError('page'); - let filter = {}; - for (const key of Object.getOwnPropertySymbols(cursor)) { - if (key.toString() !== 'Symbol(filter)') continue; - filter = cursor[key]; - break; - } - const coll = db.collection(cursor.namespace.collection as any); - const [count, pageDocs] = await Promise.all([ - Object.keys(filter).length ? coll.count(filter) : coll.countDocuments(filter), - cursor.skip((page - 1) * pageSize).limit(pageSize).toArray(), - ]); - const numPages = Math.floor((count + pageSize - 1) / pageSize); - return [pageDocs, numPages, count]; -} - -export = paginate; diff --git a/packages/hydrooj/src/lib/rank.ts b/packages/hydrooj/src/lib/rank.ts deleted file mode 100644 index 2f6fc17a9..000000000 --- a/packages/hydrooj/src/lib/rank.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { FindCursor } from 'mongodb'; - -type ranked = >(cursor: FindCursor, equ: ((a: T, b: T) => boolean)) => Promise<[number, T][]>; - -const ranked: ranked = async (cursor, equ) => { - let last = null; - let r = 0; - let count = 0; - const results = []; - const docs = await cursor.toArray(); - for (const doc of docs) { - if (doc.unrank) { - results.push([0, doc]); - continue; - } - count++; - if (!last || !equ(last, doc)) r = count; - last = doc; - results.push([r, doc]); - } - return results; -}; - -global.Hydro.lib.rank = ranked; -export = ranked; diff --git a/packages/hydrooj/src/lib/rating.ts b/packages/hydrooj/src/lib/rating.ts index 029cf8030..5c89c6678 100644 --- a/packages/hydrooj/src/lib/rating.ts +++ b/packages/hydrooj/src/lib/rating.ts @@ -118,6 +118,6 @@ function calculate(users: RatingInputUser[]): RatingOutputUser[] { return calculator.calculate() as RatingOutputUser[]; } -export = calculate; +export default calculate; global.Hydro.lib.rating = calculate; diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index 1e208bff3..03def05d9 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -9,7 +9,6 @@ import { BaseUserDict, ContestRule, ContestRules, ProblemDict, RecordDoc, ScoreboardConfig, ScoreboardNode, ScoreboardRow, SubtaskResult, Tdoc, } from '../interface'; -import ranked from '../lib/rank'; import * as bus from '../service/bus'; import db from '../service/db'; import type { Handler } from '../service/server'; @@ -224,7 +223,7 @@ const acm = buildContestRule({ return row; }, async scoreboard(config, _, tdoc, pdict, cursor) { - const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score && a.time === b.time); + const rankedTsdocs = await db.ranked(cursor, (a, b) => a.score === b.score && a.time === b.time); const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid); const udict = await user.getListForRender(tdoc.domainId, uids, config.showDisplayName); // Find first accept @@ -257,7 +256,7 @@ const acm = buildContestRule({ return [rows, udict]; }, async ranked(tdoc, cursor) { - return await ranked(cursor, (a, b) => a.accept === b.accept && a.time === b.time); + return await db.ranked(cursor, (a, b) => a.accept === b.accept && a.time === b.time); }, applyProjection(tdoc, rdoc) { if (isDone(tdoc)) return rdoc; @@ -394,7 +393,7 @@ const oi = buildContestRule({ return row; }, async scoreboard(config, _, tdoc, pdict, cursor) { - const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score); + const rankedTsdocs = await db.ranked(cursor, (a, b) => a.score === b.score); const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid); const udict = await user.getListForRender(tdoc.domainId, uids, config.showDisplayName); const psdict = {}; @@ -431,7 +430,7 @@ const oi = buildContestRule({ return [rows, udict]; }, async ranked(tdoc, cursor) { - return await ranked(cursor, (a, b) => a.score === b.score); + return await db.ranked(cursor, (a, b) => a.score === b.score); }, applyProjection(tdoc, rdoc) { if (isDone(tdoc)) return rdoc; @@ -744,7 +743,7 @@ const homework = buildContestRule({ return row; }, async scoreboard(config, _, tdoc, pdict, cursor) { - const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score); + const rankedTsdocs = await db.ranked(cursor, (a, b) => a.score === b.score); const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid); const udict = await user.getListForRender(tdoc.domainId, uids, config.showDisplayName); const columns = await this.scoreboardHeader(config, _, tdoc, pdict); @@ -757,7 +756,7 @@ const homework = buildContestRule({ return [rows, udict]; }, async ranked(tdoc, cursor) { - return await ranked(cursor, (a, b) => a.score === b.score); + return await db.ranked(cursor, (a, b) => a.score === b.score); }, }); diff --git a/packages/hydrooj/src/plugin-api.ts b/packages/hydrooj/src/plugin-api.ts index c58de3abf..fafe3f107 100644 --- a/packages/hydrooj/src/plugin-api.ts +++ b/packages/hydrooj/src/plugin-api.ts @@ -43,8 +43,10 @@ export { buildContent } from './lib/content'; export { default as mime } from './lib/mime'; export { default as rating } from './lib/rating'; export { default as avatar } from './lib/avatar'; -export { default as rank } from './lib/rank'; -export { default as paginate } from './lib/paginate'; +/** @deprecated use Handler.paginate instead */ +export const paginate = db.paginate.bind(db); +/** @deprecated use db.ranked instead */ +export const rank = db.ranked.bind(db); export { UiContextBase } from './service/layers/base'; export * from '@hydrooj/framework/decorators'; export * from '@hydrooj/framework/validator'; diff --git a/packages/hydrooj/src/service/db.ts b/packages/hydrooj/src/service/db.ts index 6e9768708..9430b9ea6 100644 --- a/packages/hydrooj/src/service/db.ts +++ b/packages/hydrooj/src/service/db.ts @@ -1,9 +1,10 @@ /* eslint-disable no-await-in-loop */ import { - Collection, Db, IndexDescription, MongoClient, + Collection, Db, FindCursor, IndexDescription, MongoClient, } from 'mongodb'; import mongoUri from 'mongodb-uri'; import { Time } from '@hydrooj/utils'; +import { ValidationError } from '../error'; import { Logger } from '../logger'; import { load } from '../options'; import * as bus from './bus'; @@ -102,6 +103,44 @@ class MongoService { } } + async paginate( + cursor: FindCursor, page: number, pageSize: number, + ): Promise<[docs: T[], numPages: number, count: number]> { + if (page <= 0) throw new ValidationError('page'); + let filter = {}; + for (const key of Object.getOwnPropertySymbols(cursor)) { + if (key.toString() !== 'Symbol(filter)') continue; + filter = cursor[key]; + break; + } + const coll = this.db.collection(cursor.namespace.collection as any); + const [count, pageDocs] = await Promise.all([ + Object.keys(filter).length ? coll.count(filter) : coll.countDocuments(filter), + cursor.skip((page - 1) * pageSize).limit(pageSize).toArray(), + ]); + const numPages = Math.floor((count + pageSize - 1) / pageSize); + return [pageDocs, numPages, count]; + } + + async ranked>(cursor: FindCursor, equ: (a: T, b: T) => boolean): Promise<[number, T][]> { + let last = null; + let r = 0; + let count = 0; + const results = []; + const docs = await cursor.toArray(); + for (const doc of docs) { + if ((doc as any).unrank) { + results.push([0, doc]); + continue; + } + count++; + if (!last || !equ(last, doc)) r = count; + last = doc; + results.push([r, doc]); + } + return results; + } + public async apply(ctx) { await this.start(); ctx.on('dispose', () => this.client.close()); diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index 85394d15d..d75126913 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -11,13 +11,13 @@ import { errorMessage, Time } from '@hydrooj/utils'; import { Context } from '../context'; import { PermissionError, PrivilegeError } from '../error'; import type { DomainDoc } from '../interface'; -import paginate from '../lib/paginate'; import { Logger } from '../logger'; import { PERM, PRIV } from '../model/builtin'; import * as opcount from '../model/opcount'; import * as OplogModel from '../model/oplog'; import * as system from '../model/system'; import { builtinConfig } from '../settings'; +import db from './db'; import baseLayer from './layers/base'; import domainLayer from './layers/domain'; import userLayer from './layers/user'; @@ -228,7 +228,7 @@ export async function apply(ctx: Context) { return res; }, paginate(cursor: FindCursor, page: number, key: string) { - return paginate(cursor, page, this.ctx.setting.get(`pagination.${key}`)); + return db.paginate(cursor, page, this.ctx.setting.get(`pagination.${key}`)); }, checkPerm(...args: bigint[]) { if (!this.user.hasPerm(...args)) { diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 077d83cdc..568215c5a 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -25,7 +25,7 @@ "@fontsource/source-code-pro": "^5.0.18", "@fontsource/ubuntu-mono": "^5.0.20", "@hydrooj/utils": "workspace:^", - "@sentry/browser": "^8.8.0", + "@sentry/browser": "^8.9.2", "@sentry/webpack-plugin": "^2.18.0", "@simplewebauthn/browser": "7.4.0", "@svgr/webpack": "^8.1.0", @@ -120,7 +120,7 @@ "vditor": "^3.10.4", "vinyl-buffer": "^1.0.1", "web-streams-polyfill": "^4.0.0", - "webpack": "^5.91.0", + "webpack": "^5.92.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-dev-server": "^5.0.4", "webpack-manifest-plugin": "^5.0.0", @@ -136,7 +136,7 @@ "katex": "^0.16.10", "lodash": "^4.17.21", "markdown-it": "^14.1.0", - "markdown-it-anchor": "^8.6.7", + "markdown-it-anchor": "^9.0.1", "markdown-it-attrs": "^4.1.6", "markdown-it-footnote": "^4.0.0", "markdown-it-imsize": "^2.0.1", diff --git a/packages/utils/package.json b/packages/utils/package.json index b8a1c9ef9..7273454a3 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -16,7 +16,7 @@ "mongodb": "^5.9.2", "reggol": "^1.6.3", "search-query-parser": "^1.6.0", - "systeminformation": "^5.22.10" + "systeminformation": "^5.22.11" }, "devDependencies": { "@types/fs-extra": "^11.0.4" From 18ad235a1cb3d79ae78e0345a885dd44e7de4f73 Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 14 Jun 2024 13:51:46 +0800 Subject: [PATCH 09/41] core: fix --- packages/hydrooj/src/lib/hash.hydro.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hydrooj/src/lib/hash.hydro.ts b/packages/hydrooj/src/lib/hash.hydro.ts index 8d246413a..be7bf7a8c 100644 --- a/packages/hydrooj/src/lib/hash.hydro.ts +++ b/packages/hydrooj/src/lib/hash.hydro.ts @@ -9,5 +9,5 @@ function hash(password: string, salt: string): Promise { }); } -export = hash; +export default hash; global.Hydro.module.hash.hydro = hash; From 9971745e7354818801fa7c49a791bacfd45067f3 Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 14 Jun 2024 13:56:03 +0800 Subject: [PATCH 10/41] fix --- packages/hydrooj/src/lib/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/hydrooj/src/lib/index.ts b/packages/hydrooj/src/lib/index.ts index 0b836d544..9ba5eff22 100644 --- a/packages/hydrooj/src/lib/index.ts +++ b/packages/hydrooj/src/lib/index.ts @@ -1,7 +1,6 @@ import './i18n'; import './mail'; import './hash.hydro'; -import './rank'; import './ui'; import './testdataConfig'; import './difficulty'; From 2c00a4233e70147cf04bfaffa4014d4588db3a43 Mon Sep 17 00:00:00 2001 From: smallfang <59567465+smallfangqwq@users.noreply.github.com> Date: Fri, 14 Jun 2024 14:05:48 +0800 Subject: [PATCH 11/41] ui: fix dropdown bug (#814) --- packages/ui-default/components/dropdown/dropdown.page.styl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ui-default/components/dropdown/dropdown.page.styl b/packages/ui-default/components/dropdown/dropdown.page.styl index 03ecb8ad3..2af5705b9 100644 --- a/packages/ui-default/components/dropdown/dropdown.page.styl +++ b/packages/ui-default/components/dropdown/dropdown.page.styl @@ -6,9 +6,13 @@ .drop.nav__dropdown max-height: calc(100% - 60px) + overflow-x: hidden overflow-y: auto box-shadow: 0rem 0.125rem 0.4375rem rgba(0 0 0 , 30%); +.drop.nav__dropdown.drop-open + overflow-x: auto + .dropdown .menu min-width: rem(150px) From 5803a1d94daf9b2d0894b83fab76f905bf782326 Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 14 Jun 2024 14:42:07 +0800 Subject: [PATCH 12/41] utils: subtask matcher: support uppercase --- packages/utils/lib/common.ts | 52 ++++++++++++++---------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/packages/utils/lib/common.ts b/packages/utils/lib/common.ts index be43a3521..657dcbd68 100644 --- a/packages/utils/lib/common.ts +++ b/packages/utils/lib/common.ts @@ -236,7 +236,7 @@ export function sortFiles(files: Record[] | string[], key = '_id') interface MatchRule { regex: RegExp; - output: ((a: RegExpExecArray) => string)[]; + output: (a: RegExpExecArray) => string[]; id: (a: RegExpExecArray) => number; subtask: (a: RegExpExecArray) => number; preferredScorerType: (a: RegExpExecArray) => 'min' | 'max' | 'sum'; @@ -244,47 +244,35 @@ interface MatchRule { const SubtaskMatcher: MatchRule[] = [ { - regex: /^(([A-Za-z0-9._-]*?)(?:(\d*)[-_])?(\d+))\.(in|txt|in\.txt)$/, - output: [ - (a) => `${a[1]}.out`, - (a) => `${a[1]}.out.txt`, - (a) => `${a[1]}.ans`, - (a) => `${a[1]}.ans.txt`, - (a) => `${a[1]}.out`.replace(/input/g, 'output'), - (a) => (a[1].includes('input') ? `${a[1]}.txt`.replace(/input/g, 'output') : null), - ], + regex: /^(([A-Za-z0-9._-]*?)(?:(\d*)[-_])?(\d+))\.(in|IN|txt|TXT|in\.txt|IN\.TXT)$/, + output: (a) => ['out', 'ans'] + .flatMap((i) => [i, i.toUpperCase(), `${i}.txt`, `${i.toUpperCase()}.TXT`]) + .flatMap((i) => [`${a[1]}.${i}`, `${a[1]}.${i}`.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')]) + .concat(a[1].includes('input') ? `${a[1]}.txt`.replace(/input/g, 'output') : null), id: (a) => +a[4], subtask: (a) => +(a[3] || 1), preferredScorerType: (a) => (a[3] ? 'min' : 'sum'), }, { - regex: /^([^\d]*)\.in(\d+)$/, - output: [ - (a) => `${a[1]}.ou${a[2]}`, - (a) => `${a[1]}.ou${a[2]}`.replace(/input/g, 'output'), - (a) => `${a[1]}.out${a[2]}`, - (a) => `${a[1]}.out${a[2]}`.replace(/input/g, 'output'), - ], + regex: /^([^\d]*)\.(in|IN)(\d+)$/, + output: (a) => [ + `${a[1]}.${a[2] === 'in' ? 'ou' : 'OU'}${a[3]}`, + `${a[1]}.${a[2] === 'in' ? 'out' : 'OUT'}${a[3]}`, + ].flatMap((i) => [i, i.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')]), id: (a) => +a[2], subtask: () => 1, preferredScorerType: () => 'sum', }, { - regex: /^([^\d]*)([0-9]+)([-_])([0-9]+)\.in$/, - output: [ - (a) => `${a[1]}${a[2]}${a[3]}${a[4]}.out`, - (a) => `${a[1]}${a[2]}${a[3]}${a[4]}.ans`, - ], + regex: /^([^\d]*)([0-9]+)([-_])([0-9]+)\.(in|IN)$/, + output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}${a[2]}${a[3]}${a[4]}.${i}`), id: (a) => +a[4], subtask: (a) => +a[2], preferredScorerType: () => 'min', }, { - regex: /^(([0-9]+)[-_](?:.*))\.in$/, - output: [ - (a) => `${a[1]}.out`, - (a) => `${a[1]}.ans`, - ], + regex: /^(([0-9]+)[-_](?:.*))\.(in|IN)$/, + output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}.${i}`), id: (a) => +a[2], subtask: () => 1, preferredScorerType: () => 'sum', @@ -329,12 +317,12 @@ export function readSubtasksFromFiles(files: string[], config) { const sid = rule.subtask(data); const c = { input: file, output: '', id: rule.id(data) }; const type = rule.preferredScorerType(data); - for (const func of rule.output) { - if (config.noOutputFile) c.output = '/dev/null'; - else c.output = func(data); - if (c.output === file) continue; - if (c.output === '/dev/null' || files.includes(c.output)) { + const outputs = (config.noOutputFile ? ['/dev/null'] : rule.output(data)).filter((i) => i); + for (const output of outputs) { + if (output === file) continue; + if (output === '/dev/null' || files.includes(output)) { match = true; + c.output = output; if (!subtask[sid]) { subtask[sid] = { time: config.time, From e62a7fe3948fdb53700b0a8378fede8b1c91ebbb Mon Sep 17 00:00:00 2001 From: undefined Date: Sun, 16 Jun 2024 14:33:54 +0800 Subject: [PATCH 13/41] core&judge: probe compiler versions --- packages/hydrojudge/src/compiler.ts | 18 +++++++++++++ packages/hydrojudge/src/hosts/builtin.ts | 18 +++++++++++-- packages/hydrojudge/src/hosts/hydro.ts | 13 ++++++--- packages/hydrooj/setting.yaml | 19 +++++++++++-- packages/hydrooj/src/handler/status.ts | 34 ++++++++++++++++++++++-- packages/utils/lib/lang.ts | 1 + 6 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 packages/hydrojudge/src/compiler.ts diff --git a/packages/hydrojudge/src/compiler.ts b/packages/hydrojudge/src/compiler.ts new file mode 100644 index 000000000..bcf5050ac --- /dev/null +++ b/packages/hydrojudge/src/compiler.ts @@ -0,0 +1,18 @@ +import { LangConfig } from '@hydrooj/utils/lib/lang'; +import { runQueued } from './sandbox'; + +export async function compilerVersions(langs: Record) { + const result = {}; + for (const lang in langs) { + const version = langs[lang].version; + if (!version) continue; + // eslint-disable-next-line no-await-in-loop + const res = await runQueued(version, { + copyIn: {}, + time: 10000, + memory: 256 * 1024 * 1024, + }, 5); + result[lang] = `${res.stdout}\n${res.stderr}`.trim(); + } + return result; +} diff --git a/packages/hydrojudge/src/hosts/builtin.ts b/packages/hydrojudge/src/hosts/builtin.ts index 455132618..9e78e6da2 100644 --- a/packages/hydrojudge/src/hosts/builtin.ts +++ b/packages/hydrojudge/src/hosts/builtin.ts @@ -2,10 +2,13 @@ /* eslint-disable no-await-in-loop */ import path from 'path'; import { fs } from '@hydrooj/utils'; +import * as sysinfo from '@hydrooj/utils/lib/sysinfo'; import { - JudgeHandler, JudgeResultBody, ObjectId, RecordModel, SettingModel, - StorageModel, SystemModel, TaskModel, + db, JudgeHandler, JudgeResultBody, ObjectId, RecordModel, + SettingModel, StorageModel, SystemModel, TaskModel, } from 'hydrooj'; +import { langs } from 'hydrooj/src/model/setting'; +import { compilerVersions } from '../compiler'; import { getConfig } from '../config'; import { FormatError, SystemError } from '../error'; import { Context } from '../judge/interface'; @@ -82,6 +85,7 @@ export async function postInit(ctx) { c.check.addChecker('Judge', (_ctx, log, warn, error) => versionCheck(warn, error)); }); await fs.ensureDir(getConfig('tmp_dir')); + const info = await sysinfo.get(); const handle = async (t) => { const rdoc = await RecordModel.get(t.domainId, t.rid); if (!rdoc) { @@ -92,9 +96,19 @@ export async function postInit(ctx) { }; const parallelism = Math.max(getConfig('parallelism'), 2); const taskConsumer = TaskModel.consume({ type: 'judge' }, handle, true, parallelism); + function collectCompilerInfo() { + const coll = db.collection('status'); + compilerVersions(langs).then((compilers) => coll.updateOne( + { mid: info.mid, type: 'judge' }, + { $set: { compilers } }, + { upsert: true }, + )).catch((e) => logger.error(e)); + } ctx.on('system/setting', () => { taskConsumer.setConcurrency(Math.max(getConfig('parallelism'), 2)); + collectCompilerInfo(); }); + collectCompilerInfo(); TaskModel.consume({ type: 'judge', priority: { $gt: -50 } }, handle); TaskModel.consume({ type: 'generate' }, handle); } diff --git a/packages/hydrojudge/src/hosts/hydro.ts b/packages/hydrojudge/src/hosts/hydro.ts index 9bcad2147..ef501778b 100644 --- a/packages/hydrojudge/src/hosts/hydro.ts +++ b/packages/hydrojudge/src/hosts/hydro.ts @@ -7,6 +7,7 @@ import { fs, pipeRequest } from '@hydrooj/utils'; import { LangConfig } from '@hydrooj/utils/lib/lang'; import * as sysinfo from '@hydrooj/utils/lib/sysinfo'; import type { JudgeResultBody } from 'hydrooj'; +import { compilerVersions } from '../compiler'; import { getConfig } from '../config'; import { FormatError, SystemError } from '../error'; import { Session } from '../interface'; @@ -173,13 +174,19 @@ export default class Hydro implements Session { const content = Object.keys(config).length ? JSON.stringify({ key: 'config', ...config }) : '{"key":"ping"}'; + let compilers = {}; this.ws.on('message', (data) => { if (data.toString() === 'ping') { this.ws.send('pong'); return; } const request = JSON.parse(data.toString()); - if (request.language) this.language = request.language; + if (request.language) { + this.language = request.language; + compilerVersions(this.language).then((res) => { + compilers = res; + }); + } if (request.task) queue.add(() => new JudgeTask(this, request.task).handle().catch((e) => log.error(e))); }); this.ws.on('close', (data, reason) => { @@ -196,10 +203,10 @@ export default class Hydro implements Session { this.ws.send('{"key":"start"}'); if (!this.config.noStatus) { const info = await sysinfo.get(); - this.ws.send(JSON.stringify({ key: 'status', info })); + this.ws.send(JSON.stringify({ key: 'status', info: { ...info, compilers } })); setInterval(async () => { const [mid, inf] = await sysinfo.update(); - this.ws.send(JSON.stringify({ key: 'status', info: { mid, ...inf } })); + this.ws.send(JSON.stringify({ key: 'status', info: { mid, ...inf, compilers } })); }, 1200000); } resolve(null); diff --git a/packages/hydrooj/setting.yaml b/packages/hydrooj/setting.yaml index 5ff8a6ede..e9e3b6501 100644 --- a/packages/hydrooj/setting.yaml +++ b/packages/hydrooj/setting.yaml @@ -23,12 +23,12 @@ langs: - ~ All keys starting with _ will be ignored - ~ Entries named like [foo], [foo].[bar] [foo].[baz] - will be merged into one category. - - ~ A entry named [foo].[bar] will inhert all options + - ~ A entry named [foo].[bar] will inhert all options - defined in entry [foo] if not overwritten - ~ code_file option defaults to foo.[name] - ~ highlight option defaults to [name] - ~ if highlight option was set, monaco option defaults - - to [highlight], otherwise defaults to [name] + - to [highlight], otherwise defaults to [name] - ~ Compiling be be ignored if compile option wasn't set - ~ Key marked with a ? means the option is optional - (name): @@ -49,11 +49,13 @@ langs: code_file: foo.sh execute: /bin/bash foo.sh highlight: bash + version: /bin/bash --version c: compile: /usr/bin/gcc -Wall --std=c99 -o foo foo.c -lm highlight: c monaco: c display: C + version: /usr/bin/gcc --version cc: compile: /usr/bin/g++ -Wall -std=c++14 -o foo foo.cc -lm -I/include code_file: foo.cc @@ -62,6 +64,7 @@ langs: display: C++ address_space_limit: true process_limit: 1 + version: /usr/bin/g++ --version cc.cc98: compile: /usr/bin/g++ -Wall -std=c++98 -o foo foo.cc -lm -I/include display: C++98 @@ -95,6 +98,7 @@ langs: compile: /usr/bin/fpc -O2 -o/w/foo foo.pas highlight: pascal display: Pascal + version: /usr/bin/fpc -iW java: compile: /usr/bin/bash -c "javac -d /w -encoding utf8 ./Main.java && jar cvf Main.jar *.class >/dev/null" code_file: Main.java @@ -105,6 +109,7 @@ langs: monaco: java display: Java compile_time_limit: 30000 + version: /usr/bin/javac -version kt: compile: echo "Invalid language selection" code_file: foo.kt @@ -119,6 +124,7 @@ langs: execute: /usr/bin/java -jar foo.jar display: Kotlin/JVM compile_time_limit: 30000 + version: /usr/bin/kotlinc -version py: compile: /usr/bin/python -c "import py_compile; py_compile.compile('/w/foo.py', '/w/foo', doraise=True)" code_file: foo.py @@ -132,38 +138,47 @@ langs: compile: /usr/bin/python3 -c "import py_compile; py_compile.compile('/w/foo.py', '/w/foo', doraise=True)" execute: /usr/bin/python3 foo display: Python 3 + version: /usr/bin/python3 --version py.pypy3: compile: /bin/bash -c "/usr/bin/pypy3 -c \"import py_compile; py_compile.compile('/w/foo.py', '/w/foo', doraise=True)\" && mv foo.py foo" execute: /bin/bash -c 'mv foo foo.py && /usr/bin/pypy3 -B foo.py' display: PyPy3 + version: /usr/bin/pypy3 --version php: execute: /usr/bin/php foo.php display: PHP + version: /usr/bin/php -v rs: compile: /usr/bin/rustc -O -o /w/foo /w/foo.rs highlight: rust display: Rust + version: /usr/bin/rustc --version hs: compile: /usr/bin/ghc -O -outputdir /tmp -o foo foo.hs highlight: haskell display: Haskell + version: /usr/bin/ghc --version js: execute: /usr/bin/node /w/foo.js highlight: javascript display: NodeJS + version: /usr/bin/node --version go: compile: /usr/bin/go build -o foo foo.go display: Golang + version: /usr/bin/go version rb: execute: /usr/bin/ruby foo.rb highlight: ruby display: Ruby + version: /usr/bin/ruby --version cs: compile: /usr/bin/mcs -optimize+ -out:/w/foo /w/foo.cs execute: /usr/bin/mono foo highlight: csharp monaco: csharp display: 'C#' + version: /usr/bin/mcs --version r: display: R execute: /bin/sh -c "cat /dev/stdin >stdin && /usr/bin/Rscript --vanilla foo.r" diff --git a/packages/hydrooj/src/handler/status.ts b/packages/hydrooj/src/handler/status.ts index 5d0137aef..2f9fc3816 100644 --- a/packages/hydrooj/src/handler/status.ts +++ b/packages/hydrooj/src/handler/status.ts @@ -25,11 +25,41 @@ async function getStatus() { class StatusHandler extends Handler { async get() { const stats = await getStatus(); + const compilers = {}; + const warn = {}; + const result: Array<{ key: string[], message: string }> = []; + // For each language, select the most common compiler message version, + // then merge languages with the same message. for (const stat of stats) { - if (!stat.battery.hasBattery) stat.battery = 'No battery'; + if (!stat.battery?.hasBattery) stat.battery = 'No battery'; else stat.battery = `${stat.battery.type} ${stat.battery.model} ${stat.battery.percent}%${stat.battery.isCharging ? ' Charging' : ''}`; + if (stat.compilers) { + for (const key in stat.compilers) { + if (!compilers[key]) compilers[key] ||= []; + const related = compilers[key].find((i) => i.message === stat.compilers[key]); + if (related) related.related.push(stat._id); + else { + compilers[key].push({ + related: [stat._id], + message: stat.compilers[key], + }); + } + } + } } - this.response.body = { stats }; + for (const key in compilers) { + compilers[key].sort((a, b) => b.related.length - a.related.length); + const message = compilers[key][0].message; + for (let i = 1; i < compilers[key].length; i++) { + for (const id of compilers[key][i].related) { + warn[id] = true; + } + } + const t = result.find((i) => i.message === message); + if (t) t.key.push(key); + else result.push({ key: [key], message }); + } + this.response.body = { stats, compilers: result }; this.response.template = 'status.html'; } } diff --git a/packages/utils/lib/lang.ts b/packages/utils/lib/lang.ts index 14f4c2c38..f794f5a2c 100644 --- a/packages/utils/lib/lang.ts +++ b/packages/utils/lib/lang.ts @@ -24,6 +24,7 @@ export interface LangConfig { comment?: string | [string, string]; compile_time_limit?: number; compile_memory_limit?: number; + version?: string; } export function parseLang(config: string): Record { From 047d0c8a8ba12285a43b419bae6f36685c1cf1a3 Mon Sep 17 00:00:00 2001 From: undefined Date: Tue, 18 Jun 2024 17:46:41 +0800 Subject: [PATCH 14/41] a11y: add performance test tool --- packages/a11y/index.ts | 4 +- packages/a11y/performance-test.ts | 252 ++++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 packages/a11y/performance-test.ts diff --git a/packages/a11y/index.ts b/packages/a11y/index.ts index 4d9fc0fcf..e9b47505c 100644 --- a/packages/a11y/index.ts +++ b/packages/a11y/index.ts @@ -1,8 +1,10 @@ -import { Context, UserModel } from 'hydrooj'; +import { Context, Schema, UserModel } from 'hydrooj'; +import { startPerformanceTest } from './performance-test'; export async function apply(ctx: Context) { // Auto authorize user 2 as super admin ctx.on('handler/after/UserRegisterWithCode#post', async (that) => { if (that.session.uid === 2) await UserModel.setSuperAdmin(2); }); + ctx.addScript('performance-test', 'test', Schema.object({}), startPerformanceTest); } diff --git a/packages/a11y/performance-test.ts b/packages/a11y/performance-test.ts new file mode 100644 index 000000000..d95b98e1d --- /dev/null +++ b/packages/a11y/performance-test.ts @@ -0,0 +1,252 @@ +/* eslint-disable no-await-in-loop */ +import { + ProblemModel, RecordModel, sleep, STATUS, yaml, +} from 'hydrooj'; + +const TESTS = { + 'test1': `//Test 1 +#include + +using namespace std; + +int main(){ + int a=1000000000,b=1; + while(a)b<<=1,a--; + printf("%d\\n",b); + return 0; +}`, + 'test2': `//Test 2 +#include + +using namespace std; +const int MX=50000000; +int p[MX],m[MX],pc; +int main(){ + for(int i=2;i + +using namespace std; +const int MX=1000; +int G[MX][MX]; +int sed=0; +inline int rand(){return sed=(sed*sed*73+sed*233+19260817)&0x0000ffff;} +int main(){ + for(int i=0;iG[j][i]+G[i][k])G[j][k]=G[j][i]+G[i][k]; + int ans=0; + for(int i=0;i +#include +#include + +using namespace std; + +const int MX=1000000; +int sed=0; +inline int rand(){return sed=(sed*sed*73+sed*233+19260817);} +int main(){ + setS; + for(int i=0;i::iterator it=S.begin();it!=S.end();it++)ans^=*it; + printf("%d\\n",ans); + return 0; +}`, + 'test6-1': `//Test 6-1 +#include + +using namespace std; +const int MX=1<<25; +int a[MX]; + +inline unsigned int rand(){static unsigned int sed=0;return (sed=(sed*233+19260421))&(MX-1);} + +int main(){ + for(int i=0;i + +using namespace std; +const int MX=1<<25; +int a[MX]; + +inline unsigned int rand(){static unsigned int sed=0;return (sed=(sed*(MX+1)+1025))&(MX-1);} + +int main(){ + for(int i=0;i + +using namespace std; +const int MX=1<<25; +int a[MX]; + +inline unsigned int rand(){static unsigned int sed=0;return (sed=(sed*(MX+1)+1))&(MX-1);} + +int main(){ + for(int i=0;i + +using namespace std; +typedef unsigned long long ull; + +#define P 1000000007 +const int MX=100000000; + +int main(){ + ull ans=1; + for(int i=1;i + +using namespace std; +typedef unsigned long long ull; + +int P=1000000007; +const int MX=100000000; + +int main(){ + ull ans=1; + for(int i=1;i + +using namespace std; + +const int MX=20000000; + +int main(){ + double ans=0.61234567898765,t=1,s=0; + for(int i=1;i + +using namespace std; +typedef unsigned int uint; + +const int MX=1<<10; +uint a[MX][MX],b[MX][MX]; + +inline uint rand(){static unsigned int sed=0;return (sed=(sed*233+19260421))&(MX-1);} + +int main(){ + register int i,j,k; + for(i=0;i + +using namespace std; +typedef unsigned int uint; + +const int MX=1<<10; +uint a[MX][MX],b[MX][MX]; + +inline uint rand(){static unsigned int sed=0;return (sed=(sed*233+19260421))&(MX-1);} + +int main(){ + register int i,j,k; + for(i=0;i { + const id = await RecordModel.add('system', docId, 1, 'cc.cc14o2', TESTS[key], true); + while ([ + STATUS.STATUS_WAITING, STATUS.STATUS_JUDGING, STATUS.STATUS_FETCHED, STATUS.STATUS_COMPILING, + ].includes((await RecordModel.get('system', id))?.status)) { + await sleep(500); + } + const result = await RecordModel.get('system', id); + if (result.status !== STATUS.STATUS_ACCEPTED && result.status !== STATUS.STATUS_WRONG_ANSWER) { + report({ message: `Test ${key} failed (${id}) ${result.status}` }); + } + const timeArr = result.testCases.map((i) => i.time); + const avgTime = Math.sum(...timeArr) / 20; + report({ message: '' }); + report({ message: `Test ${key} Avg: ${Math.floor(avgTime)}` }); + report({ message: `Max: ${Math.floor(Math.max(...timeArr))} / Min: ${Math.floor(Math.min(...timeArr))}` }); + report({ message: `Variance: ${Math.floor(Math.sum(...timeArr.map((i) => (i - avgTime) ** 2)) / 20)}` }); + report({ message: '' }); + })); + return true; +} From dc6fa6d46e8bae47ee48aa09184db925ba5ef0bd Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 19 Jun 2024 02:06:40 +0800 Subject: [PATCH 15/41] a11y: performance: add test5 --- packages/a11y/index.ts | 2 +- packages/a11y/performance-test.ts | 161 +++++++++++------------------- 2 files changed, 57 insertions(+), 106 deletions(-) diff --git a/packages/a11y/index.ts b/packages/a11y/index.ts index e9b47505c..0e582a027 100644 --- a/packages/a11y/index.ts +++ b/packages/a11y/index.ts @@ -6,5 +6,5 @@ export async function apply(ctx: Context) { ctx.on('handler/after/UserRegisterWithCode#post', async (that) => { if (that.session.uid === 2) await UserModel.setSuperAdmin(2); }); - ctx.addScript('performance-test', 'test', Schema.object({}), startPerformanceTest); + ctx.addScript('performance-test', 'test', Schema.object({ enable5: Schema.boolean().default(false) }), startPerformanceTest); } diff --git a/packages/a11y/performance-test.ts b/packages/a11y/performance-test.ts index d95b98e1d..263bb71b7 100644 --- a/packages/a11y/performance-test.ts +++ b/packages/a11y/performance-test.ts @@ -3,22 +3,16 @@ import { ProblemModel, RecordModel, sleep, STATUS, yaml, } from 'hydrooj'; +// These tests are from https://loj.ac/d/425 +const head = '#include\nusing namespace std;\n'; const TESTS = { - 'test1': `//Test 1 -#include - -using namespace std; - + ' 1 ': ` int main(){ int a=1000000000,b=1; while(a)b<<=1,a--; - printf("%d\\n",b); - return 0; -}`, - 'test2': `//Test 2 -#include + printf("%d\\n",b);`, -using namespace std; + ' 2 ': ` const int MX=50000000; int p[MX],m[MX],pc; int main(){ @@ -29,14 +23,9 @@ int main(){ } int ans=0; for(int i=1;i<=pc;i++)ans^=p[i]; - printf("%d\\n",ans); - return 0; -}`, - 'test3': ` -//Test 3 -#include + printf("%d\\n",ans);`, -using namespace std; + ' 3 ': ` const int MX=1000; int G[MX][MX]; int sed=0; @@ -53,16 +42,11 @@ int main(){ for(int i=0;i + printf("%d\\n",ans);`, + + ' 4 ': ` #include #include - -using namespace std; - const int MX=1000000; int sed=0; inline int rand(){return sed=(sed*sed*73+sed*233+19260817);} @@ -71,102 +55,70 @@ int main(){ for(int i=0;i::iterator it=S.begin();it!=S.end();it++)ans^=*it; - printf("%d\\n",ans); - return 0; -}`, - 'test6-1': `//Test 6-1 -#include + printf("%d\\n",ans);`, -using namespace std; + // skipping test 5 by default as it contains a lot of ram + ' 5 ': ` +const int MX=20000000; +int *it[MX]; +int main(){ + for(int i=0;i + for(int i=0;i + for(int i=0;i + for(int i=0;i + printf("%llu\\n",ans);`, -using namespace std; + '7-2': ` typedef unsigned long long ull; - int P=1000000007; const int MX=100000000; - int main(){ ull ans=1; for(int i=1;i - -using namespace std; + printf("%llu\\n",ans);`, + ' 8 ': ` const int MX=20000000; - int main(){ double ans=0.61234567898765,t=1,s=0; for(int i=1;i + printf("%f\\n",s);`, -using namespace std; + '9-1': ` typedef unsigned int uint; - const int MX=1<<10; uint a[MX][MX],b[MX][MX]; - inline uint rand(){static unsigned int sed=0;return (sed=(sed*233+19260421))&(MX-1);} - int main(){ register int i,j,k; for(i=0;i + printf("%u\\n",s);`, -using namespace std; + '9-2': ` typedef unsigned int uint; - const int MX=1<<10; uint a[MX][MX],b[MX][MX]; - inline uint rand(){static unsigned int sed=0;return (sed=(sed*233+19260421))&(MX-1);} - int main(){ register int i,j,k; for(i=0;i { - const id = await RecordModel.add('system', docId, 1, 'cc.cc14o2', TESTS[key], true); + if (key === ' 5 ' && !args.enable5) return; + const id = await RecordModel.add('system', docId, 1, 'cc.cc14o2', `// TEST ${key}\n${head}${TESTS[key]}\nreturn 0;}`, true); while ([ STATUS.STATUS_WAITING, STATUS.STATUS_JUDGING, STATUS.STATUS_FETCHED, STATUS.STATUS_COMPILING, ].includes((await RecordModel.get('system', id))?.status)) { @@ -239,14 +185,19 @@ export async function startPerformanceTest(args, report) { const result = await RecordModel.get('system', id); if (result.status !== STATUS.STATUS_ACCEPTED && result.status !== STATUS.STATUS_WRONG_ANSWER) { report({ message: `Test ${key} failed (${id}) ${result.status}` }); + } else { + results[key] = result.testCases.map((i) => i.time); } - const timeArr = result.testCases.map((i) => i.time); - const avgTime = Math.sum(...timeArr) / 20; - report({ message: '' }); - report({ message: `Test ${key} Avg: ${Math.floor(avgTime)}` }); - report({ message: `Max: ${Math.floor(Math.max(...timeArr))} / Min: ${Math.floor(Math.min(...timeArr))}` }); - report({ message: `Variance: ${Math.floor(Math.sum(...timeArr.map((i) => (i - avgTime) ** 2)) / 20)}` }); - report({ message: '' }); })); + const formatL = (t: number, width = 4) => Math.floor(t).toString().padEnd(width); + const formatR = (t: number, width = 4) => Math.floor(t).toString().padStart(width); + for (const key of Object.keys(TESTS)) { + const timeArr = results[key]; + if (!timeArr) continue; + const avgTime = Math.sum(...timeArr) / 20; + report({ message: `-------Test ${key}-------` }); + report({ message: ` Avg: ${formatL(avgTime)} D: ${formatR(Math.sum(...timeArr.map((i) => (i - avgTime) ** 2)) / 20, 5)} ` }); + report({ message: ` Max: ${formatL(Math.max(...timeArr))} Min: ${formatR(Math.min(...timeArr))} ` }); + } return true; } From 82b73a95e0ce50883c0954ff8a4417b3e9c306cb Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 19 Jun 2024 13:04:39 +0800 Subject: [PATCH 16/41] license: mark register and framework as MIT --- README.md | 3 ++- framework/framework/package.json | 3 ++- {packages => framework}/register/index.js | 0 {packages => framework}/register/package.json | 0 4 files changed, 4 insertions(+), 2 deletions(-) rename {packages => framework}/register/index.js (100%) rename {packages => framework}/register/package.json (100%) diff --git a/README.md b/README.md index edf3cc724..50adde9c8 100644 --- a/README.md +++ b/README.md @@ -634,7 +634,8 @@ Hydro 用户群:1085853538 ## 开源许可 -本项目中的 examples/ install/ packages/ui-default/ 下的内容仅采用 AGPL-3.0 进行授权。 +本项目中 framework/ examples/ install/ 下的内容采用 MIT 协议授权,您可自由使用。 +本项目中 packages/ui-default/ 下的内容仅采用 AGPL-3.0 进行授权。 项目其余部分使用双重许可: 1. 您可以在遵守 AGPL-3.0 许可证和下述附加条款章节的前提下免费使用这些代码: diff --git a/framework/framework/package.json b/framework/framework/package.json index b17d0cdb9..c2d9eacf3 100644 --- a/framework/framework/package.json +++ b/framework/framework/package.json @@ -2,6 +2,7 @@ "name": "@hydrooj/framework", "version": "0.0.3", "main": "index.ts", + "license": "MIT", "peerDependencies": { "cordis": "*" }, @@ -19,7 +20,7 @@ "sanitize-filename": "^1.6.3", "saslprep": "^1.0.3", "shorty.js": "^1.0.1", - "ws": "^8.17.0" + "ws": "^8.17.1" }, "devDependencies": { "@types/fs-extra": "^11.0.4", diff --git a/packages/register/index.js b/framework/register/index.js similarity index 100% rename from packages/register/index.js rename to framework/register/index.js diff --git a/packages/register/package.json b/framework/register/package.json similarity index 100% rename from packages/register/package.json rename to framework/register/package.json From fc2ac236570f8518b1709579be50a1efed20a31e Mon Sep 17 00:00:00 2001 From: undefined Date: Sat, 22 Jun 2024 23:35:07 +0800 Subject: [PATCH 17/41] core: storage: use soft copy (#830) --- packages/hydrooj/src/interface.ts | 2 ++ packages/hydrooj/src/model/problem.ts | 3 ++- packages/hydrooj/src/model/storage.ts | 26 ++++++++++++++++++------- packages/hydrooj/src/service/storage.ts | 22 ++------------------- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 5ce54e0de..572ea162d 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -644,6 +644,8 @@ export interface FileNode { size?: number; /** AutoDelete */ autoDelete?: Date; + /** fileId if linked to an existing file */ + link?: string; owner?: number; operator?: number[]; meta?: Record; diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index b372c0fad..618c3f6ae 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -262,7 +262,8 @@ export class ProblemModel { const res = await Promise.all([ document.deleteOne(domainId, document.TYPE_PROBLEM, docId), document.deleteMultiStatus(domainId, document.TYPE_PROBLEM, { docId }), - storage.list(`problem/${domainId}/${docId}/`).then((items) => storage.del(items.map((item) => item.prefix + item.name))), + storage.list(`problem/${domainId}/${docId}/`) + .then((items) => storage.del(items.map((item) => `problem/${domainId}/${docId}/${item.name}`))), bus.parallel('problem/delete', domainId, docId), ]); await bus.emit('problem/del', domainId, docId); diff --git a/packages/hydrooj/src/model/storage.ts b/packages/hydrooj/src/model/storage.ts index cd9c6631d..c0f2972ed 100644 --- a/packages/hydrooj/src/model/storage.ts +++ b/packages/hydrooj/src/model/storage.ts @@ -1,5 +1,5 @@ import { extname } from 'path'; -import { escapeRegExp } from 'lodash'; +import { escapeRegExp, omit } from 'lodash'; import moment from 'moment-timezone'; import { nanoid } from 'nanoid'; import type { Readable } from 'stream'; @@ -39,7 +39,7 @@ export class StorageModel { { $set: { lastUsage: new Date() } }, { returnDocument: 'after' }, ); - return await storage.get(value?._id || path, savePath); + return await storage.get(value?.link || value?._id || path, savePath); } static async rename(path: string, newPath: string, operator = 1) { @@ -51,6 +51,20 @@ export class StorageModel { static async del(path: string[], operator = 1) { if (!path.length) return; + const affected = await StorageModel.coll.find({ path: { $in: path } }).toArray(); + if (!affected.length) return; + const linked = await StorageModel.coll.find({ link: { $in: affected.map((i) => i._id) }, path: { $nin: path } }).toArray(); + const processedIds = []; + for (const i of linked || []) { + if (processedIds.includes(i.link)) continue; + const current = affected.find((t) => t._id === t.link); // to be deleted + // eslint-disable-next-line no-await-in-loop + await Promise.all([ + StorageModel.coll.updateOne({ _id: current._id }, { $set: omit(i, ['_id']) }), + StorageModel.coll.updateOne({ _id: i._id }, { $set: omit(current, ['_id']), $unset: { link: '' } }), + ]); + processedIds.push(i.link); + } const autoDelete = moment().add(7, 'day').toDate(); await StorageModel.coll.updateMany( { path: { $in: path }, autoDelete: null }, @@ -66,7 +80,7 @@ export class StorageModel { autoDelete: null, }).toArray(); return results.map((i) => ({ - ...i, name: i.path.split(target)[1], prefix: target, + ...i, name: i.path.split(target)[1], })); } @@ -106,12 +120,10 @@ export class StorageModel { // Make sure id is not used // eslint-disable-next-line no-await-in-loop while (await StorageModel.coll.findOne({ _id })) _id = StorageModel.generateId(extname(dst)); - const result = await storage.copy(value._id, _id); - const { metaData, size, etag } = await storage.getMeta(_id); await StorageModel.coll.insertOne({ - _id, meta: metaData, path: dst, size, etag, lastModified: new Date(), owner: value.owner || 1, + ...value, _id, path: dst, link: value._id, lastModified: new Date(), owner: value.owner || 1, }); - return result; + return _id; } } diff --git a/packages/hydrooj/src/service/storage.ts b/packages/hydrooj/src/service/storage.ts index fdb15a668..06b7c54df 100644 --- a/packages/hydrooj/src/service/storage.ts +++ b/packages/hydrooj/src/service/storage.ts @@ -2,8 +2,8 @@ import { dirname, resolve } from 'path'; import { PassThrough, Readable } from 'stream'; import { URL } from 'url'; import { - CopyObjectCommand, DeleteObjectCommand, DeleteObjectsCommand, - GetObjectCommand, HeadObjectCommand, PutObjectCommand, PutObjectCommandInput, S3Client, + DeleteObjectCommand, DeleteObjectsCommand, GetObjectCommand, + HeadObjectCommand, PutObjectCommand, PutObjectCommandInput, S3Client, } from '@aws-sdk/client-s3'; import { Upload } from '@aws-sdk/lib-storage'; import { createPresignedPost } from '@aws-sdk/s3-presigned-post'; @@ -243,16 +243,6 @@ class RemoteStorageService { return { url, fields }; } - async copy(src: string, target: string) { - src = convertPath(src); - target = convertPath(target); - return await this.client.send(new CopyObjectCommand({ - Bucket: this.bucket, - Key: target, - CopySource: src, - })); - } - async status() { return { type: 'S3', @@ -332,14 +322,6 @@ class LocalStorageService { throw new Error('Not implemented'); } - async copy(src: string, target: string) { - src = resolve(this.dir, convertPath(src)); - target = resolve(this.dir, convertPath(target)); - await ensureDir(dirname(target)); - await copyFile(src, target); - return { etag: target, lastModified: new Date() }; - } - async status() { return { type: 'Local', From 7be29b19f6ce0c5c20f07cbc07aadc16adde79fe Mon Sep 17 00:00:00 2001 From: undefined Date: Tue, 25 Jun 2024 14:06:46 +0800 Subject: [PATCH 18/41] core: fix setup --- packages/hydrooj/src/entry/worker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hydrooj/src/entry/worker.ts b/packages/hydrooj/src/entry/worker.ts index eed061989..88ce696ca 100644 --- a/packages/hydrooj/src/entry/worker.ts +++ b/packages/hydrooj/src/entry/worker.ts @@ -26,7 +26,7 @@ export async function apply(ctx: Context) { const config = load(); if (!process.env.CI && !config) { logger.info('Starting setup'); - await require('./setup').load(); + await require('./setup').load(ctx); } const pending = global.addons; const fail = []; From 34f470d50fd006336c3979d4b607941ba67ea829 Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 26 Jun 2024 00:00:10 +0800 Subject: [PATCH 19/41] core: record: add index [domainId, pid] --- package.json | 4 ++-- packages/elastic/package.json | 2 +- packages/eslint-config/package.json | 10 +++++----- packages/hydrojudge/package.json | 4 ++-- packages/hydrooj/package.json | 16 ++++++++-------- packages/hydrooj/src/model/record.ts | 13 +++++++------ packages/utils/package.json | 2 +- 7 files changed, 26 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 43f07faca..732c16fb9 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,8 @@ "fs-extra": "^11.2.0", "globby": "^14.0.1", "inspectpack": "^4.7.1", - "mongodb-memory-server": "^9.3.0", - "nyc": "^15.1.0", + "mongodb-memory-server": "^9.4.0", + "nyc": "^17.0.0", "ora": "^8.0.1", "package-json": "^10.0.0", "semver": "^7.6.2", diff --git a/packages/elastic/package.json b/packages/elastic/package.json index 805e19eaa..258d331ba 100644 --- a/packages/elastic/package.json +++ b/packages/elastic/package.json @@ -6,6 +6,6 @@ "author": "undefined ", "license": "AGPL-3.0-or-later", "dependencies": { - "@elastic/elasticsearch": "^8.13.1" + "@elastic/elasticsearch": "^8.14.0" } } diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 25cc6c4fd..2989f4751 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -4,15 +4,15 @@ "license": "MIT", "main": "react.yaml", "dependencies": { - "@stylistic/eslint-plugin": "^2.1.0", - "@typescript-eslint/eslint-plugin": "^7.13.0", - "@typescript-eslint/parser": "^7.13.0", + "@stylistic/eslint-plugin": "^2.2.2", + "@typescript-eslint/eslint-plugin": "^7.14.1", + "@typescript-eslint/parser": "^7.14.1", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^18.0.0", "eslint-plugin-github": "^5.0.1", "eslint-plugin-import": "2.29.1", - "eslint-plugin-jsx-a11y": "^6.8.0", - "eslint-plugin-react": "^7.34.2", + "eslint-plugin-jsx-a11y": "^6.9.0", + "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-simple-import-sort": "^12.1.0" }, diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 6dc24e72d..8cb3182a8 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -7,14 +7,14 @@ "repository": "https://github.com/hydro-dev/Hydro.git", "dependencies": { "@hydrooj/utils": "workspace:^", - "adm-zip": "0.5.5", + "adm-zip": "0.5.14", "cac": "^6.7.14", "p-queue": "^8.0.1", "schemastery": "^3.14.6", "semver": "^7.6.2", "shell-quote": "^1.8.1", "superagent": "^9.0.2", - "ws": "^8.17.0" + "ws": "^8.17.1" }, "preferUnplugged": true, "license": "AGPL-3.0-or-later", diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 209eecf7d..819970fdb 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -12,23 +12,23 @@ }, "preferUnplugged": true, "dependencies": { - "@aws-sdk/client-s3": "3.596.0", - "@aws-sdk/lib-storage": "3.596.0", - "@aws-sdk/s3-presigned-post": "3.596.0", - "@aws-sdk/s3-request-presigner": "3.596.0", + "@aws-sdk/client-s3": "3.600.0", + "@aws-sdk/lib-storage": "3.600.0", + "@aws-sdk/s3-presigned-post": "3.600.0", + "@aws-sdk/s3-request-presigner": "3.600.0", "@graphql-tools/schema": "^10.0.4", "@hydrooj/framework": "workspace:^", "@hydrooj/utils": "workspace:^", "@simplewebauthn/server": "7.4.0", "@undefined-moe/require-resolve-hook": "^1.1.0", - "adm-zip": "0.5.5", + "adm-zip": "0.5.14", "cac": "^6.7.14", "cordis": "3.16.0", "detect-browser": "^5.3.0", "emoji-regex": "^10.3.0", "emojis-list": "2.1.0", "fs-extra": "^11.2.0", - "graphql": "^16.8.2", + "graphql": "^16.9.0", "graphql-scalars": "^1.23.0", "js-yaml": "^4.1.0", "koa-proxies": "^0.12.4", @@ -40,7 +40,7 @@ "mongodb": "^5.9.2", "mongodb-uri": "^0.9.7", "nanoid": "^5.0.7", - "nodemailer": "^6.9.13", + "nodemailer": "^6.9.14", "notp": "^2.0.3", "p-queue": "^8.0.1", "path-to-regexp": "^6.2.2", @@ -52,7 +52,7 @@ "superagent": "^9.0.2", "tar": "6.2.1", "thirty-two": "^1.0.2", - "ws": "^8.17.0" + "ws": "^8.17.1" }, "devDependencies": { "@types/adm-zip": "^0.4.34", diff --git a/packages/hydrooj/src/model/record.ts b/packages/hydrooj/src/model/record.ts index 093cf0664..087b1bccc 100644 --- a/packages/hydrooj/src/model/record.ts +++ b/packages/hydrooj/src/model/record.ts @@ -280,22 +280,23 @@ export function apply(ctx: Context) { }, { upsert: true }); } }); - ctx.on('ready', async () => { - await db.ensureIndexes( + ctx.on('ready', () => Promise.all([ + db.ensureIndexes( RecordModel.coll, + { key: { domainId: 1, pid: 1 }, name: 'delete' }, { key: { domainId: 1, contest: 1, _id: -1 }, name: 'basic' }, { key: { domainId: 1, contest: 1, uid: 1, _id: -1 }, name: 'withUser' }, { key: { domainId: 1, contest: 1, pid: 1, _id: -1 }, name: 'withProblem' }, { key: { domainId: 1, contest: 1, pid: 1, uid: 1, _id: -1 }, name: 'withUserAndProblem' }, { key: { domainId: 1, contest: 1, status: 1, _id: -1 }, name: 'withStatus' }, - ); - await db.ensureIndexes( + ), + db.ensureIndexes( RecordModel.collStat, { key: { domainId: 1, pid: 1, uid: 1, _id: -1 }, name: 'basic' }, { key: { domainId: 1, pid: 1, uid: 1, time: 1 }, name: 'time' }, { key: { domainId: 1, pid: 1, uid: 1, memory: 1 }, name: 'memory' }, { key: { domainId: 1, pid: 1, uid: 1, length: 1 }, name: 'length' }, - ); - }); + ), + ]) as any); } global.Hydro.model.record = RecordModel; diff --git a/packages/utils/package.json b/packages/utils/package.json index 7273454a3..7062de1fb 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -14,7 +14,7 @@ "js-yaml": "^4.1.0", "moment-timezone": "^0.5.45", "mongodb": "^5.9.2", - "reggol": "^1.6.3", + "reggol": "^1.7.0", "search-query-parser": "^1.6.0", "systeminformation": "^5.22.11" }, From 970104f227f28ef390f8cc0b8c4fe7647e7b4278 Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 26 Jun 2024 00:23:16 +0800 Subject: [PATCH 20/41] core&ui: add compiler versions display --- build/prepare.js | 26 ++--- packages/hydrooj/src/handler/status.ts | 9 +- packages/ui-default/components/tab/Tab.js | 2 - .../ui-default/components/tab/tab.page.styl | 20 ++++ packages/ui-default/index.ts | 7 -- packages/ui-default/package.json | 14 +-- .../ui-default/pages/problem_edit.page.styl | 13 --- packages/ui-default/templates/status.html | 100 ++++++++++++------ packages/ui-default/templates/wiki_help.html | 9 +- 9 files changed, 107 insertions(+), 93 deletions(-) diff --git a/build/prepare.js b/build/prepare.js index 2f579a136..a10578c2e 100644 --- a/build/prepare.js +++ b/build/prepare.js @@ -72,7 +72,7 @@ for (const name of ['plugins', 'modules']) { const modules = [ 'packages/hydrooj', - ...['packages', 'framework', 'modules'].flatMap((i) => fs.readdirSync(path.resolve(process.cwd(), i)).map((j) => `${i}/${j}`)), + ...['packages', 'framework'].flatMap((i) => fs.readdirSync(path.resolve(process.cwd(), i)).map((j) => `${i}/${j}`)), ].filter((i) => !i.includes('/.') && !i.includes('ui-default')).filter((i) => fs.statSync(path.resolve(process.cwd(), i)).isDirectory()); const UIConfig = { @@ -80,22 +80,18 @@ const UIConfig = { 'packages/ui-default/public', '**/node_modules', ], - include: ['ts', 'tsx'] - .flatMap((ext) => ['plugins', 'modules'] + include: ['ts', 'tsx', 'vue'] + .flatMap((ext) => ['plugins'] .flatMap((name) => [`${name}/**/public/**/*.${ext}`, `${name}/**/frontend/**/*.${ext}`]) .concat(`packages/ui-default/**/*.${ext}`)), compilerOptions: { - experimentalDecorators: true, - esModuleInterop: true, - resolveJsonModule: true, - jsx: 'react', + ...compilerOptionsBase, module: 'ESNext', skipLibCheck: true, allowSyntheticDefaultImports: true, - target: 'es2020', baseUrl: '.', outDir: path.join(baseOutDir, 'ui'), - moduleResolution: 'node', + moduleResolution: 'bundler', paths: { 'vj/*': [ './packages/ui-default/*', @@ -120,17 +116,7 @@ const pluginsConfig = { ], exclude, compilerOptions: { - target: 'es2020', - module: 'commonjs', - esModuleInterop: true, - moduleResolution: 'node', - jsx: 'react', - sourceMap: false, - composite: true, - strictBindCallApply: true, - resolveJsonModule: true, - experimentalDecorators: true, - incremental: true, + ...compilerOptionsBase, rootDir: '.', baseUrl: '.', outDir: path.join(baseOutDir, 'plugins'), diff --git a/packages/hydrooj/src/handler/status.ts b/packages/hydrooj/src/handler/status.ts index 2f9fc3816..f19c23a75 100644 --- a/packages/hydrooj/src/handler/status.ts +++ b/packages/hydrooj/src/handler/status.ts @@ -3,6 +3,7 @@ import { PRIV } from '../model/builtin'; import * as DocumentModel from '../model/document'; import DomainModel from '../model/domain'; import RecordModel from '../model/record'; +import * as SettingModel from '../model/setting'; import UserModel from '../model/user'; import db from '../service/db'; import { Handler } from '../service/server'; @@ -59,7 +60,13 @@ class StatusHandler extends Handler { if (t) t.key.push(key); else result.push({ key: [key], message }); } - this.response.body = { stats, compilers: result }; + const LANGS = SettingModel.langs; + const languages = {}; + for (const key in LANGS) { + if (LANGS[key].hidden) continue; + languages[`${LANGS[key].display}(${key})`] = LANGS[key].compile || LANGS[key].execute; + } + this.response.body = { stats, languages, compilers: result }; this.response.template = 'status.html'; } } diff --git a/packages/ui-default/components/tab/Tab.js b/packages/ui-default/components/tab/Tab.js index 64ab314cc..1d10129f9 100644 --- a/packages/ui-default/components/tab/Tab.js +++ b/packages/ui-default/components/tab/Tab.js @@ -144,5 +144,3 @@ export default class Tab extends DOMAttachedObject { return true; } } - -_.assign(Tab, DOMAttachedObject); diff --git a/packages/ui-default/components/tab/tab.page.styl b/packages/ui-default/components/tab/tab.page.styl index 23d4de1ca..906e91e19 100644 --- a/packages/ui-default/components/tab/tab.page.styl +++ b/packages/ui-default/components/tab/tab.page.styl @@ -73,6 +73,26 @@ background: $secondary-color content: '' +.section__tab-container:not(.immersive) + .section__tab-header + padding-top: 0 !important + + .section__tab-header-item + border-top: 3px solid transparent; + transition: border-color 0.2s, background 0.2s, color 0.2s; + transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + margin-right: 0 + + &:hover + border-color: #5f9fd6; + background: #f8f8f8; + + &.tab--active + border-color: #ed5f82; + + &.selected + border-color: #ed5f82; + .section__tab-content position: relative diff --git a/packages/ui-default/index.ts b/packages/ui-default/index.ts index ae9d64b57..95ee9733d 100644 --- a/packages/ui-default/index.ts +++ b/packages/ui-default/index.ts @@ -10,13 +10,6 @@ class WikiHelpHandler extends Handler { noCheckPermView = true; async get() { - const LANGS = SettingModel.langs; - const languages = {}; - for (const key in LANGS) { - if (LANGS[key].hidden) continue; - languages[`${LANGS[key].display}(${key})`] = LANGS[key].compile || LANGS[key].execute; - } - this.response.body = { languages }; this.response.template = 'wiki_help.html'; } } diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 568215c5a..e377b8ddb 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -15,7 +15,7 @@ }, "devDependencies": { "@blueprintjs/core": "^5.10.3", - "@cordisjs/core": "^3.16.2", + "@cordisjs/core": "^3.17.3", "@fontsource/dm-mono": "^5.0.20", "@fontsource/fira-code": "^5.0.18", "@fontsource/inconsolata": "^5.0.18", @@ -25,8 +25,8 @@ "@fontsource/source-code-pro": "^5.0.18", "@fontsource/ubuntu-mono": "^5.0.20", "@hydrooj/utils": "workspace:^", - "@sentry/browser": "^8.9.2", - "@sentry/webpack-plugin": "^2.18.0", + "@sentry/browser": "^8.11.0", + "@sentry/webpack-plugin": "^2.20.0", "@simplewebauthn/browser": "7.4.0", "@svgr/webpack": "^8.1.0", "@types/gulp-if": "^0.0.34", @@ -45,7 +45,7 @@ "ajv": "^8.16.0", "allotment": "^1.20.2", "autoprefixer": "^10.4.19", - "browser-update": "^3.3.51", + "browser-update": "^3.3.53", "chalk": "^5.3.0", "classnames": "^2.5.1", "clean-webpack-plugin": "^4.0.0", @@ -57,7 +57,7 @@ "echarts": "^5.5.0", "emojis-keywords": "2.0.0", "emojis-list": "2.1.0", - "esbuild-loader": "^4.1.0", + "esbuild-loader": "^4.2.0", "fancy-log": "^2.0.0", "flatpickr": "^4.6.13", "graphiql": "^2.4.7", @@ -77,7 +77,7 @@ "monaco-editor-nls": "^3.1.0", "monaco-editor-webpack-plugin": "^7.1.0", "monaco-themes": "^0.4.4", - "monaco-yaml": "^5.1.1", + "monaco-yaml": "5.1.1", "nanoid": "^5.0.7", "normalize.css": "^8.0.1", "nprogress": "^0.2.0", @@ -120,7 +120,7 @@ "vditor": "^3.10.4", "vinyl-buffer": "^1.0.1", "web-streams-polyfill": "^4.0.0", - "webpack": "^5.92.0", + "webpack": "^5.92.1", "webpack-bundle-analyzer": "^4.10.2", "webpack-dev-server": "^5.0.4", "webpack-manifest-plugin": "^5.0.0", diff --git a/packages/ui-default/pages/problem_edit.page.styl b/packages/ui-default/pages/problem_edit.page.styl index 083869c79..ba0a7d6ab 100644 --- a/packages/ui-default/pages/problem_edit.page.styl +++ b/packages/ui-default/pages/problem_edit.page.styl @@ -1,17 +1,4 @@ .page--problem_edit,.page--problem_create - .section__tab-header-item - border-top: 3px solid transparent; - transition: border-color 0.2s, background 0.2s, color 0.2s; - transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); - margin-right: 0 - - &:hover - border-color: #5f9fd6; - background: #f8f8f8; - - &.tab--active - border-color: #ed5f82; - .problem-sidebar-additional_file height: 270px overflow-y: auto diff --git a/packages/ui-default/templates/status.html b/packages/ui-default/templates/status.html index bbfa2cad0..df337aadd 100644 --- a/packages/ui-default/templates/status.html +++ b/packages/ui-default/templates/status.html @@ -3,41 +3,71 @@
-
-
- - - - - - - - - - - - - - - - - - - - - {%- for doc in stats -%} - - - - - - - - - {%- endfor -%} - -
{{ _('ID') }}{{ _('Current Status') }}{{ _('OS Info') }}{{ _('CPU Info') }}{{ _('Memory Info') }}{{ _('Handled requests') }}
{{ doc._id.toHexString()|truncate(8,True,'') }}{% if doc.isOnline %}{{ doc.status }}{% else %}{{ datetimeSpan(doc.updateAt)|safe }}{% endif %}{{ doc.osinfo.distro }} {{ doc.osinfo.release }} {{ doc.osinfo.codename }} {{ doc.osinfo.arch }}{{ doc.cpu.manufacturer }} {{ doc.cpu.brand }}@{{ doc.cpu.speed }}GHz{{ size(doc.memory.used) }}/{{ size(doc.memory.total) }}{{ doc.reqCount }}
-
+
+
    +
  • +

    {{ _('Servers') }}

    +
    + + + + + + + + + + + + + + + + + + + + + {%- for doc in stats -%} + + + + + + + + + {%- endfor -%} + +
    {{ _('ID') }}{{ _('Current Status') }}{{ _('OS Info') }}{{ _('CPU Info') }}{{ _('Memory Info') }}{{ _('Handled requests') }}
    {{ doc._id.toHexString()|truncate(8,True,'') }}{% if doc.isOnline %}{{ doc.status }}{% else %}{{ datetimeSpan(doc.updateAt)|safe }}{% endif %}{{ doc.osinfo.distro }} {{ doc.osinfo.release }} {{ doc.osinfo.codename }} {{ doc.osinfo.arch }}{{ doc.cpu.manufacturer }} {{ doc.cpu.brand }}@{{ doc.cpu.speed }}GHz{{ size(doc.memory.used) }}/{{ size(doc.memory.total) }}{{ doc.reqCount }}
    +
    +
  • + {% if compilers|length %} +
  • +

    {{ _('Compiler Versions') }}

    +
    +
    + {% for line in compilers %} +

    {{ line.key|join(', ') }}

    +
    {{ line.message }}
    + {% endfor %} +
    +
    +
  • + {% endif %} +
  • +

    {{ _('Compile Commands') }}

    +
    +
    +
      + {%- for k, v in languages -%} +
    • {{ k }}
      {{ v }}
    • + {%- endfor -%} +
    +
    +
    +
  • +
diff --git a/packages/ui-default/templates/wiki_help.html b/packages/ui-default/templates/wiki_help.html index 5407431c2..e901e25a8 100644 --- a/packages/ui-default/templates/wiki_help.html +++ b/packages/ui-default/templates/wiki_help.html @@ -17,14 +17,7 @@

{{ _("Compilers' Version and Parameters") }}

-

{{ name }} 使用 HydroJudge 进行评测,编译参数如下:

- {% if languages %} -
    - {%- for k, v in languages -%} -
  • {{ k }}
    {{ v }}
  • - {%- endfor -%} -
- {% endif %} +

{{ name }} 使用 HydroJudge 进行评测,编译参数和当前所用编译器版本见状态页。

From 5b083241ef2021d4328198b60d3ec630a3b715c5 Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 26 Jun 2024 00:36:32 +0800 Subject: [PATCH 21/41] fix --- build/prepare.js | 3 +-- packages/hydrooj/src/interface.ts | 4 ++-- packages/ui-default/components/tab/Tab.js | 1 - packages/utils/package.json | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/build/prepare.js b/build/prepare.js index a10578c2e..52cd75024 100644 --- a/build/prepare.js +++ b/build/prepare.js @@ -80,7 +80,7 @@ const UIConfig = { 'packages/ui-default/public', '**/node_modules', ], - include: ['ts', 'tsx', 'vue'] + include: ['ts', 'tsx', 'vue', 'json'] .flatMap((ext) => ['plugins'] .flatMap((name) => [`${name}/**/public/**/*.${ext}`, `${name}/**/frontend/**/*.${ext}`]) .concat(`packages/ui-default/**/*.${ext}`)), @@ -91,7 +91,6 @@ const UIConfig = { allowSyntheticDefaultImports: true, baseUrl: '.', outDir: path.join(baseOutDir, 'ui'), - moduleResolution: 'bundler', paths: { 'vj/*': [ './packages/ui-default/*', diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 572ea162d..34a959fbe 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -1,5 +1,5 @@ -import { AttestationFormat } from '@simplewebauthn/server/dist/helpers/decodeAttestationObject'; -import { AuthenticationExtensionsAuthenticatorOutputs } from '@simplewebauthn/server/dist/helpers/decodeAuthenticatorExtensions'; +import type { AttestationFormat } from '@simplewebauthn/server/dist/helpers/decodeAttestationObject'; +import type { AuthenticationExtensionsAuthenticatorOutputs } from '@simplewebauthn/server/dist/helpers/decodeAuthenticatorExtensions'; import { CredentialDeviceType } from '@simplewebauthn/typescript-types'; import type fs from 'fs'; import type { Dictionary, NumericDictionary } from 'lodash'; diff --git a/packages/ui-default/components/tab/Tab.js b/packages/ui-default/components/tab/Tab.js index 1d10129f9..b2ba79de5 100644 --- a/packages/ui-default/components/tab/Tab.js +++ b/packages/ui-default/components/tab/Tab.js @@ -1,5 +1,4 @@ import $ from 'jquery'; -import _ from 'lodash'; import DOMAttachedObject from 'vj/components/DOMAttachedObject'; const TAB_TRANSITION_DURATION = 300; diff --git a/packages/utils/package.json b/packages/utils/package.json index 7062de1fb..7273454a3 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -14,7 +14,7 @@ "js-yaml": "^4.1.0", "moment-timezone": "^0.5.45", "mongodb": "^5.9.2", - "reggol": "^1.7.0", + "reggol": "^1.6.3", "search-query-parser": "^1.6.0", "systeminformation": "^5.22.11" }, From 61af311048752c46c73f663d49c3324450ce23ec Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 26 Jun 2024 11:39:33 +0800 Subject: [PATCH 22/41] build: add sentry token --- .github/workflows/build.yml | 2 ++ packages/eslint-config/package.json | 2 +- .../components/problemconfig/ProblemConfigEditor.tsx | 4 ++-- packages/ui-default/package.json | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1f34de607..80f4e4a11 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,6 +67,8 @@ jobs: else parallel --tty -j+0 yarn ::: lint:ci build test fi + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Publish if: ${{ github.event_name == 'push' }} run: node -r @hydrooj/register build/publish.ts diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 2989f4751..2582dc23a 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -4,7 +4,7 @@ "license": "MIT", "main": "react.yaml", "dependencies": { - "@stylistic/eslint-plugin": "^2.2.2", + "@stylistic/eslint-plugin": "^2.3.0", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.14.1", "eslint-config-airbnb": "^19.0.4", diff --git a/packages/ui-default/components/problemconfig/ProblemConfigEditor.tsx b/packages/ui-default/components/problemconfig/ProblemConfigEditor.tsx index d381d605b..e08e18c2d 100644 --- a/packages/ui-default/components/problemconfig/ProblemConfigEditor.tsx +++ b/packages/ui-default/components/problemconfig/ProblemConfigEditor.tsx @@ -87,8 +87,8 @@ export function configYamlFormat(config: ProblemConfigFile) { export default connect(mapStateToProps, mapDispatchToProps)(class MonacoEditor extends React.PureComponent { disposable = []; containerElement: HTMLElement; - private __preventUpdate = false; - private __preventFormat = false; + __preventUpdate = false; + __preventFormat = false; editor: editor.IStandaloneCodeEditor; model: editor.ITextModel; diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index e377b8ddb..076e06b3c 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -25,8 +25,8 @@ "@fontsource/source-code-pro": "^5.0.18", "@fontsource/ubuntu-mono": "^5.0.20", "@hydrooj/utils": "workspace:^", - "@sentry/browser": "^8.11.0", - "@sentry/webpack-plugin": "^2.20.0", + "@sentry/browser": "^8.12.0", + "@sentry/webpack-plugin": "^2.20.1", "@simplewebauthn/browser": "7.4.0", "@svgr/webpack": "^8.1.0", "@types/gulp-if": "^0.0.34", From 16b6f53653ca8f9b0ee99f13dfed0ca9825db352 Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 26 Jun 2024 14:22:30 +0800 Subject: [PATCH 23/41] bump version --- README.md | 27 +++++++++++++++++++++++++++ changelog | 6 ------ framework/framework/package.json | 2 +- framework/register/package.json | 2 +- packages/a11y/package.json | 2 +- packages/elastic/package.json | 2 +- packages/eslint-config/package.json | 2 +- packages/hydrojudge/package.json | 2 +- packages/hydrooj/package.json | 2 +- packages/ui-default/package.json | 2 +- packages/utils/package.json | 2 +- 11 files changed, 36 insertions(+), 15 deletions(-) delete mode 100644 changelog diff --git a/README.md b/README.md index 50adde9c8..00cb97b2c 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,33 @@ Hydro 用户群:1085853538

更新日志(点击展开)

+### Hydro 4.13.0 / UI 4.51.0 +- ui: 添加 sentry +- core&ui: 支持显示当前编译器版本 +- core: 数据库索引优化 +- core: 文件复制使用软链接 +- a11y: 添加性能测试工具 +- utils: 支持识别大写文件扩展名 +- ui: 优化下拉菜单样式 +- core: 优化页面标题 +- register: 支持从文件读取原 sourcemap +- ui: 修复站内消息推送 +- ui: ranking 页面添加说明 +- ui: 添加生成测试数据提示 +- framework: 从 core 解离 +- core: loader: 支持多 profile 切换 +- ui: 修复比赛计分板选手组显示 +- core: 重设比赛分数时自动重算分数 + +本版本同时引入下述插件 API 修改: + +- 移除了 app/load/${category} 钩子 +- 移除了 ctx.app (请使用 ctx.root) +- 移除了 ctx.options (请使用 ctx.root.config) +- serializer: 移除 showDisplayName 参数 +- 移除了 loader.addScript, loader.addon 函数 +- 移除了 Hydro.module.render (请使用 ctx.server.registerRenderer) + ### Hydro 4.12.3 / UI 4.50.2 - core: 将 API 模块移入 service - core: 比赛时提交被 hack 不触发整体重测 diff --git a/changelog b/changelog deleted file mode 100644 index 7947fd9a1..000000000 --- a/changelog +++ /dev/null @@ -1,6 +0,0 @@ -移除了 app/listen, app/load/${category} 钩子 -移除了 ctx.app (请使用 ctx.root) -移除了 ctx.options (请使用 ctx.root.config) -serializer: 移除 showDisplayName 参数 -移除了 loader.addScript, loader.addon 函数 -移除了 Hydro.module.render (请使用 ctx.server.registerRenderer) \ No newline at end of file diff --git a/framework/framework/package.json b/framework/framework/package.json index c2d9eacf3..d0f855daf 100644 --- a/framework/framework/package.json +++ b/framework/framework/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/framework", - "version": "0.0.3", + "version": "0.0.4", "main": "index.ts", "license": "MIT", "peerDependencies": { diff --git a/framework/register/package.json b/framework/register/package.json index a8a60a18e..48fb75e48 100644 --- a/framework/register/package.json +++ b/framework/register/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/register", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "main": "index.js", "engines": { diff --git a/packages/a11y/package.json b/packages/a11y/package.json index afde7a87d..7d16ff5fd 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -1,4 +1,4 @@ { "name": "@hydrooj/a11y", - "version": "0.0.1" + "version": "0.0.2" } diff --git a/packages/elastic/package.json b/packages/elastic/package.json index 258d331ba..cda59af1d 100644 --- a/packages/elastic/package.json +++ b/packages/elastic/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/elastic-search", - "version": "1.1.4", + "version": "1.1.5", "main": "index.ts", "repository": "https://github.com/hydro-dev/Hydro", "author": "undefined ", diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 2582dc23a..bf6ed7bb0 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/eslint-config", - "version": "1.0.10", + "version": "1.0.11", "license": "MIT", "main": "react.yaml", "dependencies": { diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 8cb3182a8..329cf6aab 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -1,7 +1,7 @@ { "name": "@hydrooj/hydrojudge", "bin": "bin/hydrojudge.js", - "version": "3.3.13", + "version": "3.3.14", "main": "./src/index.ts", "author": "undefined ", "repository": "https://github.com/hydro-dev/Hydro.git", diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 819970fdb..18873b8e3 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "4.12.3", + "version": "4.13.0", "bin": "bin/hydrooj.js", "main": "src/plugin-api", "module": "src/plugin-api", diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 076e06b3c..8a85c7963 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.50.2", + "version": "4.51.0", "author": "undefined ", "license": "AGPL-3.0", "main": "index.ts", diff --git a/packages/utils/package.json b/packages/utils/package.json index 7273454a3..4f3360268 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/utils", - "version": "1.4.24", + "version": "1.4.25", "description": "hydrooj utils", "main": "lib/utils.ts", "repository": "https://github.com/hydro-dev/Hydro.git", From 84f7d5dc803414a4c9b6865ef5c405edaae13f31 Mon Sep 17 00:00:00 2001 From: undefined Date: Thu, 27 Jun 2024 03:58:29 +0800 Subject: [PATCH 24/41] bug fixes --- README.md | 3 ++- packages/hydrojudge/package.json | 2 +- packages/hydrojudge/src/hosts/builtin.ts | 2 +- packages/hydrojudge/src/hosts/hydro.ts | 7 ++++++- packages/hydrooj/package.json | 2 +- packages/hydrooj/src/model/setting.ts | 1 + packages/hydrooj/src/service/layers/domain.ts | 11 +++++++++-- packages/hydrooj/src/service/layers/user.ts | 5 ----- packages/hydrooj/src/service/server.ts | 1 + packages/ui-default/build/config/webpack.ts | 1 + packages/ui-default/build/main.ts | 5 ++++- .../autocomplete/CustomSelectAutoComplete.tsx | 3 +-- .../autocomplete/FileSelectAutoComplete.tsx | 3 +-- packages/ui-default/components/autocomplete/index.tsx | 7 ++++--- packages/ui-default/components/message/index.page.ts | 8 ++------ packages/ui-default/entry.js | 2 +- packages/ui-default/package.json | 4 ++-- packages/ui-default/polyfill.ts | 3 +-- packages/ui-default/templates/layout/html5.html | 2 +- packages/ui-default/utils/base.ts | 6 +++--- 20 files changed, 43 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 00cb97b2c..df7a93513 100644 --- a/README.md +++ b/README.md @@ -137,11 +137,12 @@ Hydro 用户群:1085853538 - serializer: 移除 showDisplayName 参数 - 移除了 loader.addScript, loader.addon 函数 - 移除了 Hydro.module.render (请使用 ctx.server.registerRenderer) +- 移除了 lib/paginate 和 lib/rank (请使用 db.paginate 和 db.ranked) ### Hydro 4.12.3 / UI 4.50.2 - core: 将 API 模块移入 service - core: 比赛时提交被 hack 不触发整体重测 -- core: 将添加 pid match 的逻辑移入搜索中 +- core: 将添加 pid match 的逻辑移入搜索中 - ui: 修正 cssFilter - judge: builtin: 维护 callback 顺序 - core: 优化邮件地址处理 diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 329cf6aab..065f07433 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -1,7 +1,7 @@ { "name": "@hydrooj/hydrojudge", "bin": "bin/hydrojudge.js", - "version": "3.3.14", + "version": "3.3.15", "main": "./src/index.ts", "author": "undefined ", "repository": "https://github.com/hydro-dev/Hydro.git", diff --git a/packages/hydrojudge/src/hosts/builtin.ts b/packages/hydrojudge/src/hosts/builtin.ts index 9e78e6da2..889ce399b 100644 --- a/packages/hydrojudge/src/hosts/builtin.ts +++ b/packages/hydrojudge/src/hosts/builtin.ts @@ -99,7 +99,7 @@ export async function postInit(ctx) { function collectCompilerInfo() { const coll = db.collection('status'); compilerVersions(langs).then((compilers) => coll.updateOne( - { mid: info.mid, type: 'judge' }, + { mid: info.mid, type: 'server' }, { $set: { compilers } }, { upsert: true }, )).catch((e) => logger.error(e)); diff --git a/packages/hydrojudge/src/hosts/hydro.ts b/packages/hydrojudge/src/hosts/hydro.ts index ef501778b..eaf5e81c4 100644 --- a/packages/hydrojudge/src/hosts/hydro.ts +++ b/packages/hydrojudge/src/hosts/hydro.ts @@ -175,6 +175,7 @@ export default class Hydro implements Session { ? JSON.stringify({ key: 'config', ...config }) : '{"key":"ping"}'; let compilers = {}; + let compilerVersionCallback = () => { }; this.ws.on('message', (data) => { if (data.toString() === 'ping') { this.ws.send('pong'); @@ -185,6 +186,7 @@ export default class Hydro implements Session { this.language = request.language; compilerVersions(this.language).then((res) => { compilers = res; + compilerVersionCallback(); }); } if (request.task) queue.add(() => new JudgeTask(this, request.task).handle().catch((e) => log.error(e))); @@ -203,7 +205,10 @@ export default class Hydro implements Session { this.ws.send('{"key":"start"}'); if (!this.config.noStatus) { const info = await sysinfo.get(); - this.ws.send(JSON.stringify({ key: 'status', info: { ...info, compilers } })); + this.ws.send(JSON.stringify({ key: 'status', info: { ...info } })); + compilerVersionCallback = () => { + this.ws.send(JSON.stringify({ key: 'status', info: { ...info, compilers } })); + }; setInterval(async () => { const [mid, inf] = await sysinfo.update(); this.ws.send(JSON.stringify({ key: 'status', info: { mid, ...inf, compilers } })); diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 18873b8e3..f750db6bb 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "4.13.0", + "version": "4.13.1", "bin": "bin/hydrooj.js", "main": "src/plugin-api", "module": "src/plugin-api", diff --git a/packages/hydrooj/src/model/setting.ts b/packages/hydrooj/src/model/setting.ts index 8b7d7f1d2..feb7358b8 100644 --- a/packages/hydrooj/src/model/setting.ts +++ b/packages/hydrooj/src/model/setting.ts @@ -274,6 +274,7 @@ SystemSetting( Setting('setting_basic', 'pagination.contest', 20, 'number', 'pagination.contest', 'Contests per page'), Setting('setting_basic', 'pagination.discussion', 50, 'number', 'pagination.discussion', 'Discussions per page'), Setting('setting_basic', 'pagination.record', 100, 'number', 'pagination.record', 'Records per page'), + Setting('setting_basic', 'pagination.ranking', 100, 'number', 'pagination.ranking', 'Users per page'), Setting('setting_basic', 'pagination.solution', 20, 'number', 'pagination.solution', 'Solutions per page'), Setting('setting_basic', 'pagination.training', 10, 'number', 'pagination.training', 'Trainings per page'), Setting('setting_basic', 'pagination.reply', 50, 'number', 'pagination.reply', 'Replies per page'), diff --git a/packages/hydrooj/src/service/layers/domain.ts b/packages/hydrooj/src/service/layers/domain.ts index 93a43bb14..a9f1c92ae 100644 --- a/packages/hydrooj/src/service/layers/domain.ts +++ b/packages/hydrooj/src/service/layers/domain.ts @@ -1,4 +1,4 @@ -import { KoaContext } from '@hydrooj/framework'; +import { KoaContext, NotFoundError } from '@hydrooj/framework'; import BlackListModel from '../../model/blacklist'; import DomainModel from '../../model/domain'; import * as system from '../../model/system'; @@ -24,5 +24,12 @@ export default async (ctx: KoaContext, next) => { ctx.domainId = inferDomain?._id || domainId; ctx.domainInfo = inferDomain || absoluteDomain; if (ctx.domainInfo && ctx.domainId !== ctx.domainInfo._id) ctx.redirect(ctx.originalPath.replace(/^\/d\/[^/]+\//, `/d/${ctx.domainInfo._id}/`)); - else await next(); + else { + if (!ctx.domainInfo) { + ctx.pendingError = new NotFoundError(ctx.domainId); + ctx.domainId = 'system'; + ctx.domainInfo = await DomainModel.get('system'); + } + await next(); + } }; diff --git a/packages/hydrooj/src/service/layers/user.ts b/packages/hydrooj/src/service/layers/user.ts index 60575f45e..c9df9cb76 100644 --- a/packages/hydrooj/src/service/layers/user.ts +++ b/packages/hydrooj/src/service/layers/user.ts @@ -1,5 +1,4 @@ import type { KoaContext } from '@hydrooj/framework'; -import { NotFoundError } from '../../error'; import { PERM } from '../../model/builtin'; import UserModel from '../../model/user'; @@ -15,9 +14,5 @@ export default async (ctx: KoaContext, next) => { } if (user._id === 0) delete user.viewLang; ctx.HydroContext.user = await user.private(); - if (!domain) { - ctx.pendingError = new NotFoundError(args.domainId); - args.domainId = 'system'; - } await next(); }; diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index d75126913..cad5626b8 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -276,6 +276,7 @@ export async function apply(ctx: Context) { text: global.Hydro.module.oauth[key].text, })); if (!h.noCheckPermView && !h.user.hasPriv(PRIV.PRIV_VIEW_ALL_DOMAIN)) h.checkPerm(PERM.PERM_VIEW); + if (h.context.pendingError) throw h.context.pendingError; }); on('app/listen', () => { diff --git a/packages/ui-default/build/config/webpack.ts b/packages/ui-default/build/config/webpack.ts index 7781491d8..7e19e3ec4 100644 --- a/packages/ui-default/build/config/webpack.ts +++ b/packages/ui-default/build/config/webpack.ts @@ -89,6 +89,7 @@ export default function (env: { watch?: boolean, production?: boolean, measure?: output: { path: root('public'), publicPath: '/', // overwrite in entry.js + workerPublicPath: '/', hashFunction: 'sha1', hashDigest: 'hex', hashDigestLength: 10, diff --git a/packages/ui-default/build/main.ts b/packages/ui-default/build/main.ts index 0c9fb5ffa..646cdc45e 100644 --- a/packages/ui-default/build/main.ts +++ b/packages/ui-default/build/main.ts @@ -49,7 +49,10 @@ async function runWebpack({ return; } if (argv.options.detail) console.log(stats.toString()); - if (!watch && (!stats || stats.hasErrors())) process.exitCode = 1; + if (!watch && (!stats || stats.hasErrors())) { + if (!argv.options.detail) console.log(stats.toString()); + process.exitCode = 1; + } resolve(stats); } if (watch) compiler.watch({}, compilerCallback); diff --git a/packages/ui-default/components/autocomplete/CustomSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/CustomSelectAutoComplete.tsx index c8f3c82a3..760850fa9 100644 --- a/packages/ui-default/components/autocomplete/CustomSelectAutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/CustomSelectAutoComplete.tsx @@ -24,9 +24,8 @@ const Component = React.forwardRef((props, ref) => { ); }); -export default class CustomSelectAutoComplete extends AutoComplete { +export default class CustomSelectAutoComplete extends AutoComplete { static DOMAttachKey = 'ucwCustomSelectAutoCompleteInstance'; - options: CustomSelectOptions & AutoCompleteOptions; constructor($dom, options: CustomSelectOptions & AutoCompleteOptions) { super($dom, { diff --git a/packages/ui-default/components/autocomplete/FileSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/FileSelectAutoComplete.tsx index e128c9e6b..a30207072 100644 --- a/packages/ui-default/components/autocomplete/FileSelectAutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/FileSelectAutoComplete.tsx @@ -27,9 +27,8 @@ const Component = React.forwardRef((props, ref) => { ); }); -export default class FileSelectAutoComplete extends AutoComplete { +export default class FileSelectAutoComplete extends AutoComplete { static DOMAttachKey = 'ucwFileSelectAutoCompleteInstance'; - options: FileSelectOptions & AutoCompleteOptions; constructor($dom, options: FileSelectOptions & AutoCompleteOptions) { super($dom, { diff --git a/packages/ui-default/components/autocomplete/index.tsx b/packages/ui-default/components/autocomplete/index.tsx index 2c603c9f0..52f0a6a01 100644 --- a/packages/ui-default/components/autocomplete/index.tsx +++ b/packages/ui-default/components/autocomplete/index.tsx @@ -8,6 +8,7 @@ export interface AutoCompleteOptions { defaultItems?: string; width?: string; height?: string; + classes?: string; listStyle?: any; allowEmptyQuery?: boolean; freeSolo?: boolean; @@ -18,17 +19,17 @@ export interface AutoCompleteOptions { text?: () => string; } -export default class AutoComplete extends DOMAttachedObject { +export default class AutoComplete = {}> extends DOMAttachedObject { static DOMAttachKey = 'ucwAutoCompleteInstance'; ref = null; container = document.createElement('div'); - options: AutoCompleteOptions; + options: AutoCompleteOptions & Options; component = ReactDOM.createRoot(this.container); changeListener = [ (val) => this.$dom.val(val), ]; - constructor($dom, options = {}) { + constructor($dom, options = {} as Options) { super($dom); this.options = { items: async () => [], diff --git a/packages/ui-default/components/message/index.page.ts b/packages/ui-default/components/message/index.page.ts index 723b6610c..2c45b18a9 100644 --- a/packages/ui-default/components/message/index.page.ts +++ b/packages/ui-default/components/message/index.page.ts @@ -1,5 +1,4 @@ import { nanoid } from 'nanoid'; -import ReconnectingWebsocket from 'reconnecting-websocket'; import { InfoDialog } from 'vj/components/dialog'; import VjNotification from 'vj/components/notification/index'; import { @@ -7,6 +6,7 @@ import { } from 'vj/constant/message'; import { AutoloadPage } from 'vj/misc/Page'; import { i18n, tpl } from 'vj/utils'; +import Sock from '../socket'; let previous: VjNotification; const onmessage = (msg) => { @@ -128,12 +128,8 @@ const messagePage = new AutoloadPage('messagePage', (pagename) => { isMaster = true; localStorage.setItem('page.master', selfId); const masterChannel = new BroadcastChannel('hydro-messages'); - const sock = new ReconnectingWebsocket(endpoint); - sock.onopen = () => console.log('Connected'); - sock.onerror = console.error; - sock.onclose = (...args) => console.log('Closed', ...args); + const sock = new Sock(endpoint); sock.onmessage = async (message) => { - if (process.env.NODE_ENV !== 'production') console.log('onmessage: ', message); const payload = JSON.parse(message.data); masterChannel.postMessage({ type: 'message', payload }); }; diff --git a/packages/ui-default/entry.js b/packages/ui-default/entry.js index a9f838e58..ae344f2d9 100644 --- a/packages/ui-default/entry.js +++ b/packages/ui-default/entry.js @@ -31,7 +31,7 @@ window.UserContext = JSON.parse(window.UserContext); if (process.env.NODE_ENV === 'production' && !UiContext.sentry_disable) { window.captureException = (e) => Sentry.captureException(e); Sentry.init({ - dsn: UiContext.sentry_dsn || 'https://04d1d77b095231d55e1202bdc912ff16@sentry.hydro.ac/3', + dsn: UiContext.sentry_dsn || 'https://2f95d53751e08c74c1af1c4b93ccaff7@sentry.hydro.ac/2', release: process.env.VERSION, integrations: [ Sentry.browserTracingIntegration(), diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 8a85c7963..210528490 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.51.0", + "version": "4.51.1", "author": "undefined ", "license": "AGPL-3.0", "main": "index.ts", @@ -51,6 +51,7 @@ "clean-webpack-plugin": "^4.0.0", "clipboard": "^2.0.11", "copy-webpack-plugin": "^12.0.2", + "core-js": "^3.37.1", "css-loader": "^6.11.0", "diff": "^5.2.0", "diff-dom": "^5.1.3", @@ -90,7 +91,6 @@ "prop-types": "^15.8.1", "qface": "^1.4.1", "qrcode": "^1.5.3", - "queue-microtask": "^1.2.3", "react": "^18.3.1", "react-colorful": "^5.6.1", "react-dnd": "^16.0.1", diff --git a/packages/ui-default/polyfill.ts b/packages/ui-default/polyfill.ts index 202215e4e..0c87826d9 100644 --- a/packages/ui-default/polyfill.ts +++ b/packages/ui-default/polyfill.ts @@ -1,9 +1,8 @@ import 'matchmedia-polyfill'; +import 'core-js/stable'; import browserUpdate from 'browser-update'; -import queueMicrotask from 'queue-microtask'; -window.queueMicrotask = queueMicrotask; browserUpdate({ required: { e: -10, f: -10, o: -3, s: -1, c: -10, diff --git a/packages/ui-default/templates/layout/html5.html b/packages/ui-default/templates/layout/html5.html index dc06df6fb..0c64c4441 100644 --- a/packages/ui-default/templates/layout/html5.html +++ b/packages/ui-default/templates/layout/html5.html @@ -47,7 +47,7 @@ var _htmlNode = document.documentElement; _htmlNode.className = _htmlNode.className.replace(' nojs', ' hasjs'); try { - import('data:text/javascript,');Object.fromEntries([]);window.matchMedia('all').addEventListener('change',()=>{}); + import('data:text/javascript,');Object.fromEntries([]);window.matchMedia('all').addEventListener('change',()=>{});Array.prototype.at(0); } catch(e) { const s = document.createElement('script'); s.async = false; diff --git a/packages/ui-default/utils/base.ts b/packages/ui-default/utils/base.ts index 53744dbcb..a68f2f6b2 100644 --- a/packages/ui-default/utils/base.ts +++ b/packages/ui-default/utils/base.ts @@ -34,8 +34,8 @@ export function secureRandomString(digit = 32, dict = defaultDict) { type Substitution = string | number | { templateRaw: true, html: string }; -export function tpl(node: React.ReactNode, reactive?: boolean); -export function tpl(pieces: TemplateStringsArray, ...substitutions: Substitution[]); +export function tpl(node: React.ReactNode, reactive?: T): T extends true ? React.ReactElement : string; +export function tpl(pieces: TemplateStringsArray, ...substitutions: Substitution[]): string; export function tpl(pieces: TemplateStringsArray | React.ReactNode, ...substitutions: Substitution[] | boolean[]) { if (React.isValidElement(pieces)) { if (substitutions[0]) { @@ -49,7 +49,7 @@ export function tpl(pieces: TemplateStringsArray | React.ReactNode, ...substitut for (let i = 0; i < substitutions.length; ++i) { const subst = substitutions[i]; let substHtml: string; - if (typeof subst === 'object' && subst.templateRaw) { + if (subst && typeof subst === 'object' && subst.templateRaw) { substHtml = subst.html; } else substHtml = _.escape(String(subst)); result += substHtml + pieces[i + 1]; From c350410ee395d5b74ffb82b98931c734b8cfec7d Mon Sep 17 00:00:00 2001 From: undefined Date: Thu, 27 Jun 2024 04:03:19 +0800 Subject: [PATCH 25/41] ui: fix types --- packages/ui-default/utils/base.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui-default/utils/base.ts b/packages/ui-default/utils/base.ts index a68f2f6b2..f89726991 100644 --- a/packages/ui-default/utils/base.ts +++ b/packages/ui-default/utils/base.ts @@ -34,7 +34,7 @@ export function secureRandomString(digit = 32, dict = defaultDict) { type Substitution = string | number | { templateRaw: true, html: string }; -export function tpl(node: React.ReactNode, reactive?: T): T extends true ? React.ReactElement : string; +export function tpl(node: React.ReactNode, reactive?: T): T extends true ? HTMLDivElement : string; export function tpl(pieces: TemplateStringsArray, ...substitutions: Substitution[]): string; export function tpl(pieces: TemplateStringsArray | React.ReactNode, ...substitutions: Substitution[] | boolean[]) { if (React.isValidElement(pieces)) { From 2a8e3142b3f2d96bd20fae1b871754fd1bd9bf71 Mon Sep 17 00:00:00 2001 From: undefined Date: Thu, 27 Jun 2024 04:08:03 +0800 Subject: [PATCH 26/41] ui: fix types --- packages/ui-default/utils/base.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui-default/utils/base.ts b/packages/ui-default/utils/base.ts index f89726991..d174c012b 100644 --- a/packages/ui-default/utils/base.ts +++ b/packages/ui-default/utils/base.ts @@ -34,7 +34,7 @@ export function secureRandomString(digit = 32, dict = defaultDict) { type Substitution = string | number | { templateRaw: true, html: string }; -export function tpl(node: React.ReactNode, reactive?: T): T extends true ? HTMLDivElement : string; +export function tpl(node: React.ReactNode, reactive?: T): T extends true ? HTMLDivElement : string; export function tpl(pieces: TemplateStringsArray, ...substitutions: Substitution[]): string; export function tpl(pieces: TemplateStringsArray | React.ReactNode, ...substitutions: Substitution[] | boolean[]) { if (React.isValidElement(pieces)) { From 67307ca48da013e26f1456c0e7a9e46a116fda61 Mon Sep 17 00:00:00 2001 From: undefined Date: Thu, 27 Jun 2024 15:40:21 +0800 Subject: [PATCH 27/41] ui: add polyfill --- packages/ui-default/backendlib/builder.ts | 5 ++- packages/ui-default/build/config/webpack.ts | 37 +++++++++++++++++-- packages/ui-default/build/main.ts | 10 +---- packages/ui-default/entry.js | 14 +++---- packages/ui-default/package.json | 1 + packages/ui-default/polyfill.ts | 2 +- .../ui-default/templates/layout/html5.html | 18 ++------- 7 files changed, 48 insertions(+), 39 deletions(-) diff --git a/packages/ui-default/backendlib/builder.ts b/packages/ui-default/backendlib/builder.ts index 15d328852..cba4b3ce5 100644 --- a/packages/ui-default/backendlib/builder.ts +++ b/packages/ui-default/backendlib/builder.ts @@ -1,7 +1,7 @@ import esbuild from 'esbuild'; import { Context, fs, Handler, Logger, NotFoundError, param, SettingModel, sha1, - size, Types, UiContextBase, + size, SystemModel, Types, UiContextBase, } from 'hydrooj'; import { debounce } from 'lodash'; import { tmpdir } from 'os'; @@ -46,6 +46,7 @@ const build = async (contents: string) => { format: 'iife' as 'iife', bundle: true, outdir: tmp, + sourcemap: SystemModel.get('ui-default.nosourcemap') ? false : 'external', splitting: false, write: false, target: ['chrome65'], @@ -106,7 +107,7 @@ export async function buildUI() { ...entryPoints.map((i) => `import '${relative(tmp, i).replace(/\\/g, '\\\\')}';`), ].join('\n')); const pages = entry.outputFiles.map((i) => i.text); - const str = `window.LANGS=${JSON.stringify(SettingModel.langs)};${pages.join('\n')}`; + const str = `window.LANGS=${JSON.stringify(SettingModel.langs)};window._hydroLoad=()=>{ ${pages.join('\n')} };`; addFile('entry.js', str); UiContextBase.constantVersion = hashes['entry.js']; for (const key in vfs) { diff --git a/packages/ui-default/build/config/webpack.ts b/packages/ui-default/build/config/webpack.ts index 7e19e3ec4..e070e1891 100644 --- a/packages/ui-default/build/config/webpack.ts +++ b/packages/ui-default/build/config/webpack.ts @@ -2,18 +2,43 @@ import { sentryWebpackPlugin } from '@sentry/webpack-plugin'; import { CleanWebpackPlugin } from 'clean-webpack-plugin'; import CopyWebpackPlugin from 'copy-webpack-plugin'; +import { version as coreJsVersion } from 'core-js/package.json'; +import compat from 'core-js-compat'; import { EsbuildPlugin } from 'esbuild-loader'; +import fs from 'fs'; import { DuplicatesPlugin } from 'inspectpack/plugin'; import ExtractCssPlugin from 'mini-css-extract-plugin'; import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin'; +import packageJson from 'package-json'; import { dirname } from 'path'; +import { gt } from 'semver'; import webpack from 'webpack'; import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; import { WebpackManifestPlugin } from 'webpack-manifest-plugin'; import WebpackBar from 'webpackbar'; +import { version } from '../../package.json'; import root from '../utils/root'; -export default function (env: { watch?: boolean, production?: boolean, measure?: boolean } = {}) { +const { + list, + targets, +} = compat({ + targets: '> 0.5%, chrome 80', + modules: [ + 'core-js/stable', + ], + exclude: [], + version: coreJsVersion, + inverse: false, +}); +fs.writeFileSync(root('__core-js.js'), list.map((i) => `import 'core-js/modules/${i}';`).join('\n')); + +export default async function (env: { watch?: boolean, production?: boolean, measure?: boolean } = {}) { + if (env.production) console.log(targets); + let isNew = false; + const { version: latest } = await packageJson('@hydrooj/ui-default', { version: 'latest' }); + if (typeof version === 'string' && gt(version, latest)) isNew = true; + function esbuildLoader() { return { loader: 'esbuild-loader', @@ -73,8 +98,7 @@ export default function (env: { watch?: boolean, production?: boolean, measure?: // sentry requires source-map while keep it simple in dev mode devtool: env.production ? 'source-map' : false, entry: { - hydro: './entry.js', - polyfill: './polyfill.ts', + [`hydro-${version}`]: './entry.js', 'default.theme': './theme/default.js', 'service-worker': './service-worker.ts', }, @@ -262,6 +286,12 @@ export default function (env: { watch?: boolean, production?: boolean, measure?: org: 'hydro-dev', project: 'hydro-web', url: 'https://sentry.hydro.ac', + sourcemaps: { + rewriteSources: (source) => source.replace('@hydrooj/ui-default/../../node_modules/', ''), + }, + release: { + uploadLegacySourcemaps: (process.env.CI && isNew) ? root('public') : undefined, + }, }), new webpack.DefinePlugin({ 'process.env.VERSION': JSON.stringify(require('@hydrooj/ui-default/package.json').version), @@ -271,6 +301,7 @@ export default function (env: { watch?: boolean, production?: boolean, measure?: }), new webpack.NormalModuleReplacementPlugin(/\/(vscode-)?nls\.js/, require.resolve('../../components/monaco/nls')), new webpack.NormalModuleReplacementPlugin(/^prettier[$/]/, root('../../modules/nop.ts')), + new webpack.NormalModuleReplacementPlugin(/core-js\/stable/, root('__core-js.js')), new MonacoWebpackPlugin({ filename: '[name].[hash:6].worker.js', customLanguages: [{ diff --git a/packages/ui-default/build/main.ts b/packages/ui-default/build/main.ts index 646cdc45e..cfc9d4dba 100644 --- a/packages/ui-default/build/main.ts +++ b/packages/ui-default/build/main.ts @@ -19,7 +19,7 @@ const argv = cac().parse(); async function runWebpack({ watch, production, measure, dev, https, }) { - const compiler = webpack(webpackConfig({ watch, production, measure })); + const compiler = webpack(await webpackConfig({ watch, production, measure })); if (dev) { const server = new WebpackDevServer({ port: https ? 8001 : 8000, @@ -124,12 +124,6 @@ async function main() { if (argv.options.gulp) await runGulp(); else { await runWebpack(argv.options as any); - if (fs.existsSync('public/hydro.js')) { - fs.copyFileSync('public/hydro.js', `public/hydro-${pkg.version}.js`); - } - if (fs.existsSync('public/polyfill.js')) { - fs.copyFileSync('public/polyfill.js', `public/polyfill-${pkg.version}.js`); - } if (fs.existsSync('public/theme.css')) { fs.copyFileSync('public/theme.css', `public/theme-${pkg.version}.css`); } @@ -137,8 +131,6 @@ async function main() { for (const f of ['echarts', 'graphviz', 'mermaid', 'mathjax']) { fs.removeSync(`public/vditor/dist/js/${f}`); } - const files = fs.readdirSync('public'); - files.filter((i) => /(^[in]\..+|worker)\.js\.map$/.test(i)).forEach((i) => fs.removeSync(`public/${i}`)); } await Promise.all(globbySync('public/**/*.map').map((i) => fs.remove(i))); } diff --git a/packages/ui-default/entry.js b/packages/ui-default/entry.js index ae344f2d9..c13f762bb 100644 --- a/packages/ui-default/entry.js +++ b/packages/ui-default/entry.js @@ -1,3 +1,5 @@ +import './polyfill'; + import * as Sentry from '@sentry/browser'; import $ from 'jquery'; @@ -57,16 +59,10 @@ const PageLoader = '