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