initial import
This commit is contained in:
+349
@@ -0,0 +1,349 @@
|
||||
<?php
|
||||
/*
|
||||
* config.php
|
||||
*
|
||||
* Admin application configuration.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/auth.php';
|
||||
require_once __DIR__ . '/header.php';
|
||||
require_once __DIR__ . '/languagestore.php';
|
||||
require_once __DIR__ . '/nexttoken.php';
|
||||
require_once __DIR__ . '/usersettings.php';
|
||||
require_once __DIR__ . '/base64config.php';
|
||||
require_once __DIR__ . '/racketzip.php';
|
||||
|
||||
ini_set('display_errors', '1');
|
||||
ini_set('display_startup_errors', '1');
|
||||
ini_set('log_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
$DB_FILE = __DIR__ . '/data/racket-sandbox.sqlite';
|
||||
|
||||
$auth = new RacketSandboxAuth($DB_FILE);
|
||||
$languageStore = new LanguageStore($DB_FILE);
|
||||
$tokens = new NextTokenStore($DB_FILE);
|
||||
$userSettings = new UserSettingsStore($DB_FILE);
|
||||
$currentUser = $auth->requireAdminHtml();
|
||||
|
||||
$message = '';
|
||||
$error = '';
|
||||
$base64ChunkConfig = load_base64_chunk_config();
|
||||
$localTimezone = new DateTimeZone('Europe/Amsterdam');
|
||||
|
||||
function h($s)
|
||||
{
|
||||
return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
function t($key, $fallback = null)
|
||||
{
|
||||
global $languageStore, $language;
|
||||
|
||||
return $languageStore->translate($key, $language, $fallback);
|
||||
}
|
||||
|
||||
function post_value($name, $default = '')
|
||||
{
|
||||
return $_POST[$name] ?? $default;
|
||||
}
|
||||
|
||||
function resolve_user_language($userSettings, $userId, $allowedLanguages)
|
||||
{
|
||||
$language = isset($_GET['lang'])
|
||||
? (string)$_GET['lang']
|
||||
: (string)$userSettings->get($userId, 'language', 'en');
|
||||
|
||||
if (!in_array($language, $allowedLanguages, true)) {
|
||||
$language = 'en';
|
||||
}
|
||||
|
||||
$userSettings->set($userId, 'language', $language);
|
||||
|
||||
return $language;
|
||||
}
|
||||
|
||||
function base64_binary_chunk_bytes_for_kib($kib)
|
||||
{
|
||||
return racket_zip_binary_chunk_bytes_for_base64_kib($kib);
|
||||
}
|
||||
|
||||
function format_token_time($timestamp)
|
||||
{
|
||||
global $localTimezone;
|
||||
|
||||
$time = (new DateTimeImmutable('@' . (int)$timestamp))->setTimezone($localTimezone);
|
||||
|
||||
return $time->format('Y-m-d H:i:s T');
|
||||
}
|
||||
|
||||
function token_status_class($token)
|
||||
{
|
||||
return time() <= (int)($token['expires_at'] ?? 0)
|
||||
? 'token-valid'
|
||||
: 'token-expired';
|
||||
}
|
||||
|
||||
$language = resolve_user_language(
|
||||
$userSettings,
|
||||
$currentUser->id(),
|
||||
$languageStore->supportedLanguages()
|
||||
);
|
||||
|
||||
$languageStore->seedDefaults(array(
|
||||
'app.title' => array('en' => 'Racket sandbox', 'nl' => 'Racket sandbox'),
|
||||
'app.manage_prompts' => array('en' => 'Manage prompts', 'nl' => 'Prompts beheren'),
|
||||
'app.user_management' => array('en' => 'User management', 'nl' => 'Gebruikersbeheer'),
|
||||
'app.configuration' => array('en' => 'Configuration', 'nl' => 'Configuratie'),
|
||||
'app.logout' => array('en' => 'Logout', 'nl' => 'Uitloggen'),
|
||||
'app.language' => array('en' => 'Language', 'nl' => 'Taal'),
|
||||
'app.logged_in_as' => array('en' => 'Logged in as:', 'nl' => 'Ingelogd als:'),
|
||||
'app.admin' => array('en' => 'Admin', 'nl' => 'Admin'),
|
||||
'app.back_to_sandbox' => array('en' => 'Back to Racket sandbox', 'nl' => 'Terug naar Racket sandbox'),
|
||||
'app.download_settings' => array('en' => 'Download settings', 'nl' => 'Downloadinstellingen'),
|
||||
'app.maintenance' => array('en' => 'Maintenance', 'nl' => 'Onderhoud'),
|
||||
'app.next_tokens' => array('en' => 'Next tokens', 'nl' => 'Next-tokens'),
|
||||
'app.current_next_tokens' => array('en' => 'Next tokens', 'nl' => 'Next-tokens'),
|
||||
'app.token' => array('en' => 'Token', 'nl' => 'Token'),
|
||||
'app.created_at' => array('en' => 'Created at', 'nl' => 'Aangemaakt op'),
|
||||
'app.expires_at' => array('en' => 'Expires at', 'nl' => 'Verloopt op'),
|
||||
'app.no_current_next_tokens' => array('en' => 'No next tokens.', 'nl' => 'Geen next-tokens.'),
|
||||
'app.remove_expired_tokens' => array('en' => 'Remove expired next tokens', 'nl' => 'Verlopen next-tokens verwijderen'),
|
||||
'app.racket_zip_chunk_kb' => array('en' => 'Racket installation max base64 chunk size (KiB)', 'nl' => 'Maximale base64-chunkgrootte Racket-installatie (KiB)'),
|
||||
'app.package_zip_chunk_kb' => array('en' => 'Package/module max base64 chunk size (KiB)', 'nl' => 'Maximale base64-chunkgrootte packages/modules (KiB)'),
|
||||
'app.save_configuration' => array('en' => 'Save configuration', 'nl' => 'Configuratie opslaan'),
|
||||
'app.configuration_saved' => array('en' => 'Configuration saved.', 'nl' => 'Configuratie opgeslagen.'),
|
||||
'app.racket_parts_regenerated' => array('en' => 'Racket installation parts regenerated:', 'nl' => 'Racket-installatie parts opnieuw gemaakt:'),
|
||||
'app.chunk_size_hint_v2' => array(
|
||||
'en' => 'Values are maximum base64 payload sizes in KiB. A 6144 KiB binary chunk becomes 8192 KiB base64. Racket installation parts are regenerated when this configuration is saved.',
|
||||
'nl' => 'Waarden zijn maximale base64-payloadgroottes in KiB. Een binaire chunk van 6144 KiB wordt 8192 KiB base64. Racket-installatie parts worden opnieuw gemaakt wanneer deze configuratie wordt opgeslagen.',
|
||||
),
|
||||
'app.effective_binary_chunk' => array('en' => 'Effective binary chunk', 'nl' => 'Effectieve binaire chunk'),
|
||||
'app.racket_zip_source' => array('en' => 'Racket installation source', 'nl' => 'Bronbestand Racket-installatie'),
|
||||
'app.racket_parts' => array('en' => 'Racket installation parts', 'nl' => 'Racket-installatie parts'),
|
||||
'app.racket_parts_current' => array('en' => 'current', 'nl' => 'actueel'),
|
||||
'app.racket_parts_missing' => array('en' => 'missing or outdated; save configuration to regenerate', 'nl' => 'ontbreken of verouderd; sla configuratie op om opnieuw te maken'),
|
||||
'app.expired_tokens_removed' => array('en' => 'Expired next tokens removed:', 'nl' => 'Verlopen next-tokens verwijderd:'),
|
||||
'app.cleanup_help' => array(
|
||||
'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.'
|
||||
),
|
||||
));
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = post_value('action');
|
||||
|
||||
try {
|
||||
if ($action === 'logout') {
|
||||
$auth->logout();
|
||||
header('Location: /login.php');
|
||||
exit;
|
||||
} elseif ($action === 'update_config') {
|
||||
save_base64_chunk_config(array(
|
||||
'racket_zip_max_base64_kb' => post_value('racket_zip_max_base64_kb'),
|
||||
'package_zip_max_base64_kb' => post_value('package_zip_max_base64_kb'),
|
||||
));
|
||||
$base64ChunkConfig = load_base64_chunk_config();
|
||||
$manifest = split_racket_zip_parts($base64ChunkConfig['racket_zip_max_base64_kb']);
|
||||
$message = t('app.configuration_saved', 'Configuration saved.') . ' ' .
|
||||
t('app.racket_parts_regenerated', 'Racket installation parts regenerated:') . ' ' .
|
||||
(int)$manifest['part_count'];
|
||||
} elseif ($action === 'cleanup_tokens') {
|
||||
$deleted = $tokens->cleanup();
|
||||
$message = t('app.expired_tokens_removed', 'Expired next tokens removed:') . ' ' . $deleted;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$error = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
$racketPartsManifest = null;
|
||||
$racketPartsCurrent = false;
|
||||
|
||||
try {
|
||||
$racketPartsManifest = load_racket_zip_parts_manifest();
|
||||
$racketPartsCurrent = racket_zip_parts_current(
|
||||
$racketPartsManifest,
|
||||
$base64ChunkConfig['racket_zip_max_base64_kb']
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
$racketPartsManifest = null;
|
||||
$racketPartsCurrent = false;
|
||||
}
|
||||
|
||||
if (!$racketPartsCurrent && is_file(RACKET_ZIP_FILE) && is_readable(RACKET_ZIP_FILE)) {
|
||||
try {
|
||||
$racketPartsManifest = split_racket_zip_parts($base64ChunkConfig['racket_zip_max_base64_kb']);
|
||||
$racketPartsCurrent = true;
|
||||
|
||||
if ($message === '') {
|
||||
$message = t('app.racket_parts_regenerated', 'Racket installation parts regenerated:') . ' ' .
|
||||
(int)$racketPartsManifest['part_count'];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
if ($error === '') {
|
||||
$error = $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$headerLanguages = array();
|
||||
|
||||
$currentNextTokens = array();
|
||||
|
||||
try {
|
||||
$currentNextTokens = $tokens->tokens();
|
||||
} catch (Throwable $e) {
|
||||
if ($error === '') {
|
||||
$error = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($languageStore->supportedLanguages() as $lang) {
|
||||
$headerLanguages[$lang] = $languageStore->languageLabel($lang);
|
||||
}
|
||||
|
||||
$styleVersion = @filemtime(__DIR__ . '/styles.css') ?: time();
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="<?= h($language) ?>">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><?= h(t('app.configuration', 'Configuration')) ?></title>
|
||||
<link rel="stylesheet" href="/styles.css?v=<?= h($styleVersion) ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page">
|
||||
|
||||
<?php
|
||||
render_app_header(array(
|
||||
'title' => t('app.configuration', 'Configuration'),
|
||||
'nav_items' => array(
|
||||
array('label' => t('app.back_to_sandbox', 'Back to Racket sandbox'), 'url' => '/?lang=' . rawurlencode($language)),
|
||||
array(
|
||||
'label' => t('app.manage_prompts', 'Manage prompts'),
|
||||
'url' => '/prompts?lang=' . rawurlencode($language),
|
||||
'separator_before' => true,
|
||||
),
|
||||
array(
|
||||
'label' => t('app.user_management', 'User management'),
|
||||
'url' => '/users?lang=' . rawurlencode($language),
|
||||
'separator_before' => true,
|
||||
),
|
||||
array(
|
||||
'label' => t('app.configuration', 'Configuration'),
|
||||
'url' => '/admin-config?lang=' . rawurlencode($language),
|
||||
'active' => true,
|
||||
'separator_before' => true,
|
||||
),
|
||||
),
|
||||
'user' => $currentUser,
|
||||
'user_prefix' => t('app.logged_in_as', 'Logged in as:'),
|
||||
'admin_label' => t('app.admin', 'Admin'),
|
||||
'language_label' => t('app.language', 'Language'),
|
||||
'language' => $language,
|
||||
'languages' => $headerLanguages,
|
||||
'language_action' => '/admin-config',
|
||||
'logout_action' => '/admin-config?lang=' . rawurlencode($language),
|
||||
'logout_label' => t('app.logout', 'Logout'),
|
||||
'message' => $message,
|
||||
'error' => $error,
|
||||
));
|
||||
?>
|
||||
|
||||
<main class="page-main dashboard-main">
|
||||
|
||||
<section class="panel">
|
||||
<h2><?= h(t('app.download_settings', 'Download settings')) ?></h2>
|
||||
|
||||
<form method="post" action="/admin-config?lang=<?= h($language) ?>" class="admin-form-grid">
|
||||
<input type="hidden" name="action" value="update_config">
|
||||
<label>
|
||||
<?= h(t('app.racket_zip_chunk_kb', 'Racket installation max base64 chunk size (KiB)')) ?><br>
|
||||
<input type="number" name="racket_zip_max_base64_kb" min="1" step="1" value="<?= h($base64ChunkConfig['racket_zip_max_base64_kb']) ?>" required>
|
||||
</label>
|
||||
<label>
|
||||
<?= h(t('app.package_zip_chunk_kb', 'Package/module max base64 chunk size (KiB)')) ?><br>
|
||||
<input type="number" name="package_zip_max_base64_kb" min="1" step="1" value="<?= h($base64ChunkConfig['package_zip_max_base64_kb']) ?>" required>
|
||||
</label>
|
||||
<button type="submit"><?= h(t('app.save_configuration', 'Save configuration')) ?></button>
|
||||
</form>
|
||||
|
||||
<p class="small"><?= h(t('app.chunk_size_hint_v2', 'Values are maximum base64 payload sizes in KiB. A 6144 KiB binary chunk becomes 8192 KiB base64. Racket installation parts are regenerated when this configuration is saved.')) ?></p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th><?= h(t('app.racket_zip_chunk_kb', 'Racket installation max base64 chunk size (KiB)')) ?></th>
|
||||
<td><code><?= h($base64ChunkConfig['racket_zip_max_base64_kb']) ?></code> KiB</td>
|
||||
<td><?= h(t('app.effective_binary_chunk', 'Effective binary chunk')) ?>: <code><?= h(base64_binary_chunk_bytes_for_kib($base64ChunkConfig['racket_zip_max_base64_kb'])) ?></code> bytes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?= h(t('app.package_zip_chunk_kb', 'Package/module max base64 chunk size (KiB)')) ?></th>
|
||||
<td><code><?= h($base64ChunkConfig['package_zip_max_base64_kb']) ?></code> KiB</td>
|
||||
<td><?= h(t('app.effective_binary_chunk', 'Effective binary chunk')) ?>: <code><?= h(base64_binary_chunk_bytes_for_kib($base64ChunkConfig['package_zip_max_base64_kb'])) ?></code> bytes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?= h(t('app.racket_zip_source', 'Racket installation source')) ?></th>
|
||||
<td><code>config/racket.zip</code></td>
|
||||
<td><code><?= h(is_file(RACKET_ZIP_FILE) ? (string)filesize(RACKET_ZIP_FILE) : '0') ?></code> bytes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?= h(t('app.racket_parts', 'Racket installation parts')) ?></th>
|
||||
<td><code><?= h((string)($racketPartsManifest['part_count'] ?? 0)) ?></code></td>
|
||||
<td>
|
||||
<?php if ($racketPartsCurrent): ?>
|
||||
<?= h(t('app.racket_parts_current', 'current')) ?>,
|
||||
<?= h((string)($racketPartsManifest['created_at'] ?? '')) ?>
|
||||
<?php else: ?>
|
||||
<?= h(t('app.racket_parts_missing', 'missing or outdated; save configuration to regenerate')) ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h2><?= h(t('app.maintenance', 'Maintenance')) ?></h2>
|
||||
|
||||
<fieldset>
|
||||
<legend><?= h(t('app.next_tokens', 'Next tokens')) ?></legend>
|
||||
|
||||
<form method="post" action="/admin-config?lang=<?= h($language) ?>">
|
||||
<input type="hidden" name="action" value="cleanup_tokens">
|
||||
<button type="submit"><?= h(t('app.remove_expired_tokens', 'Remove expired next tokens')) ?></button>
|
||||
</form>
|
||||
|
||||
<p class="small">
|
||||
<?= h(t('app.cleanup_help', 'Expired links should return an outdated information message to the AI agent. Cleanup only removes old token rows from SQLite.')) ?>
|
||||
</p>
|
||||
|
||||
<h3><?= h(t('app.current_next_tokens', 'Current next tokens')) ?></h3>
|
||||
|
||||
<?php if (count($currentNextTokens) > 0): ?>
|
||||
<table>
|
||||
<tr>
|
||||
<th><?= h(t('app.token', 'Token')) ?></th>
|
||||
<th><?= h(t('app.created_at', 'Created at')) ?></th>
|
||||
<th><?= h(t('app.expires_at', 'Expires at')) ?></th>
|
||||
</tr>
|
||||
<?php foreach ($currentNextTokens as $nextToken): ?>
|
||||
<tr class="<?= h(token_status_class($nextToken)) ?>">
|
||||
<td><code><?= h($nextToken['token'] ?? '') ?></code></td>
|
||||
<td><?= h(format_token_time($nextToken['created_at'] ?? 0)) ?></td>
|
||||
<td><?= h(format_token_time($nextToken['expires_at'] ?? 0)) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<p class="small"><?= h(t('app.no_current_next_tokens', 'No current next tokens.')) ?></p>
|
||||
<?php endif; ?>
|
||||
</fieldset>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user