Compare commits
1 Commits
fix/js-for
...
bb07622e62
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb07622e62 |
14
Makefile
14
Makefile
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: all build lint fmt fmt-check test check clean docker hooks
|
.PHONY: all build lint fmt fmt-check test check clean
|
||||||
|
|
||||||
BINARY := upaasd
|
BINARY := upaasd
|
||||||
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
||||||
@@ -22,22 +22,12 @@ fmt-check:
|
|||||||
@test -z "$$(gofmt -l .)" || (echo "Files not formatted:" && gofmt -l . && exit 1)
|
@test -z "$$(gofmt -l .)" || (echo "Files not formatted:" && gofmt -l . && exit 1)
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -v -race -cover -timeout 30s ./...
|
go test -v -race -cover ./...
|
||||||
|
|
||||||
# Check runs all validation without making changes
|
# Check runs all validation without making changes
|
||||||
# Used by CI and Docker build - fails if anything is wrong
|
# Used by CI and Docker build - fails if anything is wrong
|
||||||
check: fmt-check lint test
|
check: fmt-check lint test
|
||||||
@echo "==> All checks passed!"
|
@echo "==> All checks passed!"
|
||||||
|
|
||||||
docker:
|
|
||||||
docker build .
|
|
||||||
|
|
||||||
hooks:
|
|
||||||
@echo "Installing pre-commit hook..."
|
|
||||||
@mkdir -p .git/hooks
|
|
||||||
@printf '#!/bin/sh\nmake check\n' > .git/hooks/pre-commit
|
|
||||||
@chmod +x .git/hooks/pre-commit
|
|
||||||
@echo "Pre-commit hook installed."
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin/
|
rm -rf bin/
|
||||||
|
|||||||
@@ -111,13 +111,10 @@ chi Router ──► Middleware Stack ──► Handler
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
make fmt # Format code
|
make fmt # Format code
|
||||||
make fmt-check # Check formatting (read-only, fails if unformatted)
|
|
||||||
make lint # Run comprehensive linting
|
make lint # Run comprehensive linting
|
||||||
make test # Run tests with race detection (30s timeout)
|
make test # Run tests with race detection
|
||||||
make check # Verify everything passes (fmt-check, lint, test)
|
make check # Verify everything passes (lint, test, build, format)
|
||||||
make build # Build binary
|
make build # Build binary
|
||||||
make docker # Build Docker image
|
|
||||||
make hooks # Install pre-commit hook (runs make check)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Commit Requirements
|
### Commit Requirements
|
||||||
|
|||||||
@@ -54,18 +54,12 @@ func (h *Handlers) HandleAppCreate() http.HandlerFunc { //nolint:funlen // valid
|
|||||||
repoURL := request.FormValue("repo_url")
|
repoURL := request.FormValue("repo_url")
|
||||||
branch := request.FormValue("branch")
|
branch := request.FormValue("branch")
|
||||||
dockerfilePath := request.FormValue("dockerfile_path")
|
dockerfilePath := request.FormValue("dockerfile_path")
|
||||||
dockerNetwork := request.FormValue("docker_network")
|
|
||||||
ntfyTopic := request.FormValue("ntfy_topic")
|
|
||||||
slackWebhook := request.FormValue("slack_webhook")
|
|
||||||
|
|
||||||
data := h.addGlobals(map[string]any{
|
data := h.addGlobals(map[string]any{
|
||||||
"Name": name,
|
"Name": name,
|
||||||
"RepoURL": repoURL,
|
"RepoURL": repoURL,
|
||||||
"Branch": branch,
|
"Branch": branch,
|
||||||
"DockerfilePath": dockerfilePath,
|
"DockerfilePath": dockerfilePath,
|
||||||
"DockerNetwork": dockerNetwork,
|
|
||||||
"NtfyTopic": ntfyTopic,
|
|
||||||
"SlackWebhook": slackWebhook,
|
|
||||||
}, request)
|
}, request)
|
||||||
|
|
||||||
if name == "" || repoURL == "" {
|
if name == "" || repoURL == "" {
|
||||||
@@ -106,9 +100,6 @@ func (h *Handlers) HandleAppCreate() http.HandlerFunc { //nolint:funlen // valid
|
|||||||
RepoURL: repoURL,
|
RepoURL: repoURL,
|
||||||
Branch: branch,
|
Branch: branch,
|
||||||
DockerfilePath: dockerfilePath,
|
DockerfilePath: dockerfilePath,
|
||||||
DockerNetwork: dockerNetwork,
|
|
||||||
NtfyTopic: ntfyTopic,
|
|
||||||
SlackWebhook: slackWebhook,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if createErr != nil {
|
if createErr != nil {
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
|
|
||||||
"sneak.berlin/go/upaas/internal/models"
|
|
||||||
"sneak.berlin/go/upaas/templates"
|
|
||||||
)
|
|
||||||
|
|
||||||
// webhookEventsLimit is the number of webhook events to show in history.
|
|
||||||
const webhookEventsLimit = 100
|
|
||||||
|
|
||||||
// HandleAppWebhookEvents returns the webhook event history handler.
|
|
||||||
func (h *Handlers) HandleAppWebhookEvents() http.HandlerFunc {
|
|
||||||
tmpl := templates.GetParsed()
|
|
||||||
|
|
||||||
return func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
appID := chi.URLParam(request, "id")
|
|
||||||
|
|
||||||
application, findErr := models.FindApp(request.Context(), h.db, appID)
|
|
||||||
if findErr != nil {
|
|
||||||
h.log.Error("failed to find app", "error", findErr)
|
|
||||||
http.Error(writer, "Internal Server Error", http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if application == nil {
|
|
||||||
http.NotFound(writer, request)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
events, eventsErr := application.GetWebhookEvents(
|
|
||||||
request.Context(),
|
|
||||||
webhookEventsLimit,
|
|
||||||
)
|
|
||||||
if eventsErr != nil {
|
|
||||||
h.log.Error("failed to get webhook events",
|
|
||||||
"error", eventsErr,
|
|
||||||
"app", appID,
|
|
||||||
)
|
|
||||||
|
|
||||||
events = []*models.WebhookEvent{}
|
|
||||||
}
|
|
||||||
|
|
||||||
data := h.addGlobals(map[string]any{
|
|
||||||
"App": application,
|
|
||||||
"Events": events,
|
|
||||||
}, request)
|
|
||||||
|
|
||||||
h.renderTemplate(writer, tmpl, "webhook_events.html", data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -52,20 +52,6 @@ func (w *WebhookEvent) Reload(ctx context.Context) error {
|
|||||||
return w.scan(row)
|
return w.scan(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShortCommit returns a truncated commit SHA for display.
|
|
||||||
func (w *WebhookEvent) ShortCommit() string {
|
|
||||||
if !w.CommitSHA.Valid {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
sha := w.CommitSHA.String
|
|
||||||
if len(sha) > shortCommitLength {
|
|
||||||
return sha[:shortCommitLength]
|
|
||||||
}
|
|
||||||
|
|
||||||
return sha
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebhookEvent) insert(ctx context.Context) error {
|
func (w *WebhookEvent) insert(ctx context.Context) error {
|
||||||
query := `
|
query := `
|
||||||
INSERT INTO webhook_events (
|
INSERT INTO webhook_events (
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ func (s *Server) SetupRoutes() {
|
|||||||
r.Post("/apps/{id}/deploy", s.handlers.HandleAppDeploy())
|
r.Post("/apps/{id}/deploy", s.handlers.HandleAppDeploy())
|
||||||
r.Post("/apps/{id}/deployments/cancel", s.handlers.HandleCancelDeploy())
|
r.Post("/apps/{id}/deployments/cancel", s.handlers.HandleCancelDeploy())
|
||||||
r.Get("/apps/{id}/deployments", s.handlers.HandleAppDeployments())
|
r.Get("/apps/{id}/deployments", s.handlers.HandleAppDeployments())
|
||||||
r.Get("/apps/{id}/webhooks", s.handlers.HandleAppWebhookEvents())
|
|
||||||
r.Get("/apps/{id}/deployments/{deploymentID}/logs", s.handlers.HandleDeploymentLogsAPI())
|
r.Get("/apps/{id}/deployments/{deploymentID}/logs", s.handlers.HandleDeploymentLogsAPI())
|
||||||
r.Get("/apps/{id}/deployments/{deploymentID}/download", s.handlers.HandleDeploymentLogDownload())
|
r.Get("/apps/{id}/deployments/{deploymentID}/download", s.handlers.HandleDeploymentLogDownload())
|
||||||
r.Get("/apps/{id}/logs", s.handlers.HandleAppLogs())
|
r.Get("/apps/{id}/logs", s.handlers.HandleAppLogs())
|
||||||
|
|||||||
@@ -77,10 +77,7 @@
|
|||||||
|
|
||||||
<!-- Webhook URL -->
|
<!-- Webhook URL -->
|
||||||
<div class="card p-6 mb-6">
|
<div class="card p-6 mb-6">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<h2 class="section-title mb-4">Webhook URL</h2>
|
||||||
<h2 class="section-title">Webhook URL</h2>
|
|
||||||
<a href="/apps/{{.App.ID}}/webhooks" class="text-primary-600 hover:text-primary-800 text-sm">Event History</a>
|
|
||||||
</div>
|
|
||||||
<p class="text-sm text-gray-500 mb-3">Add this URL as a push webhook in your Gitea repository:</p>
|
<p class="text-sm text-gray-500 mb-3">Add this URL as a push webhook in your Gitea repository:</p>
|
||||||
<div class="copy-field" x-data="copyButton('webhook-url')">
|
<div class="copy-field" x-data="copyButton('webhook-url')">
|
||||||
<code id="webhook-url" class="copy-field-value text-xs">{{.WebhookURL}}</code>
|
<code id="webhook-url" class="copy-field-value text-xs">{{.WebhookURL}}</code>
|
||||||
@@ -149,7 +146,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<form method="POST" action="/apps/{{.App.ID}}/env" class="flex flex-col sm:flex-row gap-2">
|
<form method="POST" action="/apps/{{.App.ID}}/env-vars" class="flex flex-col sm:flex-row gap-2">
|
||||||
{{ .CSRFField }}
|
{{ .CSRFField }}
|
||||||
<input type="text" name="key" placeholder="KEY" required class="input flex-1 font-mono text-sm">
|
<input type="text" name="key" placeholder="KEY" required class="input flex-1 font-mono text-sm">
|
||||||
<input type="text" name="value" placeholder="value" required class="input flex-1 font-mono text-sm">
|
<input type="text" name="value" placeholder="value" required class="input flex-1 font-mono text-sm">
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ func initTemplates() {
|
|||||||
"app_detail.html",
|
"app_detail.html",
|
||||||
"app_edit.html",
|
"app_edit.html",
|
||||||
"deployments.html",
|
"deployments.html",
|
||||||
"webhook_events.html",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pageTemplates = make(map[string]*template.Template)
|
pageTemplates = make(map[string]*template.Template)
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
{{template "base" .}}
|
|
||||||
|
|
||||||
{{define "title"}}Webhook Events - {{.App.Name}} - µPaaS{{end}}
|
|
||||||
|
|
||||||
{{define "content"}}
|
|
||||||
{{template "nav" .}}
|
|
||||||
|
|
||||||
<main class="max-w-4xl 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>
|
|
||||||
|
|
||||||
<div class="section-header">
|
|
||||||
<h1 class="text-2xl font-medium text-gray-900">Webhook Events</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{if .Events}}
|
|
||||||
<div class="card overflow-hidden">
|
|
||||||
<table class="table">
|
|
||||||
<thead class="table-header">
|
|
||||||
<tr>
|
|
||||||
<th>Time</th>
|
|
||||||
<th>Event</th>
|
|
||||||
<th>Branch</th>
|
|
||||||
<th>Commit</th>
|
|
||||||
<th>Status</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="table-body">
|
|
||||||
{{range .Events}}
|
|
||||||
<tr>
|
|
||||||
<td class="text-gray-500 text-sm whitespace-nowrap">
|
|
||||||
<span x-data="relativeTime('{{.CreatedAt.Format `2006-01-02T15:04:05Z07:00`}}')" x-text="display" class="cursor-default" title="{{.CreatedAt.Format `2006-01-02 15:04:05`}}"></span>
|
|
||||||
</td>
|
|
||||||
<td class="text-gray-700 text-sm">{{.EventType}}</td>
|
|
||||||
<td class="font-mono text-gray-500 text-sm">{{.Branch}}</td>
|
|
||||||
<td class="font-mono text-gray-500 text-xs">
|
|
||||||
{{if and .CommitSHA.Valid .CommitURL.Valid}}
|
|
||||||
<a href="{{.CommitURL.String}}" target="_blank" rel="noopener noreferrer" class="text-primary-600 hover:text-primary-800">{{.ShortCommit}}</a>
|
|
||||||
{{else if .CommitSHA.Valid}}
|
|
||||||
{{.ShortCommit}}
|
|
||||||
{{else}}
|
|
||||||
<span class="text-gray-400">-</span>
|
|
||||||
{{end}}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{if .Matched}}
|
|
||||||
{{if .Processed}}
|
|
||||||
<span class="badge-success">Matched</span>
|
|
||||||
{{else}}
|
|
||||||
<span class="badge-warning">Matched (pending)</span>
|
|
||||||
{{end}}
|
|
||||||
{{else}}
|
|
||||||
<span class="badge-neutral">No match</span>
|
|
||||||
{{end}}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div class="card">
|
|
||||||
<div class="empty-state">
|
|
||||||
<svg class="empty-state-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
|
||||||
</svg>
|
|
||||||
<h3 class="empty-state-title">No webhook events yet</h3>
|
|
||||||
<p class="empty-state-description">Webhook events will appear here once your repository sends push notifications.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</main>
|
|
||||||
{{end}}
|
|
||||||
Reference in New Issue
Block a user