mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core&ui: add setting provider
This commit is contained in:
10 files changed
+56
-28
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
import * as cordis from 'cordis';
|
import * as cordis from 'cordis';
|
||||||
import Schema from 'schemastery';
|
import Schema from 'schemastery';
|
||||||
import { GeoIP, ModuleInterfaces } from './interface';
|
import type { DomainDoc, GeoIP, ModuleInterfaces } from './interface';
|
||||||
import { inject } from './lib/ui';
|
import { inject } from './lib/ui';
|
||||||
import { Loader } from './loader';
|
import { Loader } from './loader';
|
||||||
import { EventMap } from './service/bus';
|
import { EventMap } from './service/bus';
|
||||||
@@ -33,14 +33,16 @@ export interface Context {
|
|||||||
setImmediate: typeof setImmediate;
|
setImmediate: typeof setImmediate;
|
||||||
addScript: typeof addScript;
|
addScript: typeof addScript;
|
||||||
provideModule: typeof provideModule;
|
provideModule: typeof provideModule;
|
||||||
|
/** @deprecated use injectUI instead */
|
||||||
inject: typeof inject;
|
inject: typeof inject;
|
||||||
|
injectUI: typeof inject;
|
||||||
api: ApiMixin;
|
api: ApiMixin;
|
||||||
broadcast: Context['emit'];
|
broadcast: Context['emit'];
|
||||||
geoip?: GeoIP;
|
geoip?: GeoIP;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Context extends cordis.Context {
|
export class Context extends cordis.Context {
|
||||||
static readonly session = Symbol('session');
|
domain?: DomainDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Context {
|
export namespace Context {
|
||||||
@@ -64,13 +66,14 @@ const T = <F extends (...args: any[]) => any>(origFunc: F, disposeFunc?) =>
|
|||||||
this.caller?.on('dispose', () => (disposeFunc ? disposeFunc(res) : res()));
|
this.caller?.on('dispose', () => (disposeFunc ? disposeFunc(res) : res()));
|
||||||
};
|
};
|
||||||
export class ApiMixin extends Service {
|
export class ApiMixin extends Service {
|
||||||
static readonly methods = ['addScript', 'setInterval', 'setTimeout', 'setImmediate', 'provideModule', 'inject', 'broadcast'];
|
static readonly methods = ['addScript', 'setInterval', 'setTimeout', 'setImmediate', 'provideModule', 'inject', 'injectUI', 'broadcast'];
|
||||||
addScript = T(addScript);
|
addScript = T(addScript);
|
||||||
setInterval = T(setInterval, clearInterval);
|
setInterval = T(setInterval, clearInterval);
|
||||||
setTimeout = T(setTimeout, clearTimeout);
|
setTimeout = T(setTimeout, clearTimeout);
|
||||||
setImmediate = T(setImmediate, clearImmediate);
|
setImmediate = T(setImmediate, clearImmediate);
|
||||||
provideModule = T(provideModule);
|
provideModule = T(provideModule);
|
||||||
inject = T(inject);
|
inject = T(inject);
|
||||||
|
injectUI = T(inject);
|
||||||
broadcast = (event: keyof EventMap, ...payload) => this.ctx.emit('bus/broadcast', event, payload);
|
broadcast = (event: keyof EventMap, ...payload) => this.ctx.emit('bus/broadcast', event, payload);
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
super(ctx, 'api', true);
|
super(ctx, 'api', true);
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export class HomeHandler extends Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async get({ domainId }) {
|
async get({ domainId }) {
|
||||||
const homepageConfig = this.domain.homepage || system.get('hydrooj.homepage');
|
const homepageConfig = this.ctx.setting.get('hydrooj.homepage');
|
||||||
const info = yaml.load(homepageConfig) as any;
|
const info = yaml.load(homepageConfig) as any;
|
||||||
const contents = [];
|
const contents = [];
|
||||||
for (const column of info) {
|
for (const column of info) {
|
||||||
|
|||||||
@@ -170,13 +170,16 @@ export async function load() {
|
|||||||
console.warn('DISCLAIMER:');
|
console.warn('DISCLAIMER:');
|
||||||
console.warn(' You are under development mode.');
|
console.warn(' You are under development mode.');
|
||||||
console.warn(' The Hydro project is licensed under AGPL3,');
|
console.warn(' The Hydro project is licensed under AGPL3,');
|
||||||
console.warn(' which means you have to open source your modifications,');
|
console.warn(' which means you have to open source all your modifications');
|
||||||
|
console.warn(' and keep all copyright notice');
|
||||||
console.warn(' unless you have got another license from the original author.');
|
console.warn(' unless you have got another license from the original author.');
|
||||||
console.warn('');
|
console.warn('');
|
||||||
console.warn('声明:');
|
console.warn('声明:');
|
||||||
console.warn(' 你正在运行开发者模式。');
|
console.warn(' 你正在运行开发者模式。');
|
||||||
console.warn(' Hydro 项目基于 AGPL3 协议开源,');
|
console.warn(' Hydro 项目基于 AGPL3 协议开源,');
|
||||||
console.warn(' 这意味着除非你获得了原作者的其他授权,你需要同样以 AGPL3 协议开源所有的修改。');
|
console.warn(' 这意味着除非你获得了原作者的其他授权,');
|
||||||
|
console.warn(' 你需要同样以 AGPL3 协议开源所有的修改,');
|
||||||
|
console.warn(' 并保留所有的版权声明。');
|
||||||
console.warn('\x1b[39m');
|
console.warn('\x1b[39m');
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Hydro will start in 5s.');
|
console.log('Hydro will start in 5s.');
|
||||||
|
|||||||
@@ -315,6 +315,10 @@ export class SettingService extends Service {
|
|||||||
constructor(ctx: Context) {
|
constructor(ctx: Context) {
|
||||||
super(ctx, 'setting', true);
|
super(ctx, 'setting', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get(key: string) {
|
||||||
|
return (this.caller ? this.caller.domain?.config?.[key.replace(/\./g, '$')] : null) ?? global.Hydro.model.system.get(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function apply(ctx: Context) {
|
export async function apply(ctx: Context) {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { Types } from '../lib/validator';
|
|||||||
import { Logger } from '../logger';
|
import { Logger } from '../logger';
|
||||||
import { PERM, PRIV } from '../model/builtin';
|
import { PERM, PRIV } from '../model/builtin';
|
||||||
import * as opcount from '../model/opcount';
|
import * as opcount from '../model/opcount';
|
||||||
|
import * as OplogModel from '../model/oplog';
|
||||||
import * as system from '../model/system';
|
import * as system from '../model/system';
|
||||||
import { User } from '../model/user';
|
import { User } from '../model/user';
|
||||||
import { builtinConfig } from '../settings';
|
import { builtinConfig } from '../settings';
|
||||||
@@ -119,9 +120,9 @@ export class HandlerCommon {
|
|||||||
url: (name: string, args?: any) => string;
|
url: (name: string, args?: any) => string;
|
||||||
translate: (key: string) => string;
|
translate: (key: string) => string;
|
||||||
session: Record<string, any>;
|
session: Record<string, any>;
|
||||||
|
ctx: Context;
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
domainId: string;
|
domainId: string;
|
||||||
ctx: Context = global.app;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public context: KoaContext, public readonly args: Record<string, any>,
|
public context: KoaContext, public readonly args: Record<string, any>,
|
||||||
@@ -134,6 +135,9 @@ export class HandlerCommon {
|
|||||||
this.translate = context.translate.bind(context);
|
this.translate = context.translate.bind(context);
|
||||||
this.session = context.session;
|
this.session = context.session;
|
||||||
this.domainId = args.domainId;
|
this.domainId = args.domainId;
|
||||||
|
this.ctx = global.app.extend({
|
||||||
|
domain: this.domain,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async limitRate(op: string, periodSecs: number, maxOperations: number, withUserId = system.get('limit.by_user')) {
|
async limitRate(op: string, periodSecs: number, maxOperations: number, withUserId = system.get('limit.by_user')) {
|
||||||
@@ -243,10 +247,11 @@ async function handle(ctx: KoaContext, HandlerClass, checker) {
|
|||||||
args, request, response, user, domain, UiContext,
|
args, request, response, user, domain, UiContext,
|
||||||
} = ctx.HydroContext;
|
} = ctx.HydroContext;
|
||||||
Object.assign(args, ctx.params);
|
Object.assign(args, ctx.params);
|
||||||
|
const init = Date.now();
|
||||||
const h = new HandlerClass(ctx, args, request, response, user, domain, UiContext);
|
const h = new HandlerClass(ctx, args, request, response, user, domain, UiContext);
|
||||||
ctx.handler = h;
|
ctx.handler = h;
|
||||||
|
const method = ctx.method.toLowerCase();
|
||||||
try {
|
try {
|
||||||
const method = ctx.method.toLowerCase();
|
|
||||||
const operation = (method === 'post' && ctx.request.body?.operation)
|
const operation = (method === 'post' && ctx.request.body?.operation)
|
||||||
? `_${ctx.request.body.operation}`.replace(/_([a-z])/gm, (s) => s[1].toUpperCase())
|
? `_${ctx.request.body.operation}`.replace(/_([a-z])/gm, (s) => s[1].toUpperCase())
|
||||||
: '';
|
: '';
|
||||||
@@ -307,8 +312,16 @@ async function handle(ctx: KoaContext, HandlerClass, checker) {
|
|||||||
await h.onerror(e);
|
await h.onerror(e);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
h.response.code = 500;
|
h.response.code = 500;
|
||||||
|
h.response.type = 'text/plain';
|
||||||
h.response.body = `${err.message}\n${err.stack}`;
|
h.response.body = `${err.message}\n${err.stack}`;
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
const finish = Date.now();
|
||||||
|
if (finish - init > 5000) {
|
||||||
|
const id = await OplogModel.log(h, 'slow_request', { method, processtime: finish - init });
|
||||||
|
logger.warn(`Slow handler: ID=${id}, `, method, ctx.path, `${finish - init}ms`);
|
||||||
|
}
|
||||||
|
// TODO metrics: calc avg response time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,12 +545,15 @@ export async function apply(pluginContext: Context) {
|
|||||||
if (process.env.DEV) {
|
if (process.env.DEV) {
|
||||||
app.use(async (ctx: Koa.Context, next: Function) => {
|
app.use(async (ctx: Koa.Context, next: Function) => {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
await next();
|
try {
|
||||||
const endTime = Date.now();
|
await next();
|
||||||
if (ctx.nolog || ctx.response.headers.nolog) return;
|
} finally {
|
||||||
ctx._remoteAddress = ctx.request.ip;
|
const endTime = Date.now();
|
||||||
logger.debug(`${ctx.request.method} /${ctx.domainId || 'system'}${ctx.request.path} \
|
if (!(ctx.nolog || ctx.response.headers.nolog)) {
|
||||||
|
logger.debug(`${ctx.request.method} /${ctx.domainId || 'system'}${ctx.request.path} \
|
||||||
${ctx.response.status} ${endTime - startTime}ms ${ctx.response.length}`);
|
${ctx.response.status} ${endTime - startTime}ms ${ctx.response.length}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const uploadDir = join(tmpdir(), 'hydro', 'upload', process.env.NODE_APP_INSTANCE || '0');
|
const uploadDir = join(tmpdir(), 'hydro', 'upload', process.env.NODE_APP_INSTANCE || '0');
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import esbuild from 'esbuild';
|
import esbuild from 'esbuild';
|
||||||
import {
|
import {
|
||||||
Context, fs, Handler, Logger, NotFoundError, param, SettingModel, sha1,
|
Context, fs, Handler, Logger, NotFoundError, param, SettingModel, sha1,
|
||||||
size, SystemModel, Types, UiContextBase,
|
size, Types, UiContextBase,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { tmpdir } from 'os';
|
import { tmpdir } from 'os';
|
||||||
@@ -17,15 +17,10 @@ declare module 'hydrooj' {
|
|||||||
'ui-default.nav_logo_dark': string;
|
'ui-default.nav_logo_dark': string;
|
||||||
}
|
}
|
||||||
interface UiContextBase {
|
interface UiContextBase {
|
||||||
nav_logo_dark?: string;
|
|
||||||
constantVersion?: string;
|
constantVersion?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLogo() {
|
|
||||||
UiContextBase.nav_logo_dark = SystemModel.get('ui-default.nav_logo_dark');
|
|
||||||
}
|
|
||||||
|
|
||||||
const vfs: Record<string, string> = {};
|
const vfs: Record<string, string> = {};
|
||||||
const hashes: Record<string, string> = {};
|
const hashes: Record<string, string> = {};
|
||||||
const logger = new Logger('ui');
|
const logger = new Logger('ui');
|
||||||
@@ -140,13 +135,11 @@ export async function apply(ctx: Context) {
|
|||||||
ctx.Route('constant', '/constant/:version', UiConstantsHandler);
|
ctx.Route('constant', '/constant/:version', UiConstantsHandler);
|
||||||
ctx.Route('constant', '/lazy/:version/:name', UiConstantsHandler);
|
ctx.Route('constant', '/lazy/:version/:name', UiConstantsHandler);
|
||||||
ctx.Route('constant', '/resource/:version/:name', UiConstantsHandler);
|
ctx.Route('constant', '/resource/:version/:name', UiConstantsHandler);
|
||||||
ctx.on('app/started', updateLogo);
|
|
||||||
ctx.on('app/started', buildUI);
|
ctx.on('app/started', buildUI);
|
||||||
const debouncedBuildUI = debounce(buildUI, 2000, { trailing: true });
|
const debouncedBuildUI = debounce(buildUI, 2000, { trailing: true });
|
||||||
const triggerHotUpdate = (path?: string) => {
|
const triggerHotUpdate = (path?: string) => {
|
||||||
if (path && !path.includes('/ui-default/') && !path.includes('/public/')) return;
|
if (path && !path.includes('/ui-default/') && !path.includes('/public/')) return;
|
||||||
debouncedBuildUI();
|
debouncedBuildUI();
|
||||||
updateLogo();
|
|
||||||
};
|
};
|
||||||
ctx.on('system/setting', () => triggerHotUpdate());
|
ctx.on('system/setting', () => triggerHotUpdate());
|
||||||
ctx.on('app/watch/change', triggerHotUpdate);
|
ctx.on('app/watch/change', triggerHotUpdate);
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ const render = (name: string, state: any) => new Promise<string>((resolve, rejec
|
|||||||
return state._(text.message).format(...text.params || []) + ((process.env.DEV && text.stack) ? `\n${text.stack}` : '');
|
return state._(text.message).format(...text.params || []) + ((process.env.DEV && text.stack) ? `\n${text.stack}` : '');
|
||||||
}).join('\n'),
|
}).join('\n'),
|
||||||
datetimeSpan: (arg0, arg1, arg2) => misc.datetimeSpan(arg0, arg1, arg2, state.handler.user?.timeZone),
|
datetimeSpan: (arg0, arg1, arg2) => misc.datetimeSpan(arg0, arg1, arg2, state.handler.user?.timeZone),
|
||||||
|
ctx: state.handler?.ctx,
|
||||||
perm: PERM,
|
perm: PERM,
|
||||||
PRIV,
|
PRIV,
|
||||||
STATUS,
|
STATUS,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="row"><div class="columns clearfix">
|
<div class="row"><div class="columns clearfix">
|
||||||
<div class="float-left">
|
<div class="float-left">
|
||||||
<a class="header--mobile__domain" href="/" target="_self">
|
<a class="header--mobile__domain" href="/" target="_self">
|
||||||
<img src="{{ UiContext.domain.nav_logo_dark or UiContext.nav_logo_dark }}">
|
<img src="{{ ctx.setting.get('ui-default.nav_logo_dark') }}">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<div class="row"><div class="columns clearfix">
|
<div class="row"><div class="columns clearfix">
|
||||||
<ol class="nav__list nav__list--main clearfix">
|
<ol class="nav__list nav__list--main clearfix">
|
||||||
<li class="nav__list-item">
|
<li class="nav__list-item">
|
||||||
<a href="/"><img class="nav__logo" src="{{ UiContext.domain.nav_logo_dark or UiContext.nav_logo_dark }}"></a>
|
<a href="/"><img class="nav__logo" src="{{ ctx.setting.get('ui-default.nav_logo_dark') }}"></a>
|
||||||
</li>
|
</li>
|
||||||
{%- for item in overrideNav|default(ui.getNodes('Nav')) -%}{% if item.checker(handler) %}
|
{%- for item in overrideNav|default(ui.getNodes('Nav')) -%}{% if item.checker(handler) %}
|
||||||
{{ nav_item(item) }}
|
{{ nav_item(item) }}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable logical-assignment-operators */
|
|
||||||
const map = {};
|
const map = {};
|
||||||
require('source-map-support').install({
|
require('source-map-support').install({
|
||||||
handleUncaughtExceptions: false,
|
handleUncaughtExceptions: false,
|
||||||
@@ -18,8 +17,7 @@ const vm = require('vm');
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const esbuild = require('esbuild');
|
const esbuild = require('esbuild');
|
||||||
|
|
||||||
// Node14 doesn't support ||= syntax
|
process.env.NODE_APP_INSTANCE ||= '0';
|
||||||
process.env.NODE_APP_INSTANCE = process.env.NODE_APP_INSTANCE || '0';
|
|
||||||
const major = +process.version.split('.')[0].split('v')[1];
|
const major = +process.version.split('.')[0].split('v')[1];
|
||||||
const minor = +process.version.split('.')[1];
|
const minor = +process.version.split('.')[1];
|
||||||
|
|
||||||
@@ -54,11 +52,21 @@ require.extensions['.js'] = function loader(module, filename) {
|
|||||||
const content = fs.readFileSync(filename, 'utf-8');
|
const content = fs.readFileSync(filename, 'utf-8');
|
||||||
return module._compile(content, filename);
|
return module._compile(content, filename);
|
||||||
} catch (e) { // ESM
|
} catch (e) { // ESM
|
||||||
return module._compile(transform(filename), filename);
|
try {
|
||||||
|
return module._compile(transform(filename), filename);
|
||||||
|
} catch (err) {
|
||||||
|
err.stack = new Error().stack;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
require.extensions['.ts'] = require.extensions['.tsx'] = function loader(module, filename) {
|
require.extensions['.ts'] = require.extensions['.tsx'] = function loader(module, filename) {
|
||||||
return module._compile(transform(filename), filename);
|
try {
|
||||||
|
return module._compile(transform(filename), filename);
|
||||||
|
} catch (e) {
|
||||||
|
e.stack = new Error().stack;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
require.extensions['.jsc'] = function loader(module, filename) {
|
require.extensions['.jsc'] = function loader(module, filename) {
|
||||||
const buf = fs.readFileSync(filename);
|
const buf = fs.readFileSync(filename);
|
||||||
|
|||||||
Reference in new issue
Block a user