Files
upaas/templates/app_edit.html
T
sneak 8e3c73de09
Check / check (pull_request) Skipped
Allow dots in app names (closes #260)
App names may now contain dots, such as sneak.berlin: runs of
lowercase letters and numbers joined by single dots or by hyphens,
2 to 63 characters. Docker accepts every such name in the app's image
name, upaas-<name>; a dot needs a letter or number on both sides
because Docker requires it. The rule also keeps a dot off either end,
so the name is safe as a directory and log file name.

The new and edit app forms use the same pattern. Their old one was not
a valid regular expression under the flag browsers compile it with, so
browsers ignored it.

A test creates an app named sneak.berlin through the new app form,
then builds and deploys it against the fake Docker API and checks the
image and container names with Docker's own rules.

Model: opus-5-5
2026-10-02 03:01:43 +00:00

159 lines
5.9 KiB
HTML

{{template "base" .}}
{{define "title"}}Edit {{.App.Name}} - µPaaS{{end}}
{{define "content"}}
{{template "nav" .}}
<main class="max-w-2xl mx-auto px-4 py-8">
<div class="mb-6">
<a href="/apps/{{.App.ID}}" class="text-primary-600 hover:text-primary-800 inline-flex items-center">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
Back to {{.App.Name}}
</a>
</div>
<h1 class="text-2xl font-medium text-gray-900 mb-6">Edit Application</h1>
<div class="card p-6">
{{template "alert-error" .}}
<form method="POST" action="/apps/{{.App.ID}}" class="space-y-6">
{{ .CSRFField }}
<div class="form-group">
<label for="name" class="label">App Name</label>
<input
type="text"
id="name"
name="name"
value="{{.App.Name}}"
required
minlength="2"
maxlength="63"
pattern="[a-z0-9]+((\.|-+)[a-z0-9]+)*"
class="input"
>
<p class="text-sm text-gray-500 mt-1">Lowercase letters, numbers, hyphens, and dots, such as my-app or example.com</p>
</div>
<div class="form-group">
<label for="repo_url" class="label">Repository URL (SSH)</label>
<input
type="text"
id="repo_url"
name="repo_url"
value="{{.App.RepoURL}}"
required
class="input font-mono"
>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="form-group">
<label for="branch" class="label">Branch</label>
<input
type="text"
id="branch"
name="branch"
value="{{.App.Branch}}"
required
class="input"
>
</div>
<div class="form-group">
<label for="dockerfile_path" class="label">Dockerfile Path</label>
<input
type="text"
id="dockerfile_path"
name="dockerfile_path"
value="{{.App.DockerfilePath}}"
required
class="input"
>
</div>
</div>
<hr class="border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Optional Settings</h3>
<div class="form-group">
<label for="docker_network" class="label">Docker Network</label>
<input
type="text"
id="docker_network"
name="docker_network"
value="{{if .App.DockerNetwork.Valid}}{{.App.DockerNetwork.String}}{{end}}"
class="input"
placeholder="bridge"
>
</div>
<div class="form-group">
<label for="ntfy_topic" class="label">Ntfy Topic URL</label>
<input
type="url"
id="ntfy_topic"
name="ntfy_topic"
value="{{if .App.NtfyTopic.Valid}}{{.App.NtfyTopic.String}}{{end}}"
class="input"
placeholder="https://ntfy.sh/my-topic"
>
</div>
<div class="form-group">
<label for="slack_webhook" class="label">Slack Webhook URL</label>
<input
type="url"
id="slack_webhook"
name="slack_webhook"
value="{{if .App.SlackWebhook.Valid}}{{.App.SlackWebhook.String}}{{end}}"
class="input"
placeholder="https://hooks.slack.com/services/..."
>
</div>
<hr class="border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Resource Limits</h3>
<div class="grid grid-cols-2 gap-4">
<div class="form-group">
<label for="cpu_limit" class="label">CPU Limit (cores)</label>
<input
type="text"
id="cpu_limit"
name="cpu_limit"
value="{{if .App.CPULimit.Valid}}{{.App.CPULimit.Float64}}{{end}}"
class="input"
placeholder="e.g. 0.5, 1, 2"
>
<p class="text-sm text-gray-500 mt-1">Number of CPU cores (e.g. 0.5 = half a core)</p>
</div>
<div class="form-group">
<label for="memory_limit" class="label">Memory Limit</label>
<input
type="text"
id="memory_limit"
name="memory_limit"
value="{{if .App.MemoryLimit.Valid}}{{formatMemoryBytes .App.MemoryLimit.Int64}}{{end}}"
class="input"
placeholder="e.g. 256m, 1g"
>
<p class="text-sm text-gray-500 mt-1">Memory with unit suffix (k, m, g) or plain bytes</p>
</div>
</div>
<div class="flex justify-end gap-3 pt-4">
<a href="/apps/{{.App.ID}}" class="btn-secondary">Cancel</a>
<button type="submit" class="btn-primary">Save Changes</button>
</div>
</form>
</div>
</main>
{{end}}