feat(frontend): add dedicated /cli page, nav links, and CTA buttons; remove CLI modals for leaner UX

- New CLI page at /cli with detailed usage and improved Quick Start card header
- Add CLI link to navbars and small ‘Try the CLI’ CTAs on Home & Templates
- Remove CLI modals and unused showCliModal() handler (keep_small_simple)
- Self-host Bootstrap and Font Awesome; add OSI logo and GPL notice in footers
- Dockerfile: verify vendor assets exist at build time
- Minor a11y/contrast and heading-order cleanups (100 a11y)
This commit is contained in:
William Valentin
2025-09-15 00:47:31 -07:00
parent 9caf95bb7a
commit a0ae5f869e
16 changed files with 589 additions and 48 deletions
+7
View File
@@ -28,6 +28,13 @@ WORKDIR /app
# Copy source code
COPY . .
# Verify self-hosted vendor assets exist (fail build if missing)
RUN test -f frontend/static/vendor/bootstrap/css/bootstrap.min.css \
&& test -f frontend/static/vendor/bootstrap/js/bootstrap.bundle.min.js \
&& test -f frontend/static/vendor/fontawesome/css/all.min.css \
&& test -f frontend/static/vendor/fontawesome/webfonts/fa-solid-900.woff2 \
&& test -f frontend/static/img/osi-logo.svg || (echo 'Missing vendor assets. Ensure static/vendor and images are committed.' && exit 1)
# Install dependencies with uv
RUN uv venv && \
uv pip install -e ".[web]"
+12
View File
@@ -11,6 +11,7 @@ from typing import Any, Dict, List, Optional
from fastapi import FastAPI, File, HTTPException, Request, UploadFile # type: ignore
from fastapi.middleware.cors import CORSMiddleware # type: ignore
from fastapi.middleware.gzip import GZipMiddleware # type: ignore
from fastapi.responses import FileResponse, HTMLResponse # type: ignore
from fastapi.staticfiles import StaticFiles # type: ignore
from fastapi.templating import Jinja2Templates # type: ignore
@@ -38,6 +39,9 @@ app.add_middleware(
allow_headers=["*"],
)
# Add GZip compression
app.add_middleware(GZipMiddleware, minimum_size=500)
# Setup templates and static files
BASE_DIR = Path(__file__).resolve().parent.parent.parent
TEMPLATES_DIR = BASE_DIR / settings.templates_dir
@@ -155,6 +159,14 @@ async def templates_page(request: Request):
context.update(settings.get_template_context())
return templates.TemplateResponse("templates.html", context)
# CLI Info Page
@app.get("/cli", response_class=HTMLResponse)
async def cli_page(request: Request):
"""Serve the CLI information page."""
context = {"request": request}
context.update(settings.get_template_context())
return templates.TemplateResponse("cli.html", context)
# API Routes
@app.post("/api/validate", response_model=ValidationResult)
+87 -5
View File
@@ -15,6 +15,17 @@
}
/* General Styles */
/* Navbar link contrast fix on primary bg */
.navbar-dark.bg-primary .navbar-nav .nav-link {
color: #ffffff;
font-weight: 600;
}
.navbar-dark.bg-primary .navbar-nav .nav-link:hover,
.navbar-dark.bg-primary .navbar-nav .nav-link:focus,
.navbar-dark.bg-primary .navbar-nav .nav-link.active {
color: #ffffff;
}
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
@@ -250,7 +261,7 @@ footer.bg-dark .text-muted {
.template-tag {
background: var(--light-color);
color: var(--secondary-color);
color: #495057;
padding: 0.125rem 0.5rem;
border-radius: 1rem;
font-size: 0.75rem;
@@ -508,16 +519,87 @@ footer.bg-dark .text-muted {
}
.form-control {
background-color: var(--bg-secondary);
background-color: #1f2937;
border-color: #4a5568;
color: #333;
color: #f7fafc;
}
.form-control::placeholder {
color: #cbd5e0;
opacity: 1;
}
.form-control:focus {
background-color: var(--bg-secondary);
background-color: #1f2937;
border-color: var(--primary-color);
color: #333;
color: #f7fafc;
}
/* Improve outline-secondary contrast on dark backgrounds */
.card.bg-dark .btn-outline-secondary,
.bg-dark .btn-outline-secondary,
.card .btn-outline-secondary {
color: #e2e8f0;
border-color: #e2e8f0;
}
.card.bg-dark .btn-outline-secondary:hover,
.bg-dark .btn-outline-secondary:hover,
.card .btn-outline-secondary:hover {
color: #1a202c;
background-color: #e2e8f0;
border-color: #e2e8f0;
}
}
/* Increase link and outline-primary contrast on dark backgrounds */
@media (prefers-color-scheme: dark) {
.card.bg-dark a,
.bg-dark a {
color: #93c5fd;
}
.card.bg-dark a:hover,
.bg-dark a:hover {
color: #bfdbfe;
}
.card.bg-dark .btn-outline-primary,
.bg-dark .btn-outline-primary,
.card .btn-outline-primary {
color: #e2e8f0;
border-color: #93c5fd;
}
.card.bg-dark .btn-outline-primary:hover,
.bg-dark .btn-outline-primary:hover,
.card .btn-outline-primary:hover {
color: #1a202c;
background-color: #93c5fd;
border-color: #93c5fd;
}
/* High-contrast link utility */
.link-contrast {
color: #93c5fd !important;
}
.link-contrast:hover,
.link-contrast:focus {
color: #bfdbfe !important;
text-decoration: underline;
}
}
/* Footer OSI logo */
footer .badge.bg-success {
position: relative;
padding-left: 1.5rem;
}
footer .badge.bg-success::before {
content: "";
position: absolute;
left: 0.35rem;
top: 50%;
transform: translateY(-50%);
width: 1rem;
height: 1rem;
background: url('/static/img/osi-logo.svg') no-repeat center / contain;
}
/* Print Styles */
+34
View File
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="128"
height="128"
viewBox="0 0 128 128"
role="img"
aria-labelledby="title desc">
<title id="title">Open Source Initiative (OSI) Logo</title>
<desc id="desc">Green circular mark with a keyhole-shaped cutout representing the Open Source Initiative.</desc>
<!--
This is a simplified vector rendition of the OSI "Open Source" mark.
The OSI logo is a trademark of the Open Source Initiative.
See https://opensource.org/trademark for usage guidelines.
-->
<defs>
<mask id="osi-cutout" maskUnits="userSpaceOnUse">
<!-- Start fully transparent -->
<rect x="0" y="0" width="128" height="128" fill="black"/>
<!-- Keep the outer circle area -->
<circle cx="64" cy="64" r="60" fill="white"/>
<!-- Cut out inner circle (keyhole top) -->
<circle cx="64" cy="64" r="26" fill="black"/>
<!-- Cut out the keyhole stem; rounded for a smooth shape -->
<rect x="53" y="86" width="22" height="30" rx="6" ry="6" fill="black"/>
</mask>
</defs>
<!-- Apply the mask to a solid green rectangle -->
<rect x="0" y="0" width="128" height="128" fill="#3DA639" mask="url(#osi-cutout)"/>
<!-- Optional subtle outline for crisp edges on various backgrounds -->
<circle cx="64" cy="64" r="60" fill="none" stroke="#2a7a2a" stroke-width="1" opacity="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+1 -4
View File
@@ -337,10 +337,7 @@ function showUploadModal() {
modal.show();
}
function showCliModal() {
const modal = new bootstrap.Modal(document.getElementById('cliModal'));
modal.show();
}
async function validateFile() {
const fileInput = document.getElementById('fileInput');
+1 -1
View File
@@ -107,7 +107,7 @@ class TemplatesBrowser {
<div class="card-header">
<div class="d-flex justify-content-between align-items-start">
<div>
<h6 class="card-title mb-1">${this.escapeHtml(template.name)}</h6>
<p class="card-title h6 mb-1">${this.escapeHtml(template.name)}</p>
<small class="opacity-75">
<i class="fas fa-${this.getUnitTypeIcon(template.unit_type)} me-1"></i>
${template.unit_type}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
+322
View File
@@ -0,0 +1,322 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CLI - {{ app_name }}</title>
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg" />
<link rel="icon" type="image/svg+xml" sizes="16x16" href="/static/img/favicon-16x16.svg" />
<link rel="alternate icon" href="/static/img/favicon.svg" />
<link rel="apple-touch-icon" href="/static/img/icon-192x192.svg" />
<link rel="manifest" href="/static/img/site.webmanifest" />
<meta name="theme-color" content="#0d6efd" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="{{ app_name }}" />
<meta name="description" content="{{ app_name }} CLI — command-line interface to create, validate, and manage systemd unit files." />
<link href="/static/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
<link rel="stylesheet" href="/static/vendor/fontawesome/css/all.min.css" />
<link href="/static/css/style.css" rel="stylesheet" />
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">
<i class="fas fa-cogs me-2"></i>{{ app_name }}
</a>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/editor">Editor</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/templates">Templates</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/cli">CLI</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{{ api_docs_url }}" target="_blank">
<i class="fas fa-book me-1"></i>API Docs
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ github_url }}" target="_blank">
<i class="fab fa-github me-1"></i>GitHub
</a>
</li>
</ul>
</div>
</div>
</nav>
<header class="hero-section bg-light py-5">
<div class="container">
<div class="row align-items-center g-4">
<div class="col-lg-7">
<h1 class="display-5 fw-bold mb-3">
<i class="fas fa-terminal me-2"></i>{{ app_name }} CLI
</h1>
<p class="lead mb-4">
A powerful command-line interface to create, validate, and manage systemd unit files.
The CLI ships with the backend sub-project and uses the same reliable core as the web app.
</p>
<div class="d-flex gap-3">
<a href="{{ github_url }}" target="_blank" class="btn btn-primary">
<i class="fab fa-github me-2"></i>View on GitHub
</a>
<a href="#install" class="btn btn-outline-primary">
<i class="fas fa-download me-2"></i>Installation
</a>
</div>
</div>
<div class="col-lg-5">
<div class="card border-0 shadow-sm code-preview">
<div class="card-header text-white" style="background: linear-gradient(135deg, var(--primary-color), #0b5ed7);">
<div class="d-flex align-items-center justify-content-between">
<div>
<i class="fas fa-rocket me-2"></i>
<strong>Quick Start</strong>
</div>
<span class="badge bg-light text-primary">CLI</span>
</div>
</div>
<div class="card-body">
<pre class="mb-0"><code># From the repository root
pip install -e .
# Show help
unitforge --help
# Validate a unit file
unitforge validate /etc/systemd/system/myapp.service -v -w
</code></pre>
</div>
</div>
</div>
</div>
</div>
</header>
<main class="container my-5">
<section id="install" class="mb-5">
<h2 class="h3 mb-3">Installation</h2>
<div class="row g-4">
<div class="col-md-6">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body">
<h3 class="h5">From repository (editable)</h3>
<p class="text-muted mb-3">
Install directly from the repository so changes are picked up automatically.
</p>
<pre class="bg-dark text-light p-3 rounded"><code>pip install -e .</code></pre>
<button class="btn btn-sm btn-outline-light mt-2" data-copy="pip install -e .">
<i class="fas fa-copy me-1"></i>Copy
</button>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body">
<h3 class="h5">Verify installation</h3>
<p class="text-muted mb-3">
Use the built-in version and help commands to confirm the CLI is available.
</p>
<pre class="bg-dark text-light p-3 rounded"><code>unitforge --version
unitforge --help</code></pre>
<div class="d-flex gap-2 mt-2">
<button class="btn btn-sm btn-outline-light" data-copy="unitforge --version">
<i class="fas fa-copy me-1"></i>Copy version
</button>
<button class="btn btn-sm btn-outline-light" data-copy="unitforge --help">
<i class="fas fa-copy me-1"></i>Copy help
</button>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="commands" class="mb-5">
<h2 class="h3 mb-3">Common Commands</h2>
<div class="row g-4">
<div class="col-lg-6">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<h3 class="h5"><i class="fas fa-check-circle text-success me-2"></i>Validate</h3>
<p class="text-muted">
Validate an existing systemd unit file. Add <code>-v</code> for verbose
details and <code>-w</code> to include warnings.
</p>
<pre class="bg-dark text-light p-3 rounded"><code>unitforge validate /etc/systemd/system/myapp.service -v -w</code></pre>
<button class="btn btn-sm btn-outline-light" data-copy="unitforge validate /etc/systemd/system/myapp.service -v -w">
<i class="fas fa-copy me-1"></i>Copy
</button>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<h3 class="h5"><i class="fas fa-plus-circle text-primary me-2"></i>Create</h3>
<p class="text-muted">
Create a new unit file from scratch with helpful flags and validation.
</p>
<pre class="bg-dark text-light p-3 rounded"><code>unitforge create \
--type service \
--name myapp \
--exec-start "/usr/bin/myapp" \
--restart on-failure \
--output myapp.service \
--validate-output</code></pre>
<button class="btn btn-sm btn-outline-light" data-copy="unitforge create --type service --name myapp --exec-start &quot;/usr/bin/myapp&quot; --restart on-failure --output myapp.service --validate-output">
<i class="fas fa-copy me-1"></i>Copy
</button>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<h3 class="h5"><i class="fas fa-layer-group text-info me-2"></i>Templates</h3>
<p class="text-muted mb-2">
Explore and generate units from curated templates.
</p>
<pre class="bg-dark text-light p-3 rounded"><code># List templates
unitforge template list
# Show details
unitforge template show webapp
# Generate (non-interactive)
unitforge template generate webapp \
--param name=mywebapp \
--param exec_start="/usr/bin/node server.js" \
--param user=www-data \
--param working_directory=/opt/mywebapp \
--validate-output
# Generate (interactive)
unitforge template generate webapp --interactive</code></pre>
<div class="d-flex gap-2 mt-2">
<button class="btn btn-sm btn-outline-light" data-copy="unitforge template list">
<i class="fas fa-copy me-1"></i>Copy list
</button>
<button class="btn btn-sm btn-outline-light" data-copy="unitforge template show webapp">
<i class="fas fa-copy me-1"></i>Copy show
</button>
<button class="btn btn-sm btn-outline-light" data-copy="unitforge template generate webapp --interactive">
<i class="fas fa-copy me-1"></i>Copy interactive
</button>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<h3 class="h5"><i class="fas fa-info-circle text-secondary me-2"></i>Info</h3>
<p class="text-muted">
Extract metadata and section/key counts from a unit file.
</p>
<pre class="bg-dark text-light p-3 rounded"><code>unitforge info myapp.service</code></pre>
<button class="btn btn-sm btn-outline-light" data-copy="unitforge info myapp.service">
<i class="fas fa-copy me-1"></i>Copy
</button>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card border-0 shadow-sm">
<div class="card-body">
<h3 class="h5"><i class="fas fa-edit text-warning me-2"></i>Edit</h3>
<p class="text-muted">
Modify values in an existing unit file by setting or removing keys.
</p>
<pre class="bg-dark text-light p-3 rounded"><code>unitforge edit source.service dest.service \
--set Service.ExecStart=/usr/bin/myapp \
--set Unit.Description="My Service" \
--remove Service.Environment \
--validate-output</code></pre>
<button class="btn btn-sm btn-outline-light" data-copy="unitforge edit source.service dest.service --set Service.ExecStart=/usr/bin/myapp --set Unit.Description=&quot;My Service&quot; --remove Service.Environment --validate-output">
<i class="fas fa-copy me-1"></i>Copy
</button>
</div>
</div>
</div>
</div>
</section>
<section id="tips" class="mb-5">
<h2 class="h3 mb-3">Tips</h2>
<ul class="list-group">
<li class="list-group-item">
Use <code>--validate-output</code> after generation or editing to catch issues early.
</li>
<li class="list-group-item">
Prefer interactive template generation (<code>--interactive</code>) to fill required fields smoothly.
</li>
<li class="list-group-item">
The CLI is part of the backend package. See the repository for the backend/CLI source:
<a href="{{ github_url }}" target="_blank">{{ github_url }}</a>
</li>
</ul>
</section>
</main>
<footer class="bg-dark text-light py-4 mt-5">
<div class="container">
<div class="row">
<div class="col-md-6">
<p class="fw-bold mb-0"><i class="fas fa-cogs me-2"></i>{{ app_name }}</p>
<p class="text-muted small">{{ app_description }}.</p>
</div>
<div class="col-md-6 text-md-end">
<div class="d-flex justify-content-md-end gap-3">
<a href="{{ api_docs_url }}" class="text-light text-decoration-none">
<i class="fas fa-book me-1"></i>API Docs
</a>
<a href="{{ github_url }}" class="text-light text-decoration-none">
<i class="fab fa-github me-1"></i>GitHub
</a>
</div>
<div class="mt-2 small">
<span class="me-2">
<i class="fas fa-code-branch me-1"></i>Open Source
<span class="badge bg-success ms-2">OSI</span>
</span>
<span>
Licensed under
<a href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank" rel="noopener" class="text-decoration-underline text-light">GPL-3.0-or-later</a>
</span>
</div>
</div>
</div>
</div>
</footer>
<script src="/static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/main.js"></script>
</body>
</html>
+48 -9
View File
@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{{ app_name }} Editor — create, validate, and download systemd unit files with a visual editor and real-time checks.">
<title>Editor - {{ app_name }}</title>
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg">
<link rel="icon" type="image/svg+xml" sizes="16x16" href="/static/img/favicon-16x16.svg">
@@ -14,10 +15,10 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="{{ app_name }}">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.0.1/codemirror.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.0.1/theme/monokai.min.css" rel="stylesheet">
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
<link href="/static/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/vendor/fontawesome/css/all.min.css">
<link href="/static/css/style.css" rel="stylesheet">
</head>
<body>
@@ -40,12 +41,15 @@
<li class="nav-item">
<a class="nav-link" href="/templates">Templates</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/cli">CLI</a>
</li>
</ul>
<div class="navbar-nav">
<button class="btn btn-outline-light btn-sm me-2" onclick="downloadFile()">
<button class="btn btn-light btn-sm me-2" onclick="downloadFile()">
<i class="fas fa-download me-1"></i>Download
</button>
<button class="btn btn-outline-light btn-sm" onclick="showUploadModal()">
<button class="btn btn-light btn-sm" onclick="showUploadModal()">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
@@ -214,7 +218,8 @@
</div>
</div>
<div class="card-body p-0">
<textarea id="editor" class="form-control border-0" rows="25" style="font-family: 'Courier New', monospace; resize: none; color: white; background-color: #2d3748;">[Unit]
<label for="editor" class="visually-hidden">Unit file editor</label>
<textarea id="editor" class="form-control border-0" aria-label="Unit file content" rows="25" style="font-family: 'Courier New', monospace; resize: none; color: white; background-color: #2d3748;">[Unit]
Description=My Service Description
After=network.target
@@ -274,7 +279,7 @@ WantedBy=multi-user.target</textarea>
<small><strong>[Install]</strong> - Installation and enabling info</small>
</div>
<div class="help-item">
<small><a href="https://www.freedesktop.org/software/systemd/man/systemd.unit.html" target="_blank">systemd documentation <i class="fas fa-external-link-alt"></i></a></small>
<small><a class="link-contrast" href="https://www.freedesktop.org/software/systemd/man/systemd.unit.html" target="_blank">systemd documentation <i class="fas fa-external-link-alt"></i></a></small>
</div>
</div>
</div>
@@ -326,7 +331,41 @@ WantedBy=multi-user.target</textarea>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<footer class="bg-dark text-light py-4 mt-5">
<div class="container">
<div class="row">
<div class="col-md-6">
<p class="fw-bold mb-0"><i class="fas fa-cogs me-2"></i>{{ app_name }}</p>
<p class="text-muted small">{{ app_description }}.</p>
</div>
<div class="col-md-6 text-md-end">
<div class="d-flex justify-content-md-end gap-3">
<a href="{{ api_docs_url }}" class="text-light text-decoration-none">
<i class="fas fa-book me-1"></i>API Docs
</a>
<a href="{{ github_url }}" class="text-light text-decoration-none">
<i class="fab fa-github me-1"></i>GitHub
</a>
</div>
<div class="mt-2 small">
<span class="me-2">
<i class="fas fa-code-branch me-1"></i>Open Source
<span class="badge bg-success ms-2">OSI</span>
</span>
<span>
Licensed under
<a href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank" rel="noopener" class="text-decoration-underline text-light">GPL-3.0-or-later</a>
</span>
</div>
</div>
</div>
</div>
</footer>
<script src="/static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/main.js"></script>
<script src="/static/js/editor.js"></script>
</body>
</html>
+28 -17
View File
@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="{{ app_name }} — Systemd unit file creator with templates, validation, and a visual editor." />
<title>{{ app_name }} - Systemd Unit File Creator</title>
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg" />
<link
@@ -19,14 +20,8 @@
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="{{ app_name }}" />
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
rel="stylesheet"
/>
<link href="/static/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
<link rel="stylesheet" href="/static/vendor/fontawesome/css/all.min.css" />
<link href="/static/css/style.css" rel="stylesheet" />
</head>
<body>
@@ -54,6 +49,9 @@
<li class="nav-item">
<a class="nav-link" href="/templates">Templates</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/cli">CLI</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
@@ -91,6 +89,9 @@
<i class="fas fa-templates me-2"></i>Browse
Templates
</a>
<a href="/cli" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-terminal me-1"></i>Try the CLI
</a>
</div>
</div>
<div class="col-lg-6">
@@ -125,7 +126,7 @@ WantedBy=multi-user.target</code></pre>
<div class="row">
<div class="col-lg-8 mx-auto text-center mb-5">
<h2 class="h1 mb-3">Choose Your Approach</h2>
<p class="lead text-muted">
<p class="lead">
{{ app_name }} offers multiple ways to create systemd
unit files, from quick templates to detailed manual
editing.
@@ -142,7 +143,7 @@ WantedBy=multi-user.target</code></pre>
>
<i class="fas fa-magic text-white"></i>
</div>
<h5 class="card-title">Quick Templates</h5>
<h3 class="card-title h5">Quick Templates</h3>
<p class="card-text text-muted">
Use pre-built templates for common services like
web apps, databases, and containers.
@@ -165,12 +166,12 @@ WantedBy=multi-user.target</code></pre>
>
<i class="fas fa-edit text-white"></i>
</div>
<h5 class="card-title">Visual Editor</h5>
<h3 class="card-title h5">Visual Editor</h3>
<p class="card-text text-muted">
Create and edit unit files with our intuitive
form-based editor with real-time validation.
</p>
<a href="/editor" class="btn btn-outline-success">
<a href="/editor" class="btn btn-success">
Open Editor
</a>
</div>
@@ -185,7 +186,7 @@ WantedBy=multi-user.target</code></pre>
>
<i class="fas fa-check-circle text-white"></i>
</div>
<h5 class="card-title">Validation</h5>
<h3 class="card-title h5">Validation</h3>
<p class="card-text text-muted">
Validate existing unit files and get detailed
feedback on syntax and best practices.
@@ -208,7 +209,7 @@ WantedBy=multi-user.target</code></pre>
>
<i class="fas fa-terminal text-white"></i>
</div>
<h5 class="card-title">CLI Tool</h5>
<h3 class="card-title h5">CLI Tool</h3>
<p class="card-text text-muted">
Use the command-line interface for automation
and integration with your development workflow.
@@ -228,7 +229,7 @@ WantedBy=multi-user.target</code></pre>
<div class="col-lg-8 mx-auto">
<div class="text-center mb-4">
<h3>Supported Unit Types</h3>
<p class="text-muted">
<p>
{{ app_name }} supports all major systemd unit types
</p>
</div>
@@ -389,7 +390,7 @@ unitforge template generate webapp \
<div class="container">
<div class="row">
<div class="col-md-6">
<h6><i class="fas fa-cogs me-2"></i>{{ app_name }}</h6>
<p class="fw-bold mb-0"><i class="fas fa-cogs me-2"></i>{{ app_name }}</p>
<p class="text-muted small">{{ app_description }}.</p>
</div>
<div class="col-md-6 text-md-end">
@@ -407,12 +408,22 @@ unitforge template generate webapp \
<i class="fab fa-github me-1"></i>GitHub
</a>
</div>
<div class="mt-2 small">
<span class="me-2">
<i class="fas fa-code-branch me-1"></i>Open Source
<span class="badge bg-success ms-2">OSI</span>
</span>
<span>
Licensed under
<a href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank" rel="noopener" class="text-decoration-underline text-light">GPL-3.0-or-later</a>
</span>
</div>
</div>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/main.js"></script>
</body>
</html>
+27 -12
View File
@@ -18,15 +18,10 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="description" content="{{ app_name }} Templates — generate production-ready systemd unit files from curated templates." />
<meta name="apple-mobile-web-app-title" content="{{ app_name }}" />
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
rel="stylesheet"
/>
<link href="/static/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
<link rel="stylesheet" href="/static/vendor/fontawesome/css/all.min.css" />
<link href="/static/css/style.css" rel="stylesheet" />
</head>
<body>
@@ -56,6 +51,9 @@
>Templates</a
>
</li>
<li class="nav-item">
<a class="nav-link" href="/cli">CLI</a>
</li>
</ul>
</div>
</div>
@@ -68,12 +66,17 @@
<h1 class="display-5 fw-bold mb-3">
<i class="fas fa-templates me-3"></i>Unit File Templates
</h1>
<p class="lead text-muted">
<p class="lead">
Choose from pre-built templates for common systemd unit
configurations. Each template provides a solid
foundation that you can customize for your specific
needs.
</p>
<div class="mt-2">
<a href="/cli" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-terminal me-1"></i>Try the CLI
</a>
</div>
</div>
<div class="col-lg-4 text-lg-end">
<div class="input-group">
@@ -87,7 +90,7 @@
<button
class="btn btn-outline-secondary"
type="button"
onclick="clearSearch()"
onclick="clearSearch()" aria-label="Clear search"
>
<i class="fas fa-times"></i>
</button>
@@ -366,7 +369,7 @@
<div class="container">
<div class="row">
<div class="col-md-6">
<h6><i class="fas fa-cogs me-2"></i>{{ app_name }}</h6>
<p class="fw-bold mb-0"><i class="fas fa-cogs me-2"></i>{{ app_name }}</p>
<p class="text-muted small">{{ app_description }}.</p>
</div>
<div class="col-md-6 text-md-end">
@@ -384,12 +387,24 @@
<i class="fab fa-github me-1"></i>GitHub
</a>
</div>
<div class="mt-2 small">
<span class="me-2">
<i class="fas fa-code-branch me-1"></i>Open Source
<span class="badge bg-success ms-2">OSI</span>
</span>
<span>
Licensed under
<a href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank" rel="noopener" class="text-decoration-underline text-light">GPL-3.0-or-later</a>
</span>
</div>
</div>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/main.js"></script>
<script src="/static/js/templates.js"></script>
</body>