Add auto resolving fields
This commit is contained in:
parent
68295c148d
commit
0bfdbce908
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hibas123/realtimedb",
|
"name": "@hibas123/realtimedb",
|
||||||
"version": "2.0.0-beta.18",
|
"version": "2.0.0-beta.19",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@ -154,6 +154,7 @@ export abstract class Query {
|
|||||||
} else if (this.permission === "write" && !perm.write) {
|
} else if (this.permission === "write" && !perm.write) {
|
||||||
throw new QueryError("No permission!");
|
throw new QueryError("No permission!");
|
||||||
}
|
}
|
||||||
|
this.query.path = perm.path;
|
||||||
return this._runner.call(
|
return this._runner.call(
|
||||||
this,
|
this,
|
||||||
collection,
|
collection,
|
||||||
@ -170,10 +171,12 @@ export abstract class Query {
|
|||||||
this.query.path,
|
this.query.path,
|
||||||
this.session
|
this.session
|
||||||
);
|
);
|
||||||
if (this.permission === "read" && !perm.read) {
|
if (!perm.read) {
|
||||||
throw new QueryError("No permission!");
|
throw new QueryError("No permission!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.query.path = perm.path;
|
||||||
|
|
||||||
const receivedChanges = (changes: Change[]) => {
|
const receivedChanges = (changes: Change[]) => {
|
||||||
let res = changes
|
let res = changes
|
||||||
.filter(change => this.checkChange(change))
|
.filter(change => this.checkChange(change))
|
||||||
|
@ -61,19 +61,21 @@ export class Rules {
|
|||||||
hasPermission(
|
hasPermission(
|
||||||
path: string[],
|
path: string[],
|
||||||
session: Session
|
session: Session
|
||||||
): { read: boolean; write: boolean } {
|
): { read: boolean; write: boolean; path: string[] } {
|
||||||
if (session.root)
|
if (session.root)
|
||||||
return {
|
return {
|
||||||
read: true,
|
read: true,
|
||||||
write: true
|
write: true,
|
||||||
|
path: path
|
||||||
};
|
};
|
||||||
let read = this.rules[".read"] || false;
|
let read = this.rules[".read"] || false;
|
||||||
let write = this.rules[".write"] || false;
|
let write = this.rules[".write"] || false;
|
||||||
|
|
||||||
let rules = this.rules;
|
let rules = this.rules;
|
||||||
|
|
||||||
for (let segment of path) {
|
for (let idx in path) {
|
||||||
if (segment.startsWith("$") || segment.startsWith(".")) {
|
let segment = path[idx];
|
||||||
|
if (segment.startsWith(".")) {
|
||||||
read = false;
|
read = false;
|
||||||
write = false;
|
write = false;
|
||||||
Logging.log("Invalid query path (started with '$' or '.'):", path);
|
Logging.log("Invalid query path (started with '$' or '.'):", path);
|
||||||
@ -85,6 +87,10 @@ export class Rules {
|
|||||||
.find(e => {
|
.find(e => {
|
||||||
switch (e) {
|
switch (e) {
|
||||||
case "$uid":
|
case "$uid":
|
||||||
|
if (segment === "$uid") {
|
||||||
|
path[idx] = session.uid;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (segment === session.uid) return true;
|
if (segment === session.uid) return true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -108,7 +114,8 @@ export class Rules {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
read: read as boolean,
|
read: read as boolean,
|
||||||
write: write as boolean
|
write: write as boolean,
|
||||||
|
path
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,16 +5,26 @@ interface IFormConfigField {
|
|||||||
type: "text" | "number" | "boolean" | "textarea";
|
type: "text" | "number" | "boolean" | "textarea";
|
||||||
label: string;
|
label: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type IFormConfig = { [name: string]: IFormConfigField }
|
type IFormConfig = { [name: string]: IFormConfigField };
|
||||||
|
|
||||||
export default function getForm(url: string, title: string, fieldConfig: IFormConfig): (ctx: Context) => void {
|
export default function getForm(
|
||||||
let fields = Object.keys(fieldConfig).map(name => ({ name, ...fieldConfig[name] }))
|
url: string,
|
||||||
|
title: string,
|
||||||
|
fieldConfig: IFormConfig
|
||||||
|
): (ctx: Context) => void {
|
||||||
|
let fields = Object.keys(fieldConfig).map(name => ({
|
||||||
|
name,
|
||||||
|
...fieldConfig[name],
|
||||||
|
disabled: fieldConfig.disabled ? "disabled" : ""
|
||||||
|
}));
|
||||||
|
|
||||||
return ctx => ctx.body = getTemplate("forms")({
|
return ctx =>
|
||||||
|
(ctx.body = getTemplate("forms")({
|
||||||
url,
|
url,
|
||||||
title,
|
title,
|
||||||
fields
|
fields
|
||||||
});
|
}));
|
||||||
}
|
}
|
@ -2,7 +2,11 @@ import * as Router from "koa-router";
|
|||||||
import Settings from "../../settings";
|
import Settings from "../../settings";
|
||||||
import getForm from "../helper/form";
|
import getForm from "../helper/form";
|
||||||
import getTable from "../helper/table";
|
import getTable from "../helper/table";
|
||||||
import { BadRequestError, NoPermissionError } from "../helper/errors";
|
import {
|
||||||
|
BadRequestError,
|
||||||
|
NoPermissionError,
|
||||||
|
NotFoundError
|
||||||
|
} from "../helper/errors";
|
||||||
import { DatabaseManager } from "../../database/database";
|
import { DatabaseManager } from "../../database/database";
|
||||||
import { MP } from "../../database/query";
|
import { MP } from "../../database/query";
|
||||||
import config from "../../config";
|
import config from "../../config";
|
||||||
@ -13,10 +17,9 @@ const AdminRoute = new Router();
|
|||||||
|
|
||||||
AdminRoute.use(async (ctx, next) => {
|
AdminRoute.use(async (ctx, next) => {
|
||||||
const { key } = ctx.query;
|
const { key } = ctx.query;
|
||||||
if (key !== config.admin)
|
if (key !== config.admin) throw new NoPermissionError("No permission!");
|
||||||
throw new NoPermissionError("No permission!");
|
|
||||||
return next();
|
return next();
|
||||||
})
|
});
|
||||||
|
|
||||||
AdminRoute.get("/", async ctx => {
|
AdminRoute.get("/", async ctx => {
|
||||||
//TODO: Main Interface
|
//TODO: Main Interface
|
||||||
@ -33,26 +36,24 @@ AdminRoute.get("/settings", async ctx => {
|
|||||||
let res = [["key", "value"]];
|
let res = [["key", "value"]];
|
||||||
stream.on("data", ({ key, value }) => {
|
stream.on("data", ({ key, value }) => {
|
||||||
res.push([key, value]);
|
res.push([key, value]);
|
||||||
})
|
});
|
||||||
|
|
||||||
stream.on("error", no);
|
stream.on("error", no);
|
||||||
stream.on("end", () => yes(res))
|
stream.on("end", () => yes(res));
|
||||||
})
|
});
|
||||||
|
|
||||||
if (ctx.query.view) {
|
if (ctx.query.view) {
|
||||||
return getTable("Settings", res, ctx);
|
return getTable("Settings", res, ctx);
|
||||||
} else {
|
} else {
|
||||||
ctx.body = res;
|
ctx.body = res;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
AdminRoute.get("/data", async ctx => {
|
AdminRoute.get("/data", async ctx => {
|
||||||
const { database } = ctx.query;
|
const { database } = ctx.query;
|
||||||
let db = DatabaseManager.getDatabase(database);
|
let db = DatabaseManager.getDatabase(database);
|
||||||
if (!db)
|
if (!db) throw new BadRequestError("Database not found");
|
||||||
throw new BadRequestError("Database not found");
|
|
||||||
let res = await new Promise<string[][]>((yes, no) => {
|
let res = await new Promise<string[][]>((yes, no) => {
|
||||||
|
|
||||||
const stream = db.data.createReadStream({
|
const stream = db.data.createReadStream({
|
||||||
keys: true,
|
keys: true,
|
||||||
values: true,
|
values: true,
|
||||||
@ -61,28 +62,37 @@ AdminRoute.get("/data", async ctx => {
|
|||||||
limit: 1000
|
limit: 1000
|
||||||
});
|
});
|
||||||
let res = [["key", "value"]];
|
let res = [["key", "value"]];
|
||||||
stream.on("data", ({ key, value }: { key: string, value: Buffer }) => {
|
stream.on("data", ({ key, value }: { key: string; value: Buffer }) => {
|
||||||
res.push([key, key.split("/").length > 2 ? value.toString() : JSON.stringify(MP.decode(value))]);
|
res.push([
|
||||||
})
|
key,
|
||||||
|
key.split("/").length > 2
|
||||||
|
? value.toString()
|
||||||
|
: JSON.stringify(MP.decode(value))
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
stream.on("error", no);
|
stream.on("error", no);
|
||||||
stream.on("end", () => yes(res))
|
stream.on("end", () => yes(res));
|
||||||
})
|
});
|
||||||
|
|
||||||
if (ctx.query.view) {
|
if (ctx.query.view) {
|
||||||
return getTable("Data from " + database, res, ctx);
|
return getTable("Data from " + database, res, ctx);
|
||||||
} else {
|
} else {
|
||||||
ctx.body = res;
|
ctx.body = res;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
AdminRoute
|
AdminRoute.get("/database", ctx => {
|
||||||
.get("/database", ctx => {
|
|
||||||
const isFull = ctx.query.full === "true" || ctx.query.full === "1";
|
const isFull = ctx.query.full === "true" || ctx.query.full === "1";
|
||||||
let res;
|
let res;
|
||||||
if (isFull) {
|
if (isFull) {
|
||||||
//TODO: Better than JSON.parse / JSON.stringify
|
//TODO: Better than JSON.parse / JSON.stringify
|
||||||
res = Array.from(DatabaseManager.databases.entries()).map(([name, config]) => ({ name, ...(JSON.parse(JSON.stringify(config))) }));
|
res = Array.from(DatabaseManager.databases.entries()).map(
|
||||||
|
([name, config]) => ({
|
||||||
|
name,
|
||||||
|
...JSON.parse(JSON.stringify(config))
|
||||||
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
res = Array.from(DatabaseManager.databases.keys());
|
res = Array.from(DatabaseManager.databases.keys());
|
||||||
}
|
}
|
||||||
@ -92,42 +102,31 @@ AdminRoute
|
|||||||
} else {
|
} else {
|
||||||
ctx.body = res;
|
ctx.body = res;
|
||||||
}
|
}
|
||||||
})
|
}).post("/database", async ctx => {
|
||||||
.post("/database", async ctx => {
|
|
||||||
const { name, rules, publickey, accesskey, rootkey } = ctx.request.body;
|
const { name, rules, publickey, accesskey, rootkey } = ctx.request.body;
|
||||||
|
|
||||||
if (!name)
|
if (!name) throw new BadRequestError("Name must be set!");
|
||||||
throw new BadRequestError("Name must be set!");
|
|
||||||
|
|
||||||
let db = DatabaseManager.getDatabase(name);
|
let db = DatabaseManager.getDatabase(name);
|
||||||
if (!db)
|
if (!db) db = await DatabaseManager.addDatabase(name);
|
||||||
db = await DatabaseManager.addDatabase(name);
|
|
||||||
|
|
||||||
if (publickey)
|
if (publickey) await db.setPublicKey(publickey);
|
||||||
await db.setPublicKey(publickey);
|
|
||||||
|
|
||||||
if (rules)
|
if (rules) await db.setRules(rules);
|
||||||
await db.setRules(rules);
|
|
||||||
|
|
||||||
if (accesskey)
|
if (accesskey) await db.setAccessKey(accesskey);
|
||||||
await db.setAccessKey(accesskey);
|
|
||||||
|
|
||||||
|
|
||||||
if (rootkey)
|
|
||||||
await db.setRootKey(rootkey);
|
|
||||||
|
|
||||||
|
if (rootkey) await db.setRootKey(rootkey);
|
||||||
|
|
||||||
ctx.body = "Success";
|
ctx.body = "Success";
|
||||||
})
|
});
|
||||||
|
|
||||||
AdminRoute.get("/collections", async ctx => {
|
AdminRoute.get("/collections", async ctx => {
|
||||||
const { database } = ctx.query;
|
const { database } = ctx.query;
|
||||||
let db = DatabaseManager.getDatabase(database);
|
let db = DatabaseManager.getDatabase(database);
|
||||||
if (!db)
|
if (!db) throw new BadRequestError("Database not found");
|
||||||
throw new BadRequestError("Database not found");
|
|
||||||
|
|
||||||
let res = await new Promise<string[]>((yes, no) => {
|
let res = await new Promise<string[]>((yes, no) => {
|
||||||
|
|
||||||
const stream = db.collections.createKeyStream({
|
const stream = db.collections.createKeyStream({
|
||||||
keyAsBuffer: false,
|
keyAsBuffer: false,
|
||||||
limit: 1000
|
limit: 1000
|
||||||
@ -135,24 +134,23 @@ AdminRoute.get("/collections", async ctx => {
|
|||||||
let res = [];
|
let res = [];
|
||||||
stream.on("data", (key: string) => {
|
stream.on("data", (key: string) => {
|
||||||
res.push(key);
|
res.push(key);
|
||||||
})
|
});
|
||||||
|
|
||||||
stream.on("error", no);
|
stream.on("error", no);
|
||||||
stream.on("end", () => yes(res))
|
stream.on("end", () => yes(res));
|
||||||
})
|
});
|
||||||
|
|
||||||
if (ctx.query.view) {
|
if (ctx.query.view) {
|
||||||
return getTable("Databases", res, ctx);
|
return getTable("Databases", res, ctx);
|
||||||
} else {
|
} else {
|
||||||
ctx.body = res;
|
ctx.body = res;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
AdminRoute.get("/collections/cleanup", async ctx => {
|
AdminRoute.get("/collections/cleanup", async ctx => {
|
||||||
const { database } = ctx.query;
|
const { database } = ctx.query;
|
||||||
let db = DatabaseManager.getDatabase(database);
|
let db = DatabaseManager.getDatabase(database);
|
||||||
if (!db)
|
if (!db) throw new BadRequestError("Database not found");
|
||||||
throw new BadRequestError("Database not found");
|
|
||||||
|
|
||||||
let deleted = await db.runCleanup();
|
let deleted = await db.runCleanup();
|
||||||
if (ctx.query.view) {
|
if (ctx.query.view) {
|
||||||
@ -160,14 +158,55 @@ AdminRoute.get("/collections/cleanup", async ctx => {
|
|||||||
} else {
|
} else {
|
||||||
ctx.body = deleted;
|
ctx.body = deleted;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
AdminRoute.get("/database/new", getForm("/v1/admin/database", "New/Change Database", {
|
AdminRoute.get(
|
||||||
name: { label: "Name", type: "text", },
|
"/database/new",
|
||||||
|
getForm("/v1/admin/database", "New Database", {
|
||||||
|
name: { label: "Name", type: "text" },
|
||||||
accesskey: { label: "Access Key", type: "text" },
|
accesskey: { label: "Access Key", type: "text" },
|
||||||
rootkey: { label: "Root access key", type: "text" },
|
rootkey: { label: "Root access key", type: "text" },
|
||||||
rules: { label: "Rules", type: "textarea", value: `{\n ".write": true, \n ".read": true \n}` },
|
rules: {
|
||||||
|
label: "Rules",
|
||||||
|
type: "textarea",
|
||||||
|
value: `{\n ".write": true, \n ".read": true \n}`
|
||||||
|
},
|
||||||
publickey: { label: "Public Key", type: "textarea" }
|
publickey: { label: "Public Key", type: "textarea" }
|
||||||
}))
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
AdminRoute.get("/database/update", async ctx => {
|
||||||
|
const { database } = ctx.query;
|
||||||
|
let db = DatabaseManager.getDatabase(database);
|
||||||
|
if (!db) throw new NotFoundError("Database not found!");
|
||||||
|
getForm("/v1/admin/database", "Change Database", {
|
||||||
|
name: {
|
||||||
|
label: "Name",
|
||||||
|
type: "text",
|
||||||
|
value: db.name,
|
||||||
|
disabled: true
|
||||||
|
},
|
||||||
|
accesskey: {
|
||||||
|
label: "Access Key",
|
||||||
|
type: "text",
|
||||||
|
value: db.accesskey
|
||||||
|
},
|
||||||
|
rootkey: {
|
||||||
|
label: "Root access key",
|
||||||
|
type: "text",
|
||||||
|
value: db.rootkey
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
label: "Rules",
|
||||||
|
type: "textarea",
|
||||||
|
value: db.rules.toJSON()
|
||||||
|
},
|
||||||
|
publickey: {
|
||||||
|
label: "Public Key",
|
||||||
|
type: "textarea",
|
||||||
|
value: db.publickey
|
||||||
|
}
|
||||||
|
})(ctx);
|
||||||
|
});
|
||||||
|
|
||||||
export default AdminRoute;
|
export default AdminRoute;
|
@ -1,13 +1,18 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
<head>
|
<meta charset="UTF-8" />
|
||||||
<meta charset="UTF-8">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<title>Admin Interface</title>
|
<title>Admin Interface</title>
|
||||||
<link rel="stylesheet" href="https://unpkg.com/@hibas123/theme/out/base.css">
|
<link
|
||||||
<link rel="stylesheet" href="https://unpkg.com/@hibas123/theme/out/light.css">
|
rel="stylesheet"
|
||||||
|
href="https://unpkg.com/@hibas123/theme/out/base.css"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://unpkg.com/@hibas123/theme/out/light.css"
|
||||||
|
/>
|
||||||
|
|
||||||
<script src="https://unpkg.com/handlebars/dist/handlebars.min.js"></script>
|
<script src="https://unpkg.com/handlebars/dist/handlebars.min.js"></script>
|
||||||
|
|
||||||
@ -16,7 +21,7 @@
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
background-color: lightgreen;
|
background-color: lightgreen;
|
||||||
border: 1px solid lime;
|
border: 1px solid lime;
|
||||||
border-radius: .5rem;
|
border-radius: 0.5rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
@ -37,29 +42,30 @@
|
|||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div style="border-right: 1px solid darkgrey; padding: 1rem;">
|
<div style="border-right: 1px solid darkgrey; padding: 1rem;">
|
||||||
<h2>Navigation: </h2>
|
<h2>Navigation:</h2>
|
||||||
<ul class="list list-clickable">
|
<ul class="list list-clickable">
|
||||||
<li onclick="loadView('settings');">Settings</li>
|
<li onclick="loadView('settings');">Settings</li>
|
||||||
<li onclick="loadView('database', {full:true});">Databases</li>
|
<li onclick="loadView('database', {full:true});">Databases</li>
|
||||||
<li onclick="loadView('database/new');">New Database</li>
|
<li onclick="loadView('database/new');">New Database</li>
|
||||||
</ul>
|
</ul>
|
||||||
Databases:
|
Databases:
|
||||||
<div id="dbs" class="list list-clickable" style="margin: 1rem;"></div>
|
<div
|
||||||
|
id="dbs"
|
||||||
|
class="list list-clickable"
|
||||||
|
style="margin: 1rem;"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div style="position:relative;">
|
<div style="position:relative;">
|
||||||
<iframe id="content"></iframe>
|
<iframe id="content"></iframe>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template>
|
<template> </template>
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const key = new URL(window.location.href).searchParams.get("key");
|
const key = new URL(window.location.href).searchParams.get("key");
|
||||||
@ -73,8 +79,7 @@
|
|||||||
url.searchParams.set(key, params[key]);
|
url.searchParams.set(key, params[key]);
|
||||||
|
|
||||||
url.searchParams.set("key", key);
|
url.searchParams.set("key", key);
|
||||||
if (view)
|
if (view) url.searchParams.set("view", "true");
|
||||||
url.searchParams.set("view", "true");
|
|
||||||
|
|
||||||
return url.href;
|
return url.href;
|
||||||
}
|
}
|
||||||
@ -83,30 +88,31 @@
|
|||||||
content.src = getUrl(name, params);
|
content.src = getUrl(name, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadView("settings")
|
loadView("settings");
|
||||||
|
|
||||||
const dbsul = document.getElementById("dbs");
|
const dbsul = document.getElementById("dbs");
|
||||||
function reloadDBs() {
|
function reloadDBs() {
|
||||||
fetch(getUrl("database", {}, false))
|
fetch(getUrl("database", {}, false))
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(databases => databases.map(database => `
|
.then(databases =>
|
||||||
|
databases.map(
|
||||||
|
database => `
|
||||||
<div class="card margin elv-4">
|
<div class="card margin elv-4">
|
||||||
<h3>${database}</h3>
|
<h3>${database}</h3>
|
||||||
<button class=btn onclick="loadView('data', {database:'${database}'})">Data</button>
|
<button class=btn onclick="loadView('data', {database:'${database}'})">Data</button>
|
||||||
<button class=btn onclick="loadView('collections', {database:'${database}'})">Collections</button>
|
<button class=btn onclick="loadView('collections', {database:'${database}'})">Collections</button>
|
||||||
<button class=btn onclick="loadView('collections/cleanup', {database:'${database}'})">Clean</button>
|
<button class=btn onclick="loadView('collections/cleanup', {database:'${database}'})">Clean</button>
|
||||||
|
<button class=btn onclick="loadView('database/update', {database:'${database}'})">Change</button>
|
||||||
</div>`
|
</div>`
|
||||||
))
|
)
|
||||||
|
)
|
||||||
.then(d => d.join("\n"))
|
.then(d => d.join("\n"))
|
||||||
.then(d => dbsul.innerHTML = d)
|
.then(d => (dbsul.innerHTML = d))
|
||||||
.catch(console.error)
|
.catch(console.error);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadDBs();
|
reloadDBs();
|
||||||
setInterval(reloadDBs, 5000);
|
setInterval(reloadDBs, 5000);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -32,19 +32,19 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label>{{label}}</label>
|
<label>{{label}}</label>
|
||||||
{{#ifCond type "===" "text"}}
|
{{#ifCond type "===" "text"}}
|
||||||
<input type="text" placeholder="{{label}}" name="{{name}}" value="{{value}}" />
|
<input type="text" placeholder="{{label}}" name="{{name}}" value="{{value}}" {{disabled}} />
|
||||||
{{/ifCond}}
|
{{/ifCond}}
|
||||||
|
|
||||||
{{#ifCond type "===" "number"}}
|
{{#ifCond type "===" "number"}}
|
||||||
<input type="number" placeholder="{{label}}" name="{{name}}" value="{{value}}" />
|
<input type="number" placeholder="{{label}}" name="{{name}}" value="{{value}}" {{disabled}} />
|
||||||
{{/ifCond}}
|
{{/ifCond}}
|
||||||
|
|
||||||
{{#ifCond type "===" "boolean"}}
|
{{#ifCond type "===" "boolean"}}
|
||||||
<input type="checkbox" name="{{name}}" checked="{{value}}" />
|
<input type="checkbox" name="{{name}}" checked="{{value}}" {{disabled}} />
|
||||||
{{/ifCond}}
|
{{/ifCond}}
|
||||||
|
|
||||||
{{#ifCond type "===" "textarea"}}
|
{{#ifCond type "===" "textarea"}}
|
||||||
<textarea class="inp" name="{{name}}" rows="20">{{value}}</textarea>
|
<textarea class="inp" name="{{name}}" rows="20" {{disabled}}>{{value}}</textarea>
|
||||||
{{/ifCond}}
|
{{/ifCond}}
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
Reference in New Issue
Block a user