Config interaction greatly improved

* Replace json view with monaco-editor with schema loaded based on url param
* Add route for unauthenticated config editing
* Auto-load subreddit config when "view" is clicked from subreddit view
This commit is contained in:
FoxxMD
2021-08-18 21:34:46 -04:00
parent 2655ae6041
commit 17440025b9
6 changed files with 133 additions and 37 deletions

11
package-lock.json generated
View File

@@ -37,6 +37,7 @@
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.21",
"lru-cache": "^6.0.0",
"monaco-editor": "^0.27.0",
"mustache": "^4.2.0",
"node-fetch": "^2.6.1",
"normalize-url": "^6.1.0",
@@ -2602,6 +2603,11 @@
"node": "*"
}
},
"node_modules/monaco-editor": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.27.0.tgz",
"integrity": "sha512-UhwP78Wb8w0ZSYoKXQNTV/0CHObp6NS3nCt51QfKE6sKyBo5PBsvuDOHoI2ooBakc6uIwByRLHVeT7+yXQe2fQ=="
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -6182,6 +6188,11 @@
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"monaco-editor": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.27.0.tgz",
"integrity": "sha512-UhwP78Wb8w0ZSYoKXQNTV/0CHObp6NS3nCt51QfKE6sKyBo5PBsvuDOHoI2ooBakc6uIwByRLHVeT7+yXQe2fQ=="
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",

View File

@@ -54,6 +54,7 @@
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.21",
"lru-cache": "^6.0.0",
"monaco-editor": "^0.27.0",
"mustache": "^4.2.0",
"node-fetch": "^2.6.1",
"normalize-url": "^6.1.0",

View File

