feat: initial test static site with Deno backend

This commit is contained in:
2026-04-11 02:35:04 +03:00
commit 5567158bbe
7 changed files with 302 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
# Test Static Site
Test repository for Docker Watcher's Static Sites feature.
## Structure
```
pages/
├── index.html # Main page
├── style.css # Styles
├── app.js # Client-side JS
└── api/
├── time.ts # GET /api/time — server time
├── hello.ts # GET /api/hello?name=X — greeting with env var
└── echo.ts # POST /api/echo — echo request body
```
## API Convention
Functions prefixed with `API_` are auto-routed:
- `API_get` in `api/time.ts``GET /api/time`
- `API_post` in `api/echo.ts``POST /api/echo`
## Environment Variables
- `GREETING_PREFIX` — custom greeting prefix (default: "Hello")
+30
View File
@@ -0,0 +1,30 @@
// POST /api/echo — echoes back the request body with server metadata.
export async function API_post(req: Request): Promise<Response> {
let body: unknown = null;
try {
body = await req.json();
} catch {
return Response.json(
{ error: "Invalid JSON body" },
{ status: 400 },
);
}
return Response.json({
echo: body,
server: {
time: new Date().toISOString(),
method: req.method,
headers: Object.fromEntries(req.headers.entries()),
},
});
}
// GET /api/echo — returns usage instructions.
export async function API_get(req: Request): Promise<Response> {
return Response.json({
usage: "POST a JSON body to this endpoint to see it echoed back.",
example: { text: "hello", timestamp: "2026-04-11T00:00:00Z" },
});
}
+14
View File
@@ -0,0 +1,14 @@
// GET /api/hello — returns a greeting.
// Demonstrates reading query parameters and using env vars.
export async function API_get(req: Request): Promise<Response> {
const url = new URL(req.url);
const name = url.searchParams.get("name") || "World";
// Example: reading a secret from environment (configured in Docker Watcher).
const prefix = Deno.env.get("GREETING_PREFIX") || "Hello";
return Response.json({
message: `${prefix}, ${name}!`,
served_by: "Deno on Docker Watcher",
});
}
+10
View File
@@ -0,0 +1,10 @@
// GET /api/time — returns the current server time.
export async function API_get(req: Request): Promise<Response> {
const now = new Date();
return Response.json({
time: now.toISOString(),
unix: Math.floor(now.getTime() / 1000),
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
});
}
+44
View File
@@ -0,0 +1,44 @@
async function fetchTime() {
const el = document.getElementById('server-time');
el.textContent = 'Loading...';
try {
const res = await fetch('/api/time');
const data = await res.json();
el.textContent = data.time;
} catch (e) {
el.textContent = 'Error: ' + e.message;
}
}
async function fetchGreeting() {
const name = document.getElementById('name-input').value || 'World';
const el = document.getElementById('greeting');
el.textContent = 'Loading...';
try {
const res = await fetch('/api/hello?name=' + encodeURIComponent(name));
const data = await res.json();
el.textContent = data.message;
} catch (e) {
el.textContent = 'Error: ' + e.message;
}
}
async function fetchEcho() {
const input = document.getElementById('echo-input').value;
const el = document.getElementById('echo-result');
el.textContent = 'Loading...';
try {
const res = await fetch('/api/echo', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: input, timestamp: new Date().toISOString() })
});
const data = await res.json();
el.textContent = JSON.stringify(data, null, 2);
} catch (e) {
el.textContent = 'Error: ' + e.message;
}
}
// Load server time on page load.
fetchTime();
+47
View File
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test Static Site</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>Docker Watcher Static Sites</h1>
<p class="subtitle">This page is served from a Gitea repository folder</p>
<div class="cards">
<div class="card">
<h2>Server Time</h2>
<p id="server-time" class="value">Loading...</p>
<button onclick="fetchTime()">Refresh</button>
</div>
<div class="card">
<h2>Greeting</h2>
<div class="input-group">
<input type="text" id="name-input" placeholder="Enter your name" value="World">
<button onclick="fetchGreeting()">Say Hello</button>
</div>
<p id="greeting" class="value"></p>
</div>
<div class="card">
<h2>Echo</h2>
<div class="input-group">
<input type="text" id="echo-input" placeholder="Type something...">
<button onclick="fetchEcho()">Send</button>
</div>
<pre id="echo-result" class="value"></pre>
</div>
</div>
<footer>
<p>Mode: <strong>Deno</strong> (Static + API) &middot; Powered by Docker Watcher</p>
</footer>
</div>
<script src="app.js"></script>
</body>
</html>
+131
View File
@@ -0,0 +1,131 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0f172a;
color: #e2e8f0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
max-width: 800px;
width: 100%;
padding: 2rem;
}
h1 {
font-size: 2rem;
background: linear-gradient(135deg, #818cf8, #c084fc);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 0.5rem;
}
.subtitle {
color: #94a3b8;
margin-bottom: 2rem;
}
.cards {
display: grid;
gap: 1.5rem;
grid-template-columns: 1fr;
}
@media (min-width: 600px) {
.cards {
grid-template-columns: 1fr 1fr;
}
.cards .card:last-child {
grid-column: 1 / -1;
}
}
.card {
background: #1e293b;
border: 1px solid #334155;
border-radius: 12px;
padding: 1.5rem;
}
.card h2 {
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #94a3b8;
margin-bottom: 1rem;
}
.value {
font-size: 1.125rem;
color: #f1f5f9;
margin-top: 0.75rem;
min-height: 1.5em;
}
pre.value {
font-size: 0.8rem;
background: #0f172a;
padding: 0.75rem;
border-radius: 8px;
overflow-x: auto;
white-space: pre-wrap;
}
.input-group {
display: flex;
gap: 0.5rem;
}
input {
flex: 1;
padding: 0.5rem 0.75rem;
border: 1px solid #475569;
border-radius: 8px;
background: #0f172a;
color: #e2e8f0;
font-size: 0.875rem;
}
input:focus {
outline: none;
border-color: #818cf8;
}
button {
padding: 0.5rem 1rem;
border: none;
border-radius: 8px;
background: #6366f1;
color: white;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: background 0.15s;
}
button:hover {
background: #4f46e5;
}
button:active {
background: #4338ca;
}
footer {
margin-top: 2rem;
text-align: center;
color: #64748b;
font-size: 0.8rem;
}
footer strong {
color: #a78bfa;
}