mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
core: schema: hide secret value
This commit is contained in:
4 files changed
+85
-22
No files matched your search
@@ -184,23 +184,53 @@ class SystemConfigHandler extends SystemHandler {
|
||||
async get() {
|
||||
this.response.template = 'manage_config.html';
|
||||
let value = this.ctx.config.configSource;
|
||||
|
||||
const processNode = (node: any, schema: Schema<any, any>, parent?: any, accessKey?: string) => {
|
||||
if (['union', 'intersect'].includes(schema.type)) {
|
||||
for (const item of schema.list) processNode(node, item, parent, accessKey);
|
||||
}
|
||||
if (parent && (schema.meta.secret === true || schema.meta.role === 'secret')) {
|
||||
if (schema.type === 'string') parent[accessKey] = '[hidden]';
|
||||
// TODO support more types
|
||||
}
|
||||
if (schema.type === 'object') {
|
||||
for (const key in schema.dict) processNode(node[key], schema.dict[key], node, key);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
value = yaml.dump(Schema.intersect(this.ctx.config.settings)(yaml.load(value)));
|
||||
value = Schema.intersect(this.ctx.config.settings)(yaml.load(value));
|
||||
for (const schema of this.ctx.config.settings) processNode(value, schema);
|
||||
} catch (e) { }
|
||||
this.response.body = {
|
||||
schema: Schema.intersect(this.ctx.config.settings).toJSON(),
|
||||
value,
|
||||
value: yaml.dump(value),
|
||||
};
|
||||
}
|
||||
|
||||
@requireSudo
|
||||
@param('value', Types.String)
|
||||
async post({ }, value: string) {
|
||||
const oldConfig = yaml.load(this.ctx.config.configSource);
|
||||
let config;
|
||||
const processNode = (node: any, old: any, schema: Schema<any, any>, parent?: any, accessKey?: string) => {
|
||||
if (['union', 'intersect'].includes(schema.type)) {
|
||||
for (const item of schema.list) processNode(node, old, item, parent, accessKey);
|
||||
}
|
||||
if (parent && (schema.meta.secret === true || schema.meta.role === 'secret')) {
|
||||
if (node === '[hidden]') parent[accessKey] = old[accessKey];
|
||||
// TODO support more types
|
||||
}
|
||||
if (schema.type === 'object') {
|
||||
for (const key in schema.dict) processNode(node[key] || {}, old[key] || {}, schema.dict[key], node, key);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
config = yaml.load(value);
|
||||
for (const schema of this.ctx.config.settings) processNode(config, oldConfig, schema, null, '');
|
||||
} catch (e) {
|
||||
throw new ValidationError('value');
|
||||
throw new ValidationError('value', '', e.message);
|
||||
}
|
||||
await this.ctx.config.saveConfig(config);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,8 @@ export const Setting = (
|
||||
declare global {
|
||||
namespace Schemastery {
|
||||
interface Meta<T> { // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
family: string;
|
||||
family?: string;
|
||||
secret?: boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export class ConfigService extends Service {
|
||||
|
||||
applyDelta(source: any, key: string, value: any) {
|
||||
const path = key.split('.');
|
||||
if (path.filter((i) => ['__proto__', 'prototype', 'constructor'].includes(i)).length) return false;
|
||||
if (path.some((i) => ['__proto__', 'prototype', 'constructor'].includes(i))) return false;
|
||||
const t = path.pop();
|
||||
const root = JSON.parse(JSON.stringify(source));
|
||||
let cursor = root;
|
||||
@@ -96,11 +96,12 @@ export class ConfigService extends Service {
|
||||
}
|
||||
const that = this;
|
||||
const getAccess = (path: (string | symbol)[]) => {
|
||||
if (path.some((p) => ['__proto__', 'prototype', 'constructor'].includes(String(p)))) throw new Error('Invalid path');
|
||||
let currentValue = curValue;
|
||||
for (const p of path) {
|
||||
currentValue = currentValue[p];
|
||||
}
|
||||
if (typeof currentValue !== 'object') return curValue;
|
||||
if (typeof currentValue !== 'object' || !currentValue) return currentValue;
|
||||
return new Proxy(currentValue, {
|
||||
get(self, key: string) {
|
||||
return getAccess(path.concat(key));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'schemastery-react/lib/schemastery-react.css';
|
||||
|
||||
import { diffLines } from 'diff';
|
||||
import yaml from 'js-yaml';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
@@ -34,6 +35,10 @@ const page = new NamedPage('manage_config', async () => {
|
||||
},
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
monaco.editor.remeasureFonts();
|
||||
}, 1000);
|
||||
|
||||
const initialConfigString = UiContext.config;
|
||||
const schema = new Schema(UiContext.schema);
|
||||
const initial = yaml.load(UiContext.config);
|
||||
@@ -42,6 +47,7 @@ const page = new NamedPage('manage_config', async () => {
|
||||
const editorRef = React.useRef<HTMLDivElement>(null);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [editor, setEditor] = React.useState<any>(null);
|
||||
const [model, setModel] = React.useState<any>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
const listener = () => {
|
||||
@@ -51,10 +57,27 @@ const page = new NamedPage('manage_config', async () => {
|
||||
window.addEventListener('resize', listener);
|
||||
return () => window.removeEventListener('resize', listener);
|
||||
}, [editor]);
|
||||
React.useEffect(() => {
|
||||
if (!editor) return;
|
||||
const disposable = editor.onDidChangeModelContent(() => {
|
||||
const val = editor.getValue({ lineEnding: '\n', preserveBOM: false });
|
||||
try {
|
||||
const loaded = yaml.load(val);
|
||||
schema(loaded);
|
||||
setValue(val);
|
||||
setError('');
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
}
|
||||
});
|
||||
return () => disposable.dispose(); // eslint-disable-line
|
||||
}, [editor, setValue, setError]);
|
||||
React.useEffect(() => {
|
||||
if (!editorRef.current || loading) return;
|
||||
setLoading(true);
|
||||
// eslint-disable-next-line
|
||||
const model = monaco.editor.createModel(config, 'yaml', monaco.Uri.parse('hydro://system/setting.yaml'));
|
||||
setModel(model);
|
||||
const e = monaco.editor.create(editorRef.current, {
|
||||
theme: 'vs-light',
|
||||
lineNumbers: 'off',
|
||||
@@ -72,23 +95,29 @@ const page = new NamedPage('manage_config', async () => {
|
||||
},
|
||||
});
|
||||
registerAction(e, model, editorRef.current);
|
||||
e.onDidChangeModelContent(() => {
|
||||
const val = e.getValue({ lineEnding: '\n', preserveBOM: false });
|
||||
try {
|
||||
const loaded = yaml.load(val);
|
||||
schema(loaded);
|
||||
setValue(val);
|
||||
setError('');
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
}
|
||||
});
|
||||
setEditor(e);
|
||||
}, [editorRef.current]);
|
||||
React.useEffect(() => {
|
||||
if (!editor) return;
|
||||
const current = editor.getValue({ lineEnding: '\n', preserveBOM: false });
|
||||
if (current !== config) editor.setValue(config);
|
||||
const diff = diffLines(current, config);
|
||||
const ops = [];
|
||||
let cursor = 1;
|
||||
for (const line of diff) {
|
||||
if (line.added) {
|
||||
let range = model.getFullModelRange();
|
||||
range = range.setStartPosition(cursor, 0);
|
||||
range = range.setEndPosition(cursor, 0);
|
||||
ops.push({ range, text: line.value });
|
||||
} else if (line.removed) {
|
||||
let range = model.getFullModelRange();
|
||||
range = range.setStartPosition(cursor, 0);
|
||||
cursor += line.count;
|
||||
range = range.setEndPosition(cursor, 0);
|
||||
ops.push({ range, text: '' });
|
||||
} else cursor += line.count;
|
||||
}
|
||||
model.pushEditOperations([], ops, undefined);
|
||||
}, [editor, config]);
|
||||
return (
|
||||
<div ref={editorRef} style={{
|
||||
@@ -117,19 +146,21 @@ const page = new NamedPage('manage_config', async () => {
|
||||
|
||||
return (<>
|
||||
<div className="row">
|
||||
<Form className="medium-7 columns" schema={schema} initial={initial} value={value} onChange={updateFromForm} />
|
||||
<div className="medium-4 columns">
|
||||
<div className="medium-5 columns">
|
||||
<MonacoContainer config={stringConfig} setValue={updateFromMonaco} setError={setInfo} />
|
||||
<pre className="help-text">{info}</pre>
|
||||
</div>
|
||||
<div className="medium-7 columns">
|
||||
<Form schema={schema} initial={initial} value={value} onChange={updateFromForm} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="row" style={{ marginTop: '20px' }}>
|
||||
<div className="medium-10 columns">
|
||||
<button onClick={() => {
|
||||
request.post('', { value: stringConfig }).then(() => {
|
||||
Notification.success('保存成功');
|
||||
Notification.success(i18n('Changes saved successfully'));
|
||||
}).catch((e) => {
|
||||
Notification.error('保存失败:', e.message);
|
||||
Notification.error(i18n('Failed to save changes:'), e.message);
|
||||
});
|
||||
}} className="rounded primary button">{i18n('Save All Changes')}</button>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user