feat: initial test static site with Deno backend
This commit is contained in:
@@ -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")
|
||||
@@ -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" },
|
||||
});
|
||||
}
|
||||
@@ -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",
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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) · Powered by Docker Watcher</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+131
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user