port to Astro
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I6e1c163a147b14b92a5f6ae4de6a87206a6a6964
This commit is contained in:
parent
69e9d63be7
commit
f25cfa3e7e
14 changed files with 5125 additions and 200 deletions
BIN
src/assets/fonts/FiraCode-Regular.woff2
Normal file
BIN
src/assets/fonts/FiraCode-Regular.woff2
Normal file
Binary file not shown.
33
src/components/MemberList.astro
Normal file
33
src/components/MemberList.astro
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
|
||||
interface Props {
|
||||
members: string[];
|
||||
}
|
||||
|
||||
const { members } = Astro.props;
|
||||
|
||||
const owner = "amr";
|
||||
const admins = ["raf", "rocoe"];
|
||||
---
|
||||
|
||||
<div class="section">
|
||||
<p>Members</p>
|
||||
<ul class="members">
|
||||
{members.length > 0 ? (
|
||||
members.map(username => (
|
||||
<li>
|
||||
<a href={`/~${username}`}>~{username}</a>
|
||||
{username === owner && (
|
||||
<Icon name="fa-solid:crown" class="member-icon owner" title="Owner" />
|
||||
)}
|
||||
{admins.includes(username) && username !== owner && (
|
||||
<Icon name="fa-solid:shield-halved" class="member-icon admin" title="Admin" />
|
||||
)}
|
||||
</li>
|
||||
))
|
||||
) : (
|
||||
<li class="error">Error fetching members!</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
32
src/components/ServicesTable.astro
Normal file
32
src/components/ServicesTable.astro
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
interface Service {
|
||||
url: string;
|
||||
name: string;
|
||||
desc: string;
|
||||
requiresMember: boolean;
|
||||
special?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
services: Service[];
|
||||
}
|
||||
|
||||
const { services } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="section">
|
||||
<p>Services</p>
|
||||
<table class="services">
|
||||
{services.map(service => (
|
||||
<tr>
|
||||
<td>{service.url ? <a href={service.url}>{service.special || service.url}</a> : service.special}</td>
|
||||
<td>{service.name}</td>
|
||||
<td>{service.desc}</td>
|
||||
</tr>
|
||||
))}
|
||||
</table>
|
||||
<small>
|
||||
<sup>1</sup> Only available for existing members<br>
|
||||
<sup>2</sup> Not running 24/7
|
||||
</small>
|
||||
</div>
|
||||
21
src/components/SystemInfo.astro
Normal file
21
src/components/SystemInfo.astro
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
interface Props {
|
||||
currentDate: string;
|
||||
}
|
||||
|
||||
const { currentDate } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="section">
|
||||
<p>System Info</p>
|
||||
<table class="system-info">
|
||||
<tr>
|
||||
<td>time:</td>
|
||||
<td>{currentDate}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>os:</td>
|
||||
<td>Ubuntu GNU/Linux 24.04 (LTS)</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
12
src/components/TorNotice.astro
Normal file
12
src/components/TorNotice.astro
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
|
||||
---
|
||||
|
||||
<div class="section tor-notice">
|
||||
<pre>
|
||||
+----------------------------------------------------------------+
|
||||
| This site is accessible via Tor! |
|
||||
| <a href="http://frzndev32nhnla77oozhxhz5yzo4abldr6zbc4qkdh5hcyanizlxs2ad.onion/">frzndev32nhnla77oozhxhz5yzo4abldr6zbc4qkdh5hcyanizlxs2ad.onion</a> |
|
||||
+----------------------------------------------------------------+
|
||||
</pre>
|
||||
</div>
|
||||
45
src/layouts/Layout.astro
Normal file
45
src/layouts/Layout.astro
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
import { Font } from "astro:assets";
|
||||
import "@/styles/global.css";
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
currentYear?: number;
|
||||
}
|
||||
|
||||
const {
|
||||
title = "frzn.dev",
|
||||
currentYear = new Date().getFullYear()
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>{title}</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<Font cssVariable="--font-fira-code" preload />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-container">
|
||||
<div class="header">
|
||||
<pre>
|
||||
_______ _____
|
||||
| ___|.----.-----.-----.-----.-----.| \.-----.--.--.
|
||||
| ___|| _| _ |-- __| -__| || -- | -__| | |
|
||||
|___| |__| |_____|_____|_____|__|__||_____/|_____|\___/</pre>
|
||||
</div>
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
<div class="footer">
|
||||
<hr>
|
||||
(c) frzn.dev 2018 - {currentYear} / design and "backend" by <a href="/~roscoe">~roscoe</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
79
src/pages/index.astro
Normal file
79
src/pages/index.astro
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import TorNotice from '../components/TorNotice.astro';
|
||||
import MemberList from '../components/MemberList.astro';
|
||||
import ServicesTable from '../components/ServicesTable.astro';
|
||||
import SystemInfo from '../components/SystemInfo.astro';
|
||||
import { readdirSync, statSync } from "node:fs";
|
||||
|
||||
function getUsers(): string[] {
|
||||
const usernames: string[] = [];
|
||||
|
||||
let users;
|
||||
try {
|
||||
users = readdirSync("/home", { withFileTypes: true });
|
||||
} catch {
|
||||
return usernames;
|
||||
}
|
||||
|
||||
for (const user of users) {
|
||||
if (user.isDirectory()) {
|
||||
try {
|
||||
statSync(`/home/${user.name}/public_html`);
|
||||
usernames.push(user.name);
|
||||
} catch {
|
||||
// public_html doesn't exist
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return usernames;
|
||||
}
|
||||
|
||||
const users = getUsers();
|
||||
|
||||
// Services data
|
||||
const services = [
|
||||
{ url: "https://git.frzn.dev", name: "Forgejo", desc: "A lightweight git server (which is better than Gogs)", requiresMember: true },
|
||||
{ url: "https://p.frzn.dev", name: "fiche", desc: "A command line pastebin (similar to termbin)", requiresMember: false },
|
||||
{ url: "https://pb.frzn.dev", name: "PrivateBin", desc: "A minimalist, open source online pastebin", requiresMember: false },
|
||||
{ url: "https://bitwarden.frzn.dev/", name: "Vaultwarden", desc: "A Bitwarden-compatible server written in Rust", requiresMember: true },
|
||||
{ url: "https://snowflake.torproject.org/", name: "", desc: "A web proxy server", requiresMember: false },
|
||||
{ url: "https://crypt.frzn.dev", name: "CryptPad", desc: "An open-source web-based encrypted suite of realtime collaborative editors", requiresMember: false },
|
||||
{ url: "", name: "Mumble", desc: "A VoIP server", requiresMember: true, special: "frzn.dev:64738 (Not running 24/7)" },
|
||||
{ url: "https://irc.frozenelectronics.ca:8088/", name: "ZNC", desc: "An IRC bouncer", requiresMember: true, special: "frzn.dev:6697/6667" },
|
||||
];
|
||||
|
||||
function formatDate(date: Date): string {
|
||||
const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||||
const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||
|
||||
const dayName = days[date.getUTCDay()];
|
||||
const day = date.getUTCDate();
|
||||
const monthName = months[date.getUTCMonth()];
|
||||
const year = date.getUTCFullYear();
|
||||
|
||||
let hours = date.getUTCHours();
|
||||
const ampm = hours >= 12 ? "PM" : "AM";
|
||||
hours = hours % 12 || 12;
|
||||
|
||||
const minutes = date.getUTCMinutes().toString().padStart(2, "0");
|
||||
const seconds = date.getUTCSeconds().toString().padStart(2, "0");
|
||||
|
||||
return `${dayName}, ${day} ${monthName} ${year} ${hours}:${minutes}:${seconds} ${ampm} (UTC)`;
|
||||
}
|
||||
|
||||
const currentDate = formatDate(new Date());
|
||||
const currentYear = new Date().getFullYear();
|
||||
---
|
||||
|
||||
<Layout title="frzn.dev" currentYear={currentYear}>
|
||||
<div>
|
||||
<TorNotice />
|
||||
<MemberList members={users} />
|
||||
<ServicesTable services={services} />
|
||||
</div>
|
||||
<div>
|
||||
<SystemInfo currentDate={currentDate} />
|
||||
</div>
|
||||
</Layout>
|
||||
189
src/styles/global.css
Normal file
189
src/styles/global.css
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
:root {
|
||||
--background: #11111b;
|
||||
--foreground: #cdd6f4;
|
||||
--links: #89b4fa;
|
||||
--font-fira-code: 'Fira Code', monospace;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 98%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background);
|
||||
color: var(--foreground);
|
||||
margin: 20px 75px 20px 75px;
|
||||
line-height: 1.5em;
|
||||
font-size: 10pt;
|
||||
letter-spacing: -0.015em;
|
||||
min-width: 520px;
|
||||
}
|
||||
|
||||
body,
|
||||
pre {
|
||||
font-family: var(--font-fira-code);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--links);
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline solid;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin-left: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
padding-left: 1em;
|
||||
text-indent: -1em;
|
||||
}
|
||||
|
||||
li:before {
|
||||
content: '-';
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
p,
|
||||
ul,
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header,
|
||||
.section {
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
grid-template-columns: auto max-content;
|
||||
grid-template-rows: 1fr;
|
||||
grid-column-gap: 0px;
|
||||
grid-row-gap: 0px;
|
||||
}
|
||||
|
||||
.section {
|
||||
min-width: 20em;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.section:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
ul.members li {
|
||||
text-indent: 0;
|
||||
}
|
||||
|
||||
ul.members li:before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
ul.members li.error {
|
||||
color: lightcoral;
|
||||
}
|
||||
|
||||
ul.members li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
ul.members li a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.member-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.member-icon.owner {
|
||||
color: #f9e2af;
|
||||
}
|
||||
|
||||
.member-icon.admin {
|
||||
color: #a6e3a1;
|
||||
}
|
||||
|
||||
ul.sidebar-links li:before {
|
||||
content: '>';
|
||||
}
|
||||
|
||||
body > div {
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
body > div:nth-last-child(-n + 3) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table.services {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
table.services td {
|
||||
padding: 0 1em 0 0;
|
||||
}
|
||||
|
||||
table.services tr td:first-child {
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
table.services tr td:nth-child(2)::before {
|
||||
content: '- ';
|
||||
}
|
||||
|
||||
table.services tr td:last-child {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
table.system-info {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
table.system-info td {
|
||||
padding: 0 1em 0 0;
|
||||
}
|
||||
|
||||
table.system-info tr td:first-child {
|
||||
padding-right: 0.5em;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
min-height: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
}
|
||||
|
||||
.page-container > div {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.page-container > div.sidebar {
|
||||
border-left: 2px solid gray;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.footer hr {
|
||||
margin-top: 0;
|
||||
border: none;
|
||||
border-top: 2px solid gray;
|
||||
}
|
||||
|
||||
small {
|
||||
line-height: 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue