mirror of
https://codeberg.org/valpackett/tiddlypwa.git
synced 2025-12-06 02:30:48 -08:00
Server: break up into more files
This commit is contained in:
parent
9bc7573415
commit
17879bca00
4 changed files with 168 additions and 168 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
// deno test --unstable --allow-env --allow-read=.
|
// deno test --unstable --allow-env --allow-read=.
|
||||||
import 'https://deno.land/std@0.192.0/dotenv/load.ts';
|
import 'https://deno.land/std@0.192.0/dotenv/load.ts';
|
||||||
import { assertEquals } from 'https://deno.land/std@0.192.0/testing/asserts.ts';
|
import { assertEquals } from 'https://deno.land/std@0.192.0/testing/asserts.ts';
|
||||||
import * as app from './tiddlypwa-server.ts';
|
import * as app from './app.ts';
|
||||||
|
|
||||||
const api = (data: any) =>
|
const api = (data: any) =>
|
||||||
app.handle(
|
app.handle(
|
||||||
|
|
@ -7,173 +7,8 @@ import { parse as argparse } from 'https://deno.land/std@0.192.0/flags/mod.ts';
|
||||||
import { serveListener } from 'https://deno.land/std@0.192.0/http/server.ts';
|
import { serveListener } from 'https://deno.land/std@0.192.0/http/server.ts';
|
||||||
import * as argon from 'https://deno.land/x/argon2ian@2.0.0/src/argon2.ts';
|
import * as argon from 'https://deno.land/x/argon2ian@2.0.0/src/argon2.ts';
|
||||||
import * as brotli from 'https://deno.land/x/brotli@0.1.7/mod.ts';
|
import * as brotli from 'https://deno.land/x/brotli@0.1.7/mod.ts';
|
||||||
import { SQLiteDatastore } from './server/sqlite.ts';
|
import { SQLiteDatastore } from './sqlite.ts';
|
||||||
|
import { homePage } from './pages.ts';
|
||||||
const html = String.raw; // For tools/editors
|
|
||||||
|
|
||||||
const homePage = html`
|
|
||||||
<!doctype html>
|
|
||||||
<html lang=en>
|
|
||||||
<head>
|
|
||||||
<meta charset=utf-8>
|
|
||||||
<title>TiddlyPWA Sync Server Control Panel</title>
|
|
||||||
<style>
|
|
||||||
* { box-sizing: border-box; }
|
|
||||||
html { background: #252525; color: #fbfbfb; -webkit-text-size-adjust: none; text-size-adjust: none; accent-color: limegreen; }
|
|
||||||
body { margin: 2rem auto; min-width: 300px; max-width: 99ch; line-height: 1.5; word-wrap: break-word; font-family: system-ui, sans-serif; }
|
|
||||||
a { color: limegreen; }
|
|
||||||
a:hover { color: lime; }
|
|
||||||
h1 { font: 1.25rem monospace; text-align: center; color: limegreen; margin-bottom: 2rem; }
|
|
||||||
h2 { font-size: 1.15rem; margin: 1rem 0; }
|
|
||||||
fieldset { border: none; text-align: center; }
|
|
||||||
thead { font-weight: bolder; background: rgba(0,240,0,.1); }
|
|
||||||
footer { text-align: center; margin-top: 2rem; }
|
|
||||||
table { border-collapse: collapse; margin: 1rem 0; }
|
|
||||||
td { padding: 0.25rem 0.6rem; }
|
|
||||||
tr:nth-child(even) { background: rgba(255,255,255,.08); }
|
|
||||||
#wikirows td:first-of-type, #wikirows td:nth-of-type(2) { font-family: monospace; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>TiddlyPWA Sync Server Control Panel</h1>
|
|
||||||
<noscript>Enable JavaScript!</noscript>
|
|
||||||
<form id=login>
|
|
||||||
<fieldset>
|
|
||||||
<input type=password id=atoken>
|
|
||||||
<button>Log in</button>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
<div id=loggedin hidden>
|
|
||||||
<h2>Wikis on the server:</h2>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td>Token</td>
|
|
||||||
<td>Salt</td>
|
|
||||||
<td>Content Size</td>
|
|
||||||
<td>App Files Size</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id=wikirows>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<button id=refresh>Refresh</button>
|
|
||||||
<button id=create>Create new wiki</button>
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
<a href=https://tiddly.packett.cool/>TiddlyPWA</a> sync server ✦ software by <a href=https://val.packett.cool/>Val Packett</a>
|
|
||||||
</footer>
|
|
||||||
<script>
|
|
||||||
const knownErrors = {
|
|
||||||
EAUTH: 'Wrong token',
|
|
||||||
};
|
|
||||||
function formatBytes(bytes) {
|
|
||||||
const sizes = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB'];
|
|
||||||
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
|
|
||||||
if (i >= sizes.length) return 'too much';
|
|
||||||
return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
|
|
||||||
}
|
|
||||||
async function serverReq(data) {
|
|
||||||
const resp = await fetch('tid.dly', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
tiddlypwa: 1,
|
|
||||||
atoken: document.getElementById('atoken').value,
|
|
||||||
...data,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
if (!resp.ok) {
|
|
||||||
alert(await resp.json().then(({ error }) => knownErrors[error] || error).catch((_e) =>
|
|
||||||
'Server returned error ' + resp.status
|
|
||||||
));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
async function refreshTokens() {
|
|
||||||
const resp = await serverReq({ op: 'list' });
|
|
||||||
if (!resp) return false;
|
|
||||||
const { wikis } = await resp.json();
|
|
||||||
const wikirows = document.getElementById('wikirows')
|
|
||||||
wikirows.replaceChildren();
|
|
||||||
for (const { token, salt, tidsize, appsize } of wikis) {
|
|
||||||
const tr = document.createElement('tr');
|
|
||||||
const tokenTd = document.createElement('td');
|
|
||||||
tokenTd.innerText = token;
|
|
||||||
tr.appendChild(tokenTd);
|
|
||||||
const saltTd = document.createElement('td');
|
|
||||||
saltTd.innerText = salt;
|
|
||||||
tr.appendChild(saltTd);
|
|
||||||
const tidsizeTd = document.createElement('td');
|
|
||||||
tidsizeTd.innerText = tidsize > 0 ? formatBytes(tidsize) : '-';
|
|
||||||
tr.appendChild(tidsizeTd);
|
|
||||||
const appsizeTd = document.createElement('td');
|
|
||||||
if (appsize > 0) {
|
|
||||||
const appsizeA = document.createElement('a');
|
|
||||||
appsizeA.href = '/' + token.slice(0, token.length / 2) + '/app.html';
|
|
||||||
appsizeA.innerText = formatBytes(appsize);
|
|
||||||
appsizeTd.appendChild(appsizeA);
|
|
||||||
} else {
|
|
||||||
appsizeTd.innerText = '-';
|
|
||||||
}
|
|
||||||
tr.appendChild(appsizeTd);
|
|
||||||
const btnsTd = document.createElement('td');
|
|
||||||
const btnReauth = document.createElement('button');
|
|
||||||
btnReauth.innerText = 'Clear Auth';
|
|
||||||
btnReauth.onclick = (e) => {
|
|
||||||
if (!confirm('Do you really want to clear authentication checks for the wiki with token ' + token + '?')) return;
|
|
||||||
serverReq({ op: 'reauth', token }).then(() => document.getElementById('refresh').click());
|
|
||||||
};
|
|
||||||
btnsTd.appendChild(btnReauth);
|
|
||||||
const btnDel = document.createElement('button');
|
|
||||||
btnDel.innerText = 'Delete';
|
|
||||||
btnDel.onclick = (e) => {
|
|
||||||
if (!confirm('Do you really want to delete the wiki with token ' + token + '?')) return;
|
|
||||||
serverReq({ op: 'delete', token }).then(() => document.getElementById('refresh').click());
|
|
||||||
};
|
|
||||||
btnsTd.appendChild(btnDel);
|
|
||||||
tr.appendChild(btnsTd);
|
|
||||||
wikirows.appendChild(tr);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
window.addEventListener('DOMContentLoaded', (_) => {
|
|
||||||
const loginForm = document.getElementById('login');
|
|
||||||
loginForm.onsubmit = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
loginForm.querySelector('fieldset').disabled = true;
|
|
||||||
refreshTokens().then((suc) => {
|
|
||||||
document.getElementById('loggedin').hidden = !suc;
|
|
||||||
loginForm.hidden = suc;
|
|
||||||
loginForm.querySelector('fieldset').disabled = suc;
|
|
||||||
}).catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
alert('Unexpected error!');
|
|
||||||
loginForm.querySelector('fieldset').disabled = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const refreshBtn = document.getElementById('refresh');
|
|
||||||
const createBtn = document.getElementById('create');
|
|
||||||
refreshBtn.onclick = () => {
|
|
||||||
refreshBtn.disabled = createBtn.disabled = true;
|
|
||||||
refreshTokens().then(() => {
|
|
||||||
refreshBtn.disabled = createBtn.disabled = false;
|
|
||||||
}).catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
alert('Unexpected error!');
|
|
||||||
refreshBtn.disabled = createBtn.disabled = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
createBtn.onclick = () => {
|
|
||||||
serverReq({ op: 'create' }).then(() => refreshBtn.click());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const args = argparse(Deno.args);
|
const args = argparse(Deno.args);
|
||||||
const denv = args.dotenv ? await dotenv.load() : {};
|
const denv = args.dotenv ? await dotenv.load() : {};
|
||||||
165
server/pages.ts
Normal file
165
server/pages.ts
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
const html = String.raw; // For tools/editors
|
||||||
|
|
||||||
|
export const homePage = html`
|
||||||
|
<!doctype html>
|
||||||
|
<html lang=en>
|
||||||
|
<head>
|
||||||
|
<meta charset=utf-8>
|
||||||
|
<title>TiddlyPWA Sync Server Control Panel</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
html { background: #252525; color: #fbfbfb; -webkit-text-size-adjust: none; text-size-adjust: none; accent-color: limegreen; }
|
||||||
|
body { margin: 2rem auto; min-width: 300px; max-width: 99ch; line-height: 1.5; word-wrap: break-word; font-family: system-ui, sans-serif; }
|
||||||
|
a { color: limegreen; }
|
||||||
|
a:hover { color: lime; }
|
||||||
|
h1 { font: 1.25rem monospace; text-align: center; color: limegreen; margin-bottom: 2rem; }
|
||||||
|
h2 { font-size: 1.15rem; margin: 1rem 0; }
|
||||||
|
fieldset { border: none; text-align: center; }
|
||||||
|
thead { font-weight: bolder; background: rgba(0,240,0,.1); }
|
||||||
|
footer { text-align: center; margin-top: 2rem; }
|
||||||
|
table { border-collapse: collapse; margin: 1rem 0; }
|
||||||
|
td { padding: 0.25rem 0.6rem; }
|
||||||
|
tr:nth-child(even) { background: rgba(255,255,255,.08); }
|
||||||
|
#wikirows td:first-of-type, #wikirows td:nth-of-type(2) { font-family: monospace; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>TiddlyPWA Sync Server Control Panel</h1>
|
||||||
|
<noscript>Enable JavaScript!</noscript>
|
||||||
|
<form id=login>
|
||||||
|
<fieldset>
|
||||||
|
<input type=password id=atoken>
|
||||||
|
<button>Log in</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
<div id=loggedin hidden>
|
||||||
|
<h2>Wikis on the server:</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Token</td>
|
||||||
|
<td>Salt</td>
|
||||||
|
<td>Content Size</td>
|
||||||
|
<td>App Files Size</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id=wikirows>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button id=refresh>Refresh</button>
|
||||||
|
<button id=create>Create new wiki</button>
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
<a href=https://tiddly.packett.cool/>TiddlyPWA</a> sync server ✦ software by <a href=https://val.packett.cool/>Val Packett</a>
|
||||||
|
</footer>
|
||||||
|
<script>
|
||||||
|
const knownErrors = {
|
||||||
|
EAUTH: 'Wrong token',
|
||||||
|
};
|
||||||
|
function formatBytes(bytes) {
|
||||||
|
const sizes = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB'];
|
||||||
|
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
|
||||||
|
if (i >= sizes.length) return 'too much';
|
||||||
|
return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
async function serverReq(data) {
|
||||||
|
const resp = await fetch('tid.dly', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
tiddlypwa: 1,
|
||||||
|
atoken: document.getElementById('atoken').value,
|
||||||
|
...data,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!resp.ok) {
|
||||||
|
alert(await resp.json().then(({ error }) => knownErrors[error] || error).catch((_e) =>
|
||||||
|
'Server returned error ' + resp.status
|
||||||
|
));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
async function refreshTokens() {
|
||||||
|
const resp = await serverReq({ op: 'list' });
|
||||||
|
if (!resp) return false;
|
||||||
|
const { wikis } = await resp.json();
|
||||||
|
const wikirows = document.getElementById('wikirows')
|
||||||
|
wikirows.replaceChildren();
|
||||||
|
for (const { token, salt, tidsize, appsize } of wikis) {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
const tokenTd = document.createElement('td');
|
||||||
|
tokenTd.innerText = token;
|
||||||
|
tr.appendChild(tokenTd);
|
||||||
|
const saltTd = document.createElement('td');
|
||||||
|
saltTd.innerText = salt;
|
||||||
|
tr.appendChild(saltTd);
|
||||||
|
const tidsizeTd = document.createElement('td');
|
||||||
|
tidsizeTd.innerText = tidsize > 0 ? formatBytes(tidsize) : '-';
|
||||||
|
tr.appendChild(tidsizeTd);
|
||||||
|
const appsizeTd = document.createElement('td');
|
||||||
|
if (appsize > 0) {
|
||||||
|
const appsizeA = document.createElement('a');
|
||||||
|
appsizeA.href = '/' + token.slice(0, token.length / 2) + '/app.html';
|
||||||
|
appsizeA.innerText = formatBytes(appsize);
|
||||||
|
appsizeTd.appendChild(appsizeA);
|
||||||
|
} else {
|
||||||
|
appsizeTd.innerText = '-';
|
||||||
|
}
|
||||||
|
tr.appendChild(appsizeTd);
|
||||||
|
const btnsTd = document.createElement('td');
|
||||||
|
const btnReauth = document.createElement('button');
|
||||||
|
btnReauth.innerText = 'Clear Auth';
|
||||||
|
btnReauth.onclick = (e) => {
|
||||||
|
if (!confirm('Do you really want to clear authentication checks for the wiki with token ' + token + '?')) return;
|
||||||
|
serverReq({ op: 'reauth', token }).then(() => document.getElementById('refresh').click());
|
||||||
|
};
|
||||||
|
btnsTd.appendChild(btnReauth);
|
||||||
|
const btnDel = document.createElement('button');
|
||||||
|
btnDel.innerText = 'Delete';
|
||||||
|
btnDel.onclick = (e) => {
|
||||||
|
if (!confirm('Do you really want to delete the wiki with token ' + token + '?')) return;
|
||||||
|
serverReq({ op: 'delete', token }).then(() => document.getElementById('refresh').click());
|
||||||
|
};
|
||||||
|
btnsTd.appendChild(btnDel);
|
||||||
|
tr.appendChild(btnsTd);
|
||||||
|
wikirows.appendChild(tr);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
window.addEventListener('DOMContentLoaded', (_) => {
|
||||||
|
const loginForm = document.getElementById('login');
|
||||||
|
loginForm.onsubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
loginForm.querySelector('fieldset').disabled = true;
|
||||||
|
refreshTokens().then((suc) => {
|
||||||
|
document.getElementById('loggedin').hidden = !suc;
|
||||||
|
loginForm.hidden = suc;
|
||||||
|
loginForm.querySelector('fieldset').disabled = suc;
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
alert('Unexpected error!');
|
||||||
|
loginForm.querySelector('fieldset').disabled = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const refreshBtn = document.getElementById('refresh');
|
||||||
|
const createBtn = document.getElementById('create');
|
||||||
|
refreshBtn.onclick = () => {
|
||||||
|
refreshBtn.disabled = createBtn.disabled = true;
|
||||||
|
refreshTokens().then(() => {
|
||||||
|
refreshBtn.disabled = createBtn.disabled = false;
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
alert('Unexpected error!');
|
||||||
|
refreshBtn.disabled = createBtn.disabled = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
createBtn.onclick = () => {
|
||||||
|
serverReq({ op: 'create' }).then(() => refreshBtn.click());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue