mirror of
https://github.com/ThunixdotNet/www.git
synced 2026-01-24 07:10:18 +00:00
added terminal interface as default. old website as no-js fallback
This commit is contained in:
27
terminal/api/_bootstrap.php
Normal file
27
terminal/api/_bootstrap.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
|
||||
$rootDir = dirname(__DIR__, 2);
|
||||
if (!is_dir($rootDir)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Invalid root directory'], JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
|
||||
function json_out(array $data, int $status = 200): void
|
||||
{
|
||||
http_response_code($status);
|
||||
try {
|
||||
echo json_encode(
|
||||
$data,
|
||||
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo '{"error":"JSON encoding failed"}';
|
||||
}
|
||||
exit;
|
||||
}
|
||||
58
terminal/api/menu.php
Normal file
58
terminal/api/menu.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/terminal/_bootstrap.php';
|
||||
|
||||
$file = $rootDir . '/includes/sidebar.md';
|
||||
if (!is_file($file)) {
|
||||
json_out(['sections' => []]);
|
||||
}
|
||||
|
||||
$md = file_get_contents($file);
|
||||
if ($md === false) {
|
||||
json_out(['sections' => []]);
|
||||
}
|
||||
|
||||
$lines = preg_split('/\r\n|\n|\r/', $md) ?: [];
|
||||
$sections = [];
|
||||
|
||||
$current = null;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$raw = rtrim($line);
|
||||
|
||||
// Section header: "- Title"
|
||||
if (preg_match('/^\-\s{2,}(.+)$/', $raw, $m)) {
|
||||
if ($current !== null) {
|
||||
$sections[] = $current;
|
||||
}
|
||||
$current = [
|
||||
'title' => trim($m[1]),
|
||||
'items' => [],
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Menu item: " - [Text](Href)"
|
||||
if ($current !== null && preg_match('/^\s+\-\s{2,}\[(.+?)\]\((.+?)\)\s*$/', $raw, $m)) {
|
||||
$text = trim($m[1]);
|
||||
$href = trim($m[2]);
|
||||
|
||||
$internal = str_starts_with($href, '/');
|
||||
$slug = $internal ? ltrim(parse_url($href, PHP_URL_PATH) ?? '', '/') : '';
|
||||
|
||||
$current['items'][] = [
|
||||
'text' => $text,
|
||||
'href' => $href,
|
||||
'internal' => $internal,
|
||||
'slug' => $slug,
|
||||
];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($current !== null) {
|
||||
$sections[] = $current;
|
||||
}
|
||||
|
||||
json_out(['sections' => $sections]);
|
||||
53
terminal/api/page.php
Normal file
53
terminal/api/page.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/_bootstrap.php';
|
||||
|
||||
$p = $_GET['p'] ?? '';
|
||||
if (!is_string($p)) {
|
||||
json_out(['error' => 'Invalid page'], 400);
|
||||
}
|
||||
|
||||
$slug = trim($p);
|
||||
$slug = ltrim($slug, '/');
|
||||
$slug = preg_replace('/\?.*$/', '', $slug) ?? $slug;
|
||||
|
||||
if ($slug === '' || !preg_match('/^[A-Za-z0-9][A-Za-z0-9_-]*$/', $slug)) {
|
||||
json_out(['error' => 'Invalid page'], 400);
|
||||
}
|
||||
|
||||
if (preg_match('/^success\d+$/', $slug) === 1) {
|
||||
json_out(['error' => 'Not found'], 404);
|
||||
}
|
||||
|
||||
$file = $rootDir . '/articles/' . $slug . '.md';
|
||||
if (!is_file($file)) {
|
||||
json_out(['error' => 'Not found'], 404);
|
||||
}
|
||||
|
||||
$md = file_get_contents($file);
|
||||
if ($md === false) {
|
||||
json_out(['error' => 'Failed to read page'], 500);
|
||||
}
|
||||
|
||||
$parsedownPath = $rootDir . '/parsedown-1.7.3/Parsedown.php';
|
||||
$extraPath = $rootDir . '/parsedown-extra-0.7.1/ParsedownExtra.php';
|
||||
|
||||
if (is_file($parsedownPath)) {
|
||||
require_once $parsedownPath;
|
||||
}
|
||||
if (is_file($extraPath)) {
|
||||
require_once $extraPath;
|
||||
}
|
||||
|
||||
$html = '';
|
||||
if (class_exists('ParsedownExtra')) {
|
||||
$pd = new ParsedownExtra();
|
||||
$html = $pd->text($md);
|
||||
}
|
||||
|
||||
json_out([
|
||||
'slug' => $slug,
|
||||
'markdown' => $md,
|
||||
'html' => $html,
|
||||
]);
|
||||
51
terminal/api/pages.php
Normal file
51
terminal/api/pages.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/_bootstrap.php';
|
||||
|
||||
$articlesDir = $rootDir . '/articles';
|
||||
if (!is_dir($articlesDir)) {
|
||||
json_out(['pages' => []]);
|
||||
}
|
||||
|
||||
$pages = [];
|
||||
$files = glob($articlesDir . '/*.md') ?: [];
|
||||
sort($files);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$slug = basename($file, '.md');
|
||||
|
||||
if (preg_match('/^success\d+$/', $slug) === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$title = $slug;
|
||||
|
||||
$fh = fopen($file, 'rb');
|
||||
if ($fh !== false) {
|
||||
for ($i = 0; $i < 20; $i++) {
|
||||
$line = fgets($fh);
|
||||
if ($line === false) {
|
||||
break;
|
||||
}
|
||||
$line = trim($line);
|
||||
if ($line === '') {
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^#\s+(.+)$/', $line, $m)) {
|
||||
$title = trim($m[1]);
|
||||
break;
|
||||
}
|
||||
$title = mb_substr($line, 0, 80);
|
||||
break;
|
||||
}
|
||||
fclose($fh);
|
||||
}
|
||||
|
||||
$pages[] = [
|
||||
'slug' => $slug,
|
||||
'title' => $title,
|
||||
];
|
||||
}
|
||||
|
||||
json_out(['pages' => $pages]);
|
||||
49
terminal/api/server.php
Normal file
49
terminal/api/server.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/_bootstrap.php';
|
||||
|
||||
$candidates = [
|
||||
$rootDir . '/report',
|
||||
$rootDir . '/includes/report',
|
||||
];
|
||||
|
||||
$report = null;
|
||||
foreach ($candidates as $cand) {
|
||||
if (is_file($cand)) {
|
||||
$report = $cand;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($report === null) {
|
||||
json_out(['rows' => [], 'lastUpdated' => null]);
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
$fh = fopen($report, 'rb');
|
||||
if ($fh === false) {
|
||||
json_out(['rows' => [], 'lastUpdated' => null]);
|
||||
}
|
||||
|
||||
while (($line = fgets($fh)) !== false) {
|
||||
$line = trim($line);
|
||||
if ($line === '') {
|
||||
continue;
|
||||
}
|
||||
$parts = str_getcsv($line);
|
||||
if (count($parts) < 3) {
|
||||
continue;
|
||||
}
|
||||
$rows[] = [
|
||||
'host' => (string)$parts[0],
|
||||
'check' => (string)$parts[1],
|
||||
'status' => (string)$parts[2],
|
||||
];
|
||||
}
|
||||
fclose($fh);
|
||||
|
||||
$mtime = @filemtime($report);
|
||||
$last = $mtime ? gmdate('c', (int)$mtime) : null;
|
||||
|
||||
json_out(['rows' => $rows, 'lastUpdated' => $last]);
|
||||
42
terminal/api/users.php
Normal file
42
terminal/api/users.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/_bootstrap.php';
|
||||
|
||||
$siteRoot = '//' . ($_SERVER['HTTP_HOST'] ?? 'localhost');
|
||||
|
||||
$skelIndex = '/etc/skel/public_html/index.html';
|
||||
$skelIndexCksum = is_file($skelIndex) ? @md5_file($skelIndex) : null;
|
||||
|
||||
$users = [];
|
||||
$homes = glob('/home/*', GLOB_ONLYDIR) ?: [];
|
||||
|
||||
foreach ($homes as $homeDir) {
|
||||
$user = basename($homeDir);
|
||||
if ($user === '' || $user === 'lost+found') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$userIndex = $homeDir . '/public_html/index.html';
|
||||
$userPub = $homeDir . '/public_html';
|
||||
|
||||
if (!is_dir($userPub)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hasCustomIndex = false;
|
||||
if (is_file($userIndex) && $skelIndexCksum !== null) {
|
||||
$userCksum = @md5_file($userIndex);
|
||||
$hasCustomIndex = ($userCksum !== false && $userCksum !== $skelIndexCksum);
|
||||
} elseif (is_file($userIndex)) {
|
||||
$hasCustomIndex = true;
|
||||
}
|
||||
|
||||
$users[] = [
|
||||
'username' => $user,
|
||||
'url' => $siteRoot . '/~' . rawurlencode($user) . '/',
|
||||
'hasContent' => $hasCustomIndex,
|
||||
];
|
||||
}
|
||||
|
||||
json_out(['users' => $users]);
|
||||
Reference in New Issue
Block a user