Move rendering into private templates

Add an explicit template renderer with HTML views and partials for the app, bootstrap, package, and catalog pages. Move shared reporting setup into config/reporting.php and relocate stylesheet assets under css/.
This commit is contained in:
www-data
2026-05-26 12:50:26 +02:00
parent 2f2e8869d6
commit 475765e31f
55 changed files with 2328 additions and 1175 deletions
+15
View File
@@ -0,0 +1,15 @@
<header class="page-header">
<div class="page-titlebar">
<h1>{{title}}</h1>
<nav class="header-nav" aria-label="{{title}} navigation">
{{{nav_html}}}
{{{logout_html}}}
{{{user_html}}}
{{{language_html}}}
</nav>
</div>
{{{message_html}}}
{{{error_html}}}
</header>
+69
View File
@@ -0,0 +1,69 @@
<!doctype html>
<html lang="nl">
<head>
<meta charset="utf-8">
<title>Racket bootstrap</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body class="simple-doc">
<h1>Racket bootstrap</h1>
<p>
<code>racket.zip</code> is vooraf via de configuratiepagina gesplitst naar
parts in de map <code>data</code>.
</p>
<p>
Package index: <a href="{{pkg_url}}">Racket package index</a>
</p>
<p>
Bronbestand: <code>config/racket.zip</code><br>
Bronbestand bytes: <code>{{zip_size}}</code><br>
Maximale base64 part-grootte: <code>{{max_base64_kb}}</code> KiB (<code>{{max_base64_bytes}}</code> bytes)<br>
Binaire chunk-grootte: <code>{{binary_chunk_bytes}}</code> bytes<br>
Aantal parts: <code>{{part_count}}</code><br>
Parts gemaakt op: <code>{{created_at}}</code><br>
next-id voor alle links op deze pagina: <code>{{next_id}}</code>
</p>
<p>
Elke link hieronder geeft <strong>text/plain</strong> met de
<strong>base64-representatie van een binair part</strong>.
De URL bevat alleen een nummer, geen bestandsnaam en geen extensie.
</p>
<table>
<thead>
<tr>
<th>#</th>
<th>partnummer</th>
<th>bytes</th>
<th>base64 text/plain URL</th>
</tr>
</thead>
<tbody>
{{{part_rows_html}}}
</tbody>
</table>
<h2>Reconstructie in de sandbox</h2>
<p>
Decodeer ieder base64-part afzonderlijk naar een binair part. Plak daarna de
binaire parts in numerieke volgorde aan elkaar.
</p>
<pre>
base64 -d part-000001.txt &gt; part-000001
base64 -d part-000002.txt &gt; part-000002
base64 -d part-000003.txt &gt; part-000003
# enzovoort
cat part-* &gt; racket.zip
unzip racket.zip -d /tmp/racket
</pre>
</body>
</html>
+229
View File
@@ -0,0 +1,229 @@
<!doctype html>
<html lang="{{language}}">
<head>
<meta charset="utf-8">
<title>{{title}}</title>
<link rel="stylesheet" href="/css/styles.css?v={{style_version}}">
</head>
<body>
<div class="page">
{{{header_html}}}
<main class="page-main dashboard-main">
<section class="panel">
<h2>{{download_settings_label}}</h2>
<form method="post" action="/admin-config?lang={{language_url}}" class="admin-form-grid">
<input type="hidden" name="action" value="update_config">
<label>
{{racket_zip_chunk_label}}<br>
<input type="number" name="racket_zip_max_base64_kb" min="1" step="1" value="{{racket_zip_max_base64_kb}}" required>
</label>
<label>
{{package_zip_chunk_label}}<br>
<input type="number" name="package_zip_max_base64_kb" min="1" step="1" value="{{package_zip_max_base64_kb}}" required>
</label>
<button type="submit">{{save_configuration_label}}</button>
</form>
<p class="small">{{chunk_size_hint}}</p>
<table>
<tr>
<th>{{racket_zip_chunk_label}}</th>
<td>{{{racket_zip_base64_chunk_size}}}</td>
<td>{{{racket_zip_effective_binary_chunk}}}</td>
</tr>
<tr>
<th>{{package_zip_chunk_label}}</th>
<td>{{{package_zip_base64_chunk_size}}}</td>
<td>{{{package_zip_effective_binary_chunk}}}</td>
</tr>
<tr>
<th>{{racket_zip_source_label}}</th>
<td><code>config/racket.zip</code></td>
<td>{{{racket_zip_file_size}}}</td>
</tr>
<tr>
<th>{{racket_parts_label}}</th>
<td><code>{{racket_part_count}}</code></td>
<td>{{racket_parts_status}}</td>
</tr>
</table>
</section>
<section class="panel">
<h2>{{maintenance_label}}</h2>
<fieldset>
<legend>{{next_tokens_label}}</legend>
<form method="post" action="/admin-config?lang={{language_url}}">
<input type="hidden" name="action" value="cleanup_tokens">
<button type="submit">{{remove_expired_tokens_label}}</button>
</form>
<p class="small">{{cleanup_help}}</p>
<h3>{{current_next_tokens_label}}</h3>
{{{current_tokens_html}}}
</fieldset>
</section>
</main>
</div>
</body>
</html>
===
{
"translations": {
"app.title": {
"en": "Racket sandbox",
"nl": "Racket sandbox"
},
"app.manage_prompts": {
"en": "Manage prompts",
"nl": "Prompts beheren"
},
"app.user_management": {
"en": "User management",
"nl": "Gebruikersbeheer"
},
"app.configuration": {
"en": "Configuration",
"nl": "Configuratie"
},
"app.logout": {
"en": "Logout",
"nl": "Uitloggen"
},
"app.language": {
"en": "Language",
"nl": "Taal"
},
"app.logged_in_as": {
"en": "Logged in as:",
"nl": "Ingelogd als:"
},
"app.admin": {
"en": "Admin",
"nl": "Admin"
},
"app.back_to_sandbox": {
"en": "Back to Racket sandbox",
"nl": "Terug naar Racket sandbox"
},
"app.download_settings": {
"en": "Download settings",
"nl": "Downloadinstellingen"
},
"app.maintenance": {
"en": "Maintenance",
"nl": "Onderhoud"
},
"app.next_tokens": {
"en": "Next tokens",
"nl": "Next-tokens"
},
"app.current_next_tokens": {
"en": "Next tokens",
"nl": "Next-tokens"
},
"app.token": {
"en": "Token",
"nl": "Token"
},
"app.created_at": {
"en": "Created at",
"nl": "Aangemaakt op"
},
"app.expires_at": {
"en": "Expires at",
"nl": "Verloopt op"
},
"app.no_current_next_tokens": {
"en": "No next tokens.",
"nl": "Geen next-tokens."
},
"app.remove_expired_tokens": {
"en": "Remove expired next tokens",
"nl": "Verlopen next-tokens verwijderen"
},
"app.racket_zip_chunk_kb": {
"en": "Racket installation max base64 chunk size (KiB)",
"nl": "Maximale base64-chunkgrootte Racket-installatie (KiB)"
},
"app.package_zip_chunk_kb": {
"en": "Package/module max base64 chunk size (KiB)",
"nl": "Maximale base64-chunkgrootte packages/modules (KiB)"
},
"app.save_configuration": {
"en": "Save configuration",
"nl": "Configuratie opslaan"
},
"app.configuration_saved": {
"en": "Configuration saved.",
"nl": "Configuratie opgeslagen."
},
"app.configuration_saved_with_parts": {
"en": "Configuration saved. Racket installation parts regenerated: {{count}}",
"nl": "Configuratie opgeslagen. Racket-installatie parts opnieuw gemaakt: {{count}}"
},
"app.racket_parts_regenerated": {
"en": "Racket installation parts regenerated: {{count}}",
"nl": "Racket-installatie parts opnieuw gemaakt: {{count}}"
},
"app.chunk_size_hint_v2": {
"en": "Values are maximum base64 payload sizes in KiB. A {{chunk_size}} KiB binary chunk becomes {{base64_chunk_size}} KiB base64. Racket installation parts are regenerated when this configuration is saved.",
"nl": "Waarden zijn maximale base64-payloadgroottes in KiB. Een binaire chunk van {{chunk_size}} KiB wordt {{base64_chunk_size}} KiB base64. Racket-installatie parts worden opnieuw gemaakt wanneer deze configuratie wordt opgeslagen."
},
"app.effective_binary_chunk": {
"en": "Effective binary chunk",
"nl": "Effectieve binaire chunk"
},
"app.effective_binary_chunk_bytes": {
"en": "Effective binary chunk: {{bytes}} bytes",
"nl": "Effectieve binaire chunk: {{bytes}} bytes"
},
"app.base64_chunk_size_kib": {
"en": "{{size}} KiB",
"nl": "{{size}} KiB"
},
"app.file_size_bytes": {
"en": "{{bytes}} bytes",
"nl": "{{bytes}} bytes"
},
"app.racket_zip_source": {
"en": "Racket installation source",
"nl": "Bronbestand Racket-installatie"
},
"app.racket_parts": {
"en": "Racket installation parts",
"nl": "Racket-installatie parts"
},
"app.racket_parts_current": {
"en": "current",
"nl": "actueel"
},
"app.racket_parts_current_with_date": {
"en": "current, {{created_at}}",
"nl": "actueel, {{created_at}}"
},
"app.racket_parts_missing": {
"en": "missing or outdated; save configuration to regenerate",
"nl": "ontbreken of verouderd; sla configuratie op om opnieuw te maken"
},
"app.expired_tokens_removed": {
"en": "Expired next tokens removed: {{count}}",
"nl": "Verlopen next-tokens verwijderd: {{count}}"
},
"app.cleanup_help": {
"en": "Expired links should return an outdated information message to the AI agent. Cleanup only removes old token rows from SQLite.",
"nl": "Verlopen links moeten een melding over verouderde informatie aan de AI-agent teruggeven. Opruimen verwijdert alleen oude tokenrijen uit SQLite."
}
}
}
+237
View File
@@ -0,0 +1,237 @@
<!doctype html>
<html lang="{{language}}">
<head>
<meta charset="utf-8">
<title>{{title}}</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<div class="page">
{{{header_html}}}
<main class="page-main dashboard-main">
<section class="panel">
<h2>{{bootstrap_link_label}}</h2>
<fieldset>
<legend>{{generate_bootstrap_link_label}}</legend>
<div class="bootstrap-result-grid {{bootstrap_result_class}}">
<div>
<form method="post" action="/?lang={{language_url}}">
<input type="hidden" name="action" value="issue_bootstrap">
<label>
{{ttl_minutes_label}}<br>
<input type="number"
name="ttl_minutes"
value="{{bootstrap_ttl_minutes}}"
min="{{bootstrap_ttl_min_minutes}}"
max="{{bootstrap_ttl_max_minutes}}">
</label>
<p class="small">{{ttl_range_help}}</p>
<button type="submit">{{generate_bootstrap_link_label}}</button>
</form>
{{{generated_link_html}}}
</div>
{{{prompt_panel_html}}}
</div>
</fieldset>
</section>
</main>
</div>
<script src="/js/clipboard.js" defer></script>
<script src="/js/bootstrap-prompt.js" defer></script>
</body>
</html>
===
{
"translations": {
"app.title": {
"en": "Racket sandbox",
"nl": "Racket sandbox"
},
"app.manage_prompts": {
"en": "Manage prompts",
"nl": "Prompts beheren"
},
"app.logout": {
"en": "Logout",
"nl": "Uitloggen"
},
"app.language": {
"en": "Language",
"nl": "Taal"
},
"app.logged_in_as": {
"en": "Logged in as:",
"nl": "Ingelogd als:"
},
"app.admin": {
"en": "admin",
"nl": "admin"
},
"app.bootstrap_link": {
"en": "Bootstrap link",
"nl": "Bootstraplink"
},
"app.generate_bootstrap_link": {
"en": "Generate bootstrap link",
"nl": "Bootstraplink genereren"
},
"app.ttl_minutes": {
"en": "TTL in minutes",
"nl": "TTL in minuten"
},
"app.ttl_range_help": {
"en": "Allowed range: 30 minutes to 8 hours.",
"nl": "Toegestaan bereik: 30 minuten tot 8 uur."
},
"app.generated_link": {
"en": "Generated link",
"nl": "Gegenereerde link"
},
"app.copy": {
"en": "Copy",
"nl": "Kopieer"
},
"app.copied": {
"en": "Copied",
"nl": "Gekopieerd"
},
"app.generated_link_help": {
"en": "Give this link to the AI agent. The agent should start from this link and then only follow links from the generated HTML pages.",
"nl": "Geef deze link aan de AI-agent. De agent moet vanaf deze link starten en daarna alleen links volgen vanuit de gegenereerde HTML-paginas."
},
"app.select_prompt": {
"en": "Select prompt",
"nl": "Prompt selecteren"
},
"app.copy_full_prompt": {
"en": "Copy full prompt",
"nl": "Volledige prompt kopieren"
},
"app.bootstrap_prompt_help": {
"en": "Choose one of your prompts. The placeholder {{bootstrap-racket-link}} is replaced by the generated bootstrap link.",
"nl": "Kies een van je prompts. De placeholder {{bootstrap-racket-link}} wordt vervangen door de gegenereerde bootstraplink."
},
"app.no_bootstrap_prompts": {
"en": "No personal prompts are available for this language. Copy a default prompt first from prompt management.",
"nl": "Er zijn geen persoonlijke prompts beschikbaar voor deze taal. Kopieer eerst een standaardprompt vanuit promptbeheer."
},
"app.user_management": {
"en": "User management",
"nl": "Gebruikersbeheer"
},
"app.configuration": {
"en": "Configuration",
"nl": "Configuratie"
},
"app.user_management_help": {
"en": "Users are registered manually by email address, full name and password. This page only manages existing users.",
"nl": "Gebruikers worden handmatig geregistreerd met e-mailadres, volledige naam en wachtwoord. Deze pagina beheert alleen bestaande gebruikers."
},
"app.id": {
"en": "ID",
"nl": "ID"
},
"app.full_name": {
"en": "Full name",
"nl": "Volledige naam"
},
"app.email": {
"en": "Email",
"nl": "E-mail"
},
"app.enabled": {
"en": "Enabled",
"nl": "Ingeschakeld"
},
"app.created": {
"en": "Created",
"nl": "Gemaakt"
},
"app.last_login": {
"en": "Last login",
"nl": "Laatste login"
},
"app.actions": {
"en": "Actions",
"nl": "Acties"
},
"app.yes": {
"en": "yes",
"nl": "ja"
},
"app.no": {
"en": "no",
"nl": "nee"
},
"app.save_flags": {
"en": "Save flags",
"nl": "Vlaggen opslaan"
},
"app.new_password": {
"en": "New password",
"nl": "Nieuw wachtwoord"
},
"app.change_password": {
"en": "Change password",
"nl": "Wachtwoord wijzigen"
},
"app.delete_user": {
"en": "Delete user",
"nl": "Gebruiker verwijderen"
},
"app.delete_user_confirm": {
"en": "Delete user",
"nl": "Gebruiker verwijderen"
},
"app.cannot_delete_self": {
"en": "You cannot delete your own account.",
"nl": "Je kunt je eigen account niet verwijderen."
},
"app.bootstrap_link_issued": {
"en": "Bootstrap link issued.",
"nl": "Bootstraplink aangemaakt."
},
"app.password_changed_for": {
"en": "Password changed for: {{email}}",
"nl": "Wachtwoord gewijzigd voor: {{email}}"
},
"app.user_flags_updated": {
"en": "User flags updated.",
"nl": "Gebruikersvlaggen bijgewerkt."
},
"app.user_deleted": {
"en": "User deleted.",
"nl": "Gebruiker verwijderd."
},
"app.admin_rights_required": {
"en": "Admin rights required.",
"nl": "Adminrechten vereist."
},
"app.cannot_remove_own_admin": {
"en": "You cannot remove your own admin rights.",
"nl": "Je kunt je eigen adminrechten niet verwijderen."
},
"app.cannot_disable_self": {
"en": "You cannot disable your own account.",
"nl": "Je kunt je eigen account niet uitschakelen."
},
"app.unknown_action": {
"en": "Unknown action: {{action}}",
"nl": "Onbekende actie: {{action}}"
}
}
}
+61
View File
@@ -0,0 +1,61 @@
<!doctype html>
<html lang="{{language}}">
<head>
<meta charset="utf-8">
<title>{{page_title}}</title>
<link rel="stylesheet" href="/css/styles.css?v={{style_version}}">
</head>
<body class="simple-doc login-page">
<main class="login-layout">
<section class="login-panel">
<h1>{{page_title}}</h1>
{{{error_html}}}
<form method="post" action="/login.php">
<label>
{{email_label}}<br>
<input type="email" name="email" autocomplete="username" required>
</label>
<label>
{{password_label}}<br>
<input type="password" name="password" autocomplete="current-password" required>
</label>
<button type="submit">{{login_label}}</button>
</form>
</section>
<aside class="login-request-panel">
<h2>{{account_title}}</h2>
<p>{{account_text}}</p>
<p><a href="https://racket.discourse.group/">{{account_link}}</a></p>
</aside>
</main>
</body>
</html>
===
{
"translations": {
"en": {
"email": "Email address",
"password": "Password",
"login": "Login",
"account_title": "Want to try it?",
"account_text": "If you would like an account to try the sandbox, please request one from Hans Dijkema through the Racket Discourse pages.",
"account_link": "Go to Racket Discourse"
},
"nl": {
"email": "E-mailadres",
"password": "Wachtwoord",
"login": "Inloggen",
"account_title": "Wil je het eens proberen?",
"account_text": "Als je een account wilt om de sandbox eens uit te proberen, doe dan een verzoek aan Hans Dijkema via de Racket Discourse-pagina's.",
"account_link": "Naar Racket Discourse"
}
}
}
+53
View File
@@ -0,0 +1,53 @@
<!doctype html>
<html lang="nl">
<head>
<meta charset="utf-8">
<title>Package {{package}}</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body class="simple-doc">
<h1>Package {{package}}</h1>
<p>
Deze pagina is HTML. Alle part-links hieronder geven <code>text/plain</code>
met base64-inhoud terug. Dezelfde <code>next</code> wordt gebruikt voor alle
part-links op deze pagina.
</p>
<h2>Bron</h2>
<table>
{{{source_rows_html}}}
</table>
<h2>Base64 parts</h2>
<table>
<thead>
<tr>
<th>part</th>
<th>base64 bytes</th>
<th>text/plain URL</th>
</tr>
</thead>
<tbody>
{{{part_rows_html}}}
</tbody>
</table>
<h2>Reconstructie in de sandbox</h2>
<pre>
# download alle links als:
# {{package}}.part.000001.b64
# {{package}}.part.000002.b64
# enz.
cat {{package}}.part.*.b64 &gt; {{package}}.zip.b64
base64 -d {{package}}.zip.b64 &gt; {{package}}.zip
raco pkg install --auto ./{{package}}.zip
</pre>
</body>
</html>
@@ -0,0 +1,5 @@
<tr class="{{status_class}}">
<td><code>{{token}}</code></td>
<td>{{created_at}}</td>
<td>{{expires_at}}</td>
</tr>
@@ -0,0 +1,8 @@
<table>
<tr>
<th>{{token_label}}</th>
<th>{{created_at_label}}</th>
<th>{{expires_at_label}}</th>
</tr>
{{{token_rows_html}}}
</table>
@@ -0,0 +1 @@
<span class="small">({{admin_label}})</span>
@@ -0,0 +1 @@
<input type="hidden" name="{{name}}" value="{{value}}">
@@ -0,0 +1,9 @@
<form class="header-language-form" method="get" action="{{language_action}}">
{{{hidden_inputs_html}}}
<label>
{{language_label}}
<select name="lang" onchange="this.form.submit()">
{{{language_options_html}}}
</select>
</label>
</form>
@@ -0,0 +1,5 @@
<span class="nav-separator" aria-hidden="true">|</span>
<form class="header-action-form" method="post" action="{{logout_action}}">
<input type="hidden" name="action" value="logout">
<button type="submit">{{logout_label}}</button>
</form>
@@ -0,0 +1 @@
<strong>{{label}}</strong>
@@ -0,0 +1 @@
<a href="{{url}}">{{label}}</a>
@@ -0,0 +1 @@
<span class="nav-separator" aria-hidden="true">|</span>
+6
View File
@@ -0,0 +1,6 @@
<span class="nav-separator" aria-hidden="true">|</span>
<span class="nav-user">
{{user_prefix}}
{{display_name}}
{{{admin_html}}}
</span>
@@ -0,0 +1,11 @@
<h3>{{generated_link_label}}</h3>
<div class="generated-link-row">
<a href="{{issued_link}}" target="_blank" rel="noopener noreferrer">{{issued_link}}</a>
<button type="button"
class="js-copy-button"
data-copy-text="{{issued_link}}"
data-copy-label="{{copy_label}}"
data-copied-label="{{copied_label}}">
{{copy_label}}
</button>
</div>
@@ -0,0 +1,21 @@
<div class="bootstrap-prompt-tool">
<label>
{{select_prompt_label}}<br>
<select id="bootstrapPromptSelect">
{{{prompt_options_html}}}
</select>
</label>
<textarea id="bootstrapPromptOutput" readonly rows="12"></textarea>
<button type="button"
class="js-copy-button"
data-copy-target="bootstrapPromptOutput"
data-copy-label="{{copy_label}}"
data-copied-label="{{copied_label}}">
{{copy_full_prompt_label}}
</button>
</div>
<script type="application/json" id="bootstrapPromptData">
{{{bootstrap_prompt_json}}}
</script>
@@ -0,0 +1,6 @@
<div>
<h3>{{copy_full_prompt_label}}</h3>
<p>{{bootstrap_prompt_help}}</p>
{{{prompt_tool_html}}}
</div>
+1
View File
@@ -0,0 +1 @@
<p><a href="/login.php">{{login_label}}</a></p>
+1
View File
@@ -0,0 +1 @@
<div class="{{class}}">{{message}}</div>
@@ -0,0 +1,6 @@
<tr id="pkg-{{id}}">
<td>{{index}}</td>
<td class="pkg-name">
<a class="pkg-link" href="{{url}}">{{name}}</a>
</td>
</tr>
@@ -0,0 +1,5 @@
<tr>
<td>{{number}}</td>
<td>{{base64_bytes}}</td>
<td><a href="{{url}}">{{url}}</a></td>
</tr>
@@ -0,0 +1 @@
<tr><th>{{label}}</th><td>{{{value_html}}}</td></tr>
+1
View File
@@ -0,0 +1 @@
<p class="{{class}}">{{text}}</p>
@@ -0,0 +1,4 @@
<div class="default-admin-notice">
<strong>{{badge}}</strong>
<span>{{hint}}</span>
</div>
@@ -0,0 +1,15 @@
<details class="create-drawer">
<summary>{{create_default_label}}</summary>
<form method="post" action="/prompts?lang={{language_url}}&mode=defaults">
<input type="hidden" name="action" value="create_default">
<label>{{default_key_label}}<br><input type="text" name="default_key" placeholder="bootstrap-racket"></label>
<label>{{name_label}}<br><input type="text" name="name"></label>
<label>{{language_label}}<br>
<select name="language">
{{{language_options_html}}}
</select>
</label>
<label>{{prompt_content_label}}<br><textarea name="content" rows="7"></textarea></label>
<button type="submit">{{create_default_label}}</button>
</form>
</details>
@@ -0,0 +1,6 @@
<form method="post" action="/prompts?lang={{language_url}}&mode=defaults"
onsubmit="return confirm({{{confirm_json}}});">
<input type="hidden" name="action" value="delete_default">
<input type="hidden" name="default_id" value="{{id}}">
<button type="submit">{{delete_label}}</button>
</form>
@@ -0,0 +1,17 @@
<div class="prompt-list-item default-prompt-item">
<button type="button"
class="prompt-select js-open-prompt"
data-kind="default"
data-id="{{id}}">
<span class="prompt-name">{{name}}</span>
<span class="prompt-subline">{{{metadata}}}</span>
</button>
<form method="post" action="/prompts?lang={{language_url}}&mode=personal">
<input type="hidden" name="action" value="copy_default">
<input type="hidden" name="default_id" value="{{id}}">
<button type="submit">{{copy_label}}</button>
</form>
{{{admin_delete_html}}}
</div>
@@ -0,0 +1,15 @@
<div class="prompt-list-item">
<button type="button"
class="prompt-select js-open-prompt"
data-kind="personal"
data-id="{{id}}">
<span class="prompt-name">{{name}}</span>
<span class="prompt-subline">{{metadata}}</span>
</button>
<form method="post" action="/prompts?lang={{language_url}}&mode=personal"
onsubmit="return confirm({{{confirm_json}}});">
<input type="hidden" name="action" value="delete_prompt">
<input type="hidden" name="prompt_id" value="{{id}}">
<button type="submit">{{delete_label}}</button>
</form>
</div>
@@ -0,0 +1,6 @@
<tr>
<td>{{index}}</td>
<td>{{number}}</td>
<td>{{size}}</td>
<td><a href="{{url}}">{{url}}</a></td>
</tr>
@@ -0,0 +1 @@
<option value="{{value}}"{{{selected}}}>{{label}}</option>
@@ -0,0 +1,6 @@
<form method="post" action="/users?lang={{language_url}}"
onsubmit="return confirm({{{confirm_json}}});">
<input type="hidden" name="action" value="delete_user">
<input type="hidden" name="user_id" value="{{user_id}}">
<button type="submit">{{delete_user_label}}</button>
</form>
+27
View File
@@ -0,0 +1,27 @@
<tr>
<td colspan="7">
<form method="post" action="/users?lang={{language_url}}" class="user-row-form">
<input type="hidden" name="action" value="update_user">
<input type="hidden" name="user_id" value="{{user_id}}">
<label>{{full_name_label}}<br><input type="text" name="full_name" value="{{full_name}}" required></label>
<label>{{email_label}}<br><input type="email" name="email" value="{{email}}" required></label>
<label><input type="checkbox" name="is_admin" value="1"{{{is_admin_checked}}}> {{admin_label}}</label>
<label><input type="checkbox" name="is_enabled" value="1"{{{is_enabled_checked}}}> {{enabled_label}}</label>
<span>{{created_at}}</span>
<span>{{last_login_at}}</span>
<button type="submit">{{update_user_label}}</button>
</form>
<div class="user-row-actions">
<form method="post" action="/users?lang={{language_url}}">
<input type="hidden" name="action" value="set_password">
<input type="hidden" name="email" value="{{email}}">
<label>{{new_password_label}}<br><input type="password" name="password" autocomplete="new-password"></label>
<button type="submit">{{change_password_label}}</button>
</form>
{{{delete_html}}}
</div>
</td>
</tr>
@@ -0,0 +1 @@
<p class="small">{{cannot_delete_self}}</p>
+462
View File
@@ -0,0 +1,462 @@
<!doctype html>
<html lang="{{language}}">
<head>
<meta charset="utf-8">
<title>{{title}}</title>
<link rel="stylesheet" href="/css/styles.css?v={{style_version}}">
</head>
<body>
<div class="page">
{{{header_html}}}
<main class="page-main prompt-workbench">
<aside class="prompt-sidebar panel">
<div class="prompt-tabs" role="tablist" aria-label="Prompt lists">
<button type="button" class="prompt-tab {{defaults_tab_active_class}}" data-tab="defaults" role="tab" aria-selected="{{defaults_tab_selected}}">
{{available_defaults_label}}
</button>
<button type="button" class="prompt-tab {{personal_tab_active_class}}" data-tab="personal" role="tab" aria-selected="{{personal_tab_selected}}">
{{your_prompts_label}}
</button>
</div>
<section class="prompt-tab-panel {{defaults_tab_active_class}}" id="tab-defaults" role="tabpanel">
{{{default_admin_notice_html}}}
<div class="sidebar-actions">
<form method="post" action="/prompts?lang={{language_url}}&mode=personal">
<input type="hidden" name="action" value="copy_all_defaults">
<input type="hidden" name="language" value="{{language}}">
<button type="submit">{{copy_all_label}}</button>
</form>
</div>
<div class="prompt-list">
{{{default_prompts_html}}}
{{{no_defaults_html}}}
</div>
{{{create_default_html}}}
</section>
<section class="prompt-tab-panel {{personal_tab_active_class}}" id="tab-personal" role="tabpanel">
<div class="prompt-list">
{{{personal_prompts_html}}}
{{{no_personal_html}}}
</div>
<details class="create-drawer">
<summary>{{create_personal_label}}</summary>
<form method="post" action="/prompts?lang={{language_url}}&mode=personal">
<input type="hidden" name="action" value="create_prompt">
<label>{{name_label}}<br><input type="text" name="name"></label>
<label>{{language_label}}<br>
<select name="language">
{{{language_options_html}}}
</select>
</label>
<label>{{prompt_content_label}}<br><textarea name="content" rows="7"></textarea></label>
<button type="submit">{{create_personal_label}}</button>
</form>
</details>
</section>
</aside>
<section id="promptViewer" class="prompt-viewer panel is-empty">
<div class="viewer-empty">
{{select_prompt_label}}
</div>
<div class="viewer-shell">
<div class="viewer-header">
<div>
<div class="viewer-title" id="viewerTitle">{{prompt_label}}</div>
<div class="version-meta" id="viewerMeta"></div>
</div>
<button type="button" id="editPromptButton">{{edit_label}}</button>
</div>
<pre id="viewerContent" class="viewer-content"></pre>
</div>
</section>
</main>
<footer class="page-footer">
<a href="/">{{back_label}}</a>
</footer>
</div>
<div id="promptModalBackdrop" class="modal-backdrop">
<div class="prompt-modal">
<form id="promptModalForm" class="edit-mode" method="post" action="/prompts?lang={{language_url}}&mode={{mode_url}}">
<div class="modal-header">
<div>
<div class="modal-title" id="modalTitle">{{prompt_label}}</div>
<div class="version-meta" id="modalMeta"></div>
</div>
<button type="button" id="cancelEditButton">{{close_label}}</button>
</div>
<div class="modal-toolbar">
<button type="button" id="versionNewerButton">{{newer_previous_label}}</button>
<button type="button" id="versionOlderButton">{{older_previous_label}}</button>
<label>
{{diff_view_label}}
<select id="diffMode">
<option value="plain">{{diff_plain_label}}</option>
<option value="all" selected>{{diff_all_label}}</option>
<option value="same">{{diff_same_label}}</option>
<option value="added">{{diff_added_label}}</option>
<option value="deleted">{{diff_deleted_label}}</option>
<option value="changed">{{diff_changed_label}}</option>
</select>
</label>
<span id="versionIndicator" class="version-meta"></span>
</div>
<div class="modal-body">
<div class="edit-pane">
<div class="modal-form-grid">
<label>
{{name_label}}<br>
<input type="text" id="modalName" name="name">
</label>
<label>
{{language_label}}<br>
<select id="modalLanguage" name="language">
{{{language_options_plain_html}}}
</select>
</label>
</div>
<div id="defaultKeyRow" class="default-key-row">
<label>
{{default_key_label}}<br>
<input type="text" id="modalDefaultKey" name="default_key">
</label>
</div>
<label class="content-label">
<span>{{prompt_content_label}}</span>
<textarea id="modalContent" name="content"></textarea>
</label>
</div>
<div class="version-pane">
<div>
<strong>{{previous_version_label}}</strong>
<div id="selectedVersionMeta" class="version-meta"></div>
</div>
<div class="version-content" id="versionContent"></div>
</div>
</div>
<div class="modal-footer">
<input type="hidden" id="modalAction" name="action" value="">
<input type="hidden" id="modalPromptId" name="prompt_id" value="">
<input type="hidden" id="modalDefaultId" name="default_id" value="">
<label>
<input type="checkbox" name="create_version" value="1" checked>
{{store_version_label}}
</label>
<label>
{{version_note_label}}
<input type="text" name="version_note" value="editor edit">
</label>
<button type="submit">{{save_label}}</button>
<button type="button" id="snapshotButton">{{store_snapshot_label}}</button>
<button type="button" id="restoreVersionButton">{{restore_version_label}}</button>
<button type="button" id="deleteVersionButton">{{delete_version_label}}</button>
</div>
</form>
</div>
</div>
<form id="modalAuxForm" method="post" action="/prompts?lang={{language_url}}&mode={{mode_url}}" class="hidden">
<input type="hidden" id="auxAction" name="action" value="">
<input type="hidden" id="auxPromptId" name="prompt_id" value="">
<input type="hidden" id="auxDefaultId" name="default_id" value="">
<input type="hidden" id="auxVersionNo" name="version_no" value="">
<input type="hidden" id="auxVersionNote" name="version_note" value="">
</form>
<script type="application/json" id="promptDataJson">
{{{prompt_data_json}}}
</script>
<script type="application/json" id="promptTextJson">
{{{prompt_text_json}}}
</script>
<script src="/js/prompt-editor.js?v={{prompt_editor_version}}" defer></script>
</body>
</html>
===
{
"translations": {
"prompts.title": {
"en": "Prompt administration",
"nl": "Promptbeheer"
},
"prompts.back": {
"en": "Back to Racket sandbox",
"nl": "Terug naar Racket sandbox"
},
"prompts.your_prompts": {
"en": "Your prompts",
"nl": "Jouw prompts"
},
"prompts.default_admin": {
"en": "Default prompt administration",
"nl": "Standaardpromptbeheer"
},
"prompts.language": {
"en": "Language",
"nl": "Taal"
},
"prompts.available_defaults": {
"en": "Available default prompts",
"nl": "Beschikbare standaardprompts"
},
"prompts.default_admin_badge": {
"en": "Admin default prompts",
"nl": "Admin standaardprompts"
},
"prompts.default_admin_hint": {
"en": "Defaults are shared with every user and can be copied into personal prompts.",
"nl": "Standaarden worden gedeeld met alle gebruikers en kunnen naar persoonlijke prompts worden gekopieerd."
},
"prompts.copy_all": {
"en": "Copy all",
"nl": "Alles kopieren"
},
"prompts.copy": {
"en": "copy",
"nl": "kopieer"
},
"prompts.delete": {
"en": "delete",
"nl": "verwijder"
},
"prompts.create_default": {
"en": "Create default prompt",
"nl": "Standaardprompt maken"
},
"prompts.create_personal": {
"en": "Create personal prompt",
"nl": "Persoonlijke prompt maken"
},
"prompts.name": {
"en": "Name",
"nl": "Naam"
},
"prompts.default_key": {
"en": "Default key",
"nl": "Standaardsleutel"
},
"prompts.prompt_content": {
"en": "Prompt content",
"nl": "Promptinhoud"
},
"prompts.no_defaults": {
"en": "No default prompts for this language yet.",
"nl": "Nog geen standaardprompts voor deze taal."
},
"prompts.no_personal": {
"en": "No personal prompts yet for this language.",
"nl": "Nog geen persoonlijke prompts voor deze taal."
},
"prompts.select_prompt": {
"en": "Select a prompt on the left to view it.",
"nl": "Selecteer links een prompt om deze te bekijken."
},
"prompts.edit": {
"en": "Edit",
"nl": "Bewerk"
},
"prompts.close": {
"en": "Close",
"nl": "Sluiten"
},
"prompts.newer_previous": {
"en": "newer previous version",
"nl": "nieuwere vorige versie"
},
"prompts.older_previous": {
"en": "older previous version",
"nl": "oudere vorige versie"
},
"prompts.diff_view": {
"en": "Diff view:",
"nl": "Verschilweergave:"
},
"prompts.diff_plain": {
"en": "text, no diff",
"nl": "tekst, geen verschil"
},
"prompts.diff_all": {
"en": "all diff",
"nl": "alle verschillen"
},
"prompts.diff_same": {
"en": "unchanged only",
"nl": "alleen ongewijzigd"
},
"prompts.diff_added": {
"en": "additions only",
"nl": "alleen toevoegingen"
},
"prompts.diff_deleted": {
"en": "deletions only",
"nl": "alleen verwijderingen"
},
"prompts.diff_changed": {
"en": "changes only",
"nl": "alleen wijzigingen"
},
"prompts.previous_version": {
"en": "Previous version",
"nl": "Vorige versie"
},
"prompts.store_version": {
"en": "store this edit as a new version",
"nl": "bewaar deze bewerking als nieuwe versie"
},
"prompts.version_note": {
"en": "Version note:",
"nl": "Versienotitie:"
},
"prompts.save": {
"en": "Save",
"nl": "Opslaan"
},
"prompts.store_snapshot": {
"en": "Store snapshot",
"nl": "Snapshot bewaren"
},
"prompts.restore_version": {
"en": "Restore selected version",
"nl": "Geselecteerde versie herstellen"
},
"prompts.delete_version": {
"en": "Delete selected version",
"nl": "Geselecteerde versie verwijderen"
},
"prompts.prompt": {
"en": "Prompt",
"nl": "Prompt"
},
"prompts.prompt_not_found": {
"en": "Prompt not found",
"nl": "Prompt niet gevonden"
},
"prompts.no_previous_versions": {
"en": "No previous versions stored.",
"nl": "Geen vorige versies opgeslagen."
},
"prompts.no_lines_for_view": {
"en": "No lines for this view.",
"nl": "Geen regels voor deze weergave."
},
"prompts.restore_version_confirm": {
"en": "Restore version",
"nl": "Versie herstellen"
},
"prompts.delete_version_confirm": {
"en": "Delete version",
"nl": "Versie verwijderen"
},
"prompts.delete_default_confirm": {
"en": "Delete default prompt {{name}}?",
"nl": "Standaardprompt {{name}} verwijderen?"
},
"prompts.delete_prompt_confirm": {
"en": "Delete prompt {{name}}?",
"nl": "Prompt {{name}} verwijderen?"
},
"prompts.default_metadata": {
"en": "{{default_key}} · {{updated_at}}",
"nl": "{{default_key}} · {{updated_at}}"
},
"prompts.personal_metadata": {
"en": "{{language}} · {{updated_at}}",
"nl": "{{language}} · {{updated_at}}"
},
"prompts.default_prompt_prefix": {
"en": "Default prompt: ",
"nl": "Standaardprompt: "
},
"prompts.prompt_prefix": {
"en": "Prompt: ",
"nl": "Prompt: "
},
"prompts.created": {
"en": "created",
"nl": "gemaakt"
},
"prompts.updated": {
"en": "updated",
"nl": "bijgewerkt"
},
"prompts.default_prompt": {
"en": "default prompt",
"nl": "standaardprompt"
},
"prompts.version": {
"en": "version",
"nl": "versie"
},
"prompts.showing_version": {
"en": "showing version",
"nl": "toont versie"
},
"prompts.of": {
"en": "of",
"nl": "van"
},
"prompts.old": {
"en": "old",
"nl": "oud"
},
"prompts.new": {
"en": "new",
"nl": "nieuw"
},
"prompts.unknown_action": {
"en": "Unknown action: {{action}}",
"nl": "Onbekende actie: {{action}}"
},
"app.admin": {
"en": "admin",
"nl": "admin"
},
"app.user_management": {
"en": "User management",
"nl": "Gebruikersbeheer"
},
"app.configuration": {
"en": "Configuration",
"nl": "Configuratie"
},
"app.logout": {
"en": "Logout",
"nl": "Uitloggen"
}
}
}
+11
View File
@@ -0,0 +1,11 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>{{title}}</title>
</head>
<body>
<h1>{{title}}</h1>
<pre>{{message}}</pre>
</body>
</html>
+37
View File
@@ -0,0 +1,37 @@
<!doctype html>
<html lang="nl">
<head>
<meta charset="utf-8">
<title>Racket package index</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body class="simple-doc package-index-page">
<h1>Racket package index</h1>
<p>
Volledige HTML-index van de Racket package catalogus op basis van
<code>pkgs-all</code>. De package-naam is de ophaallink via
<code>rktsndbx.dijkewijk.nl</code>.
</p>
<p>
Aantal packages: <code>{{package_count}}</code><br>
next-id voor alle package-links op deze pagina:
<code>{{next_id}}</code>
</p>
<table>
<thead>
<tr>
<th>#</th>
<th>package</th>
</tr>
</thead>
<tbody>
{{{package_rows_html}}}
</tbody>
</table>
</body>
</html>
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="{{language}}">
<head>
<meta charset="utf-8">
<title>{{title}}</title>
</head>
<body>
<h1>{{title}}</h1>
<p>{{message}}</p>
{{{extra_html}}}
</body>
</html>
+175
View File
@@ -0,0 +1,175 @@
<!doctype html>
<html lang="{{language}}">
<head>
<meta charset="utf-8">
<title>{{title}}</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<div class="page">
{{{header_html}}}
<main class="page-main dashboard-main">
<section class="panel">
<h2>{{create_user_label}}</h2>
<form method="post" action="/users?lang={{language_url}}" class="admin-form-grid">
<input type="hidden" name="action" value="create_user">
<label>{{full_name_label}}<br><input type="text" name="full_name" required></label>
<label>{{email_label}}<br><input type="email" name="email" required></label>
<label>{{password_label}}<br><input type="password" name="password" autocomplete="new-password" required></label>
<label><input type="checkbox" name="is_admin" value="1"> {{admin_label}}</label>
<label><input type="checkbox" name="is_enabled" value="1" checked> {{enabled_label}}</label>
<button type="submit">{{create_user_label}}</button>
</form>
</section>
<section class="panel">
<h2>{{user_management_label}}</h2>
<table>
<thead>
<tr>
<th>{{full_name_label}}</th>
<th>{{email_label}}</th>
<th>{{admin_label}}</th>
<th>{{enabled_label}}</th>
<th>{{created_label}}</th>
<th>{{last_login_label}}</th>
<th>{{actions_label}}</th>
</tr>
</thead>
<tbody>
{{{user_rows_html}}}
</tbody>
</table>
</section>
</main>
</div>
</body>
</html>
===
{
"translations": {
"app.title": {
"en": "Racket sandbox",
"nl": "Racket sandbox"
},
"app.manage_prompts": {
"en": "Manage prompts",
"nl": "Prompts beheren"
},
"app.user_management": {
"en": "User management",
"nl": "Gebruikersbeheer"
},
"app.logout": {
"en": "Logout",
"nl": "Uitloggen"
},
"app.language": {
"en": "Language",
"nl": "Taal"
},
"app.logged_in_as": {
"en": "Logged in as:",
"nl": "Ingelogd als:"
},
"app.admin": {
"en": "Admin",
"nl": "Admin"
},
"app.enabled": {
"en": "Enabled",
"nl": "Ingeschakeld"
},
"app.full_name": {
"en": "Full name",
"nl": "Volledige naam"
},
"app.email": {
"en": "Email",
"nl": "E-mail"
},
"app.password": {
"en": "Password",
"nl": "Wachtwoord"
},
"app.new_password": {
"en": "New password",
"nl": "Nieuw wachtwoord"
},
"app.created": {
"en": "Created",
"nl": "Gemaakt"
},
"app.last_login": {
"en": "Last login",
"nl": "Laatste login"
},
"app.actions": {
"en": "Actions",
"nl": "Acties"
},
"app.create_user": {
"en": "Create user",
"nl": "Gebruiker aanmaken"
},
"app.update_user": {
"en": "Update user",
"nl": "Gebruiker aanpassen"
},
"app.change_password": {
"en": "Change password",
"nl": "Wachtwoord wijzigen"
},
"app.delete_user": {
"en": "Delete user",
"nl": "Gebruiker verwijderen"
},
"app.delete_user_confirm": {
"en": "Delete user {{email}}?",
"nl": "Gebruiker {{email}} verwijderen?"
},
"app.cannot_delete_self": {
"en": "You cannot delete your own account.",
"nl": "Je kunt je eigen account niet verwijderen."
},
"app.cannot_disable_self": {
"en": "You cannot disable your own account.",
"nl": "Je kunt je eigen account niet uitschakelen."
},
"app.cannot_remove_own_admin": {
"en": "You cannot remove your own admin rights.",
"nl": "Je kunt je eigen adminrechten niet verwijderen."
},
"app.user_created": {
"en": "User created.",
"nl": "Gebruiker aangemaakt."
},
"app.user_updated": {
"en": "User updated.",
"nl": "Gebruiker aangepast."
},
"app.password_changed": {
"en": "Password changed.",
"nl": "Wachtwoord gewijzigd."
},
"app.user_deleted": {
"en": "User deleted.",
"nl": "Gebruiker verwijderd."
},
"app.back_to_sandbox": {
"en": "Back to Racket sandbox",
"nl": "Terug naar Racket sandbox"
},
"app.configuration": {
"en": "Configuration",
"nl": "Configuratie"
}
}
}