@@ -52,6 +52,8 @@ app.use(bodyParser.urlencoded({extended: false}));
app.set('views', `${__dirname}/../assets/views`);
app.set('view engine', 'ejs');
app.use('/public', express.static(`${__dirname}/../assets/public`));
app.use('/monaco', express.static(`${__dirname}/../../../node_modules/monaco-editor/`));
app.use('/schemas', express.static(`${__dirname}/../../Schema/`));
const proxy = httpProxy.createProxyServer({
ws: true,
@@ -234,6 +236,14 @@ const webClient = async (options: OperatorConfig) => {
}
}
const ensureAuthenticatedApi = async (req: express.Request, res: express.Response, next: Function) => {
if (req.isAuthenticated()) {
next();
} else {
return res.status(401).send('You must be logged in to access this route');
}
}
app.getAsync('/login', async (req, res, next) => {
if (redirectUri === undefined) {
return res.render('error', {error: `No <b>redirectUri</b> was specified through environmental variables or program argument. This must be provided in order to use the web interface.`});
@@ -788,7 +798,13 @@ const webClient = async (options: OperatorConfig) => {
});
});
app.getAsync('/config', [ensureAuthenticated, defaultSession, instanceWithPermissions, botWithPermissions, createUserToken], async (req: express.Request, res: express.Response) => {
app.getAsync('/config', async (req: express.Request, res: express.Response) => {
res.render('config', {
title: `Configuration Editor`
});
});
app.getAsync('/config/content', [ensureAuthenticatedApi, defaultSession, instanceWithPermissions, botWithPermissions, createUserToken], async (req: express.Request, res: express.Response) => {
const {subreddit} = req.query as any;
const resp = await got.get(`${(req.instance as CMInstance).normalUrl}/config`, {
headers: {
@@ -800,13 +816,7 @@ const webClient = async (options: OperatorConfig) => {
}
}).text();
const [obj, jsonErr, yamlErr] = parseFromJsonOrYamlToObject(resp);
const bot = req.instance as CMInstance;
res.render('config', {
config: prettyPrintJson.toHtml(obj, {quoteKeys: true, indent: 2}),
operatorDisplay:bot.operators.join(', '),
title: `Configuration for ${subreddit}`
});
return res.send(resp);
});
app.getAsync('/logs/settings/update',[ensureAuthenticated], async (req: express.Request, res: express.Response) => {

View File

@@ -1,21 +0,0 @@
/* adapted from https://cdn.jsdelivr.net/npm/pretty-print-json@1.0/dist/pretty-print-json.css */
.json-key { color: brown; }
.json-string { color: olive; }
.json-number { color: navy; }
.json-boolean { color: teal; }
.json-null { color: dimgray; }
.json-mark { color: black; }
a.json-link { color: purple; transition: all 400ms; }
a.json-link:visited { color: slategray; }
a.json-link:hover { color: blueviolet; }
a.json-link:active { color: slategray; }
.dark .json-key { color: indianred; }
.dark .json-string { color: darkkhaki; }
.dark .json-number { color: deepskyblue; }
.dark .json-boolean { color: mediumseagreen; }
.dark .json-null { color: darkorange; }
.dark .json-mark { color: silver; }
.dark a.json-link { color: mediumorchid; }
.dark a.json-link:visited { color: slategray; }
.dark a.json-link:hover { color: violet; }
.dark a.json-link:active { color: silver; }

View File

@@ -9,9 +9,7 @@
<script src="https://code.iconify.design/1/1.0.4/iconify.min.js"></script>
<link rel="stylesheet" href="/public/themeToggle.css">
<link rel="stylesheet" href="/public/app.css">
<link rel="stylesheet" href="/public/json.css">
<title><%= title %></title>
<!--<title><%# `CM for /u/${botName}`%></title>-->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
@@ -23,12 +21,42 @@
<%- include('partials/title') %>
<div class="container mx-auto">
<div class="grid">
<div class="bg-white dark:bg-gray-700 dark:text-white">
<div class="p-6 md:px-10 md:py-6 space-y-3">
<div>Note: Comments have been removed</div>
<pre style="user-select: text;"><%- config %></pre>
</div>
<div class="dark:text-white mb-3 pl-2">
<span class="has-tooltip">
<span style="z-index:999; margin-top: 30px;" class='tooltip rounded shadow-lg p-3 bg-gray-100 text-black space-y-2'>
<div>Copy + paste your configuration here to get:</div>
<ul class="list-inside list-disc">
<li>
formatting (right click for menu)
</li>
<li>
JSON syntax assist (red squiggly, hover for info)
</li>
<li>
annotated properties (hover for info)
</li>
<li id="schemaTypeList"></li>
</ul>
<div>When done editing hit Ctrl+A (Command+A on macOS) to select all text, then copy + paste back into your wiki/file</div>
</span>
<span class="cursor-help">
How To Use
<span>
<svg xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4 inline-block cursor-help"
fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</span>
</span>
</span>
<div id="error" class="font-semibold"></div>
</div>
<div style="min-height: 80vh" id="editor"></div>
</div>
</div>
<%- include('partials/footer') %>
@@ -62,5 +90,72 @@
}
}
</script>
<script src="/monaco/dev/vs/loader.js"></script>
<script>
require.config({ paths: { vs: 'monaco/dev/vs' } });
const preamble = [
'// Copy + paste your configuration here to get',
'// formatting, JSON syntax, annotated properties and'
];
var searchParams = new URLSearchParams(window.location.search);
let schemaType;
if(searchParams.get('schema') === 'operator') {
schemaType = 'OperatorConfig.json';
preamble.push('// automatic validation of your OPERATOR configuration');
document.querySelector('#schemaTypeList').innerHTML = 'automatic validation of your OPERATOR configuration (yellow squiggly)';
} else {
schemaType = 'App.json';
preamble.push('// automatic validation of your SUBREDDIT configuration');
document.querySelector('#schemaTypeList').innerHTML = 'automatic validation of your SUBREDDIT configuration (yellow squiggly)'
}
const schemaUri = `${document.location.origin}/schemas/${schemaType}`;
require(['vs/editor/editor.main'], function () {
const modelUri = monaco.Uri.parse("a://b/foo.json");
fetch(schemaUri).then((res) => {
res.json().then((schemaData) => {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
allowComments: true,
trailingCommas: "ignore",
schemas: [{
uri: schemaUri,
fileMatch: [modelUri.toString()],
schema: schemaData
}]
});
if(searchParams.get('subreddit') !== null) {
fetch(`${document.location.origin}/config/content${document.location.search}`).then((resp) => {
if(!resp.ok) {
resp.text().then(data => {
document.querySelector('#error').innerHTML = `Error occurred while fetching configuration => ${data}`
});
} else {
resp.text().then(data => {
var model = monaco.editor.createModel(data, "json", modelUri);
var editor = monaco.editor.create(document.getElementById('editor'), {
model,
theme: 'vs-dark'
});
editor;
})
}
})
} else {
var model = monaco.editor.createModel(preamble.join('\r\n'), "json", modelUri);
var editor = monaco.editor.create(document.getElementById('editor'), {
model,
theme: 'vs-dark'
});
editor;
}
})
})
});
</script>
</body>
</html>

View File

@@ -310,7 +310,7 @@
<span>
<a style="display: inline"
href="<%= data.wikiHref %>"><%= data.wikiLocation %></a> | <a style="display: inline" target="_blank"
href="/config?instance=<%= instanceId %>&bot=<%= bot.botName %>&subreddit=<%= data.name %>">View</a>
href="/config?instance=<%= instanceId %>&bot=<%= bot.system.name %>&subreddit=<%= data.name %>">View</a>
</span>
</div>
</div>