Compare commits
4
Commits
3cb0ba7687
..
next
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3b9c6fca9 | ||
|
|
23f378cfde | ||
|
|
db3b47e423 | ||
|
|
b101bc1a80 |
+30
-6
@@ -1,11 +1,35 @@
|
|||||||
# .git is sent so that `make build` in the Dockerfile can stamp the commit into
|
# .git is sent so that `make build` in the Dockerfile can stamp the commit into
|
||||||
# upaas. List no tracked file here: git would see it as deleted in the build and
|
# upaas. List no tracked file here: git would see it as deleted in the build and
|
||||||
# the version would end in -dirty.
|
# the version would end in -dirty.
|
||||||
|
|
||||||
|
# The patterns of .gitignore; **/ makes Docker match them in every directory.
|
||||||
|
**/.DS_Store
|
||||||
|
**/Thumbs.db
|
||||||
|
**/*.swp
|
||||||
|
**/*.swo
|
||||||
|
**/*~
|
||||||
|
**/*.bak
|
||||||
|
**/.idea/
|
||||||
|
**/.vscode/
|
||||||
|
**/*.sublime-*
|
||||||
|
**/node_modules/
|
||||||
|
**/.env
|
||||||
|
**/.env.*
|
||||||
|
**/*.pem
|
||||||
|
**/*.key
|
||||||
|
**/bin/
|
||||||
|
**/*.exe
|
||||||
|
**/*.exe~
|
||||||
|
**/*.dll
|
||||||
|
**/*.so
|
||||||
|
**/*.dylib
|
||||||
|
**/*.test
|
||||||
|
**/*.out
|
||||||
|
/data/
|
||||||
|
|
||||||
|
# Git never applies its ignore patterns inside .git; send all of it again.
|
||||||
|
!.git/**
|
||||||
|
|
||||||
# .git is sent without its config, because a remote URL there can carry a
|
# .git is sent without its config, because a remote URL there can carry a
|
||||||
# credential; `git describe` does not need it.
|
# credential; `git describe` does not need it. Keep this after !.git/**.
|
||||||
.git/config
|
.git/config
|
||||||
.env
|
|
||||||
bin/
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
|
||||||
*.test
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# .dockerignore repeats these patterns; change both together.
|
||||||
|
|
||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
@@ -29,3 +31,6 @@ bin/
|
|||||||
*.dylib
|
*.dylib
|
||||||
*.test
|
*.test
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
|
# upaasd's data directory when it runs from the checkout (UPAAS_DATA_DIR default)
|
||||||
|
/data/
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ regress.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-02: In a window too narrow for the top bar, such as 390 px, the New
|
||||||
|
App and Logout buttons move to a second row instead of running into "by
|
||||||
|
@sneak"; the bar keeps a gap between its two sides at every width (#272).
|
||||||
|
|
||||||
- 2026-10-02: App names may contain dots, such as `sneak.berlin`: lowercase
|
- 2026-10-02: App names may contain dots, such as `sneak.berlin`: lowercase
|
||||||
letters and numbers joined by single dots or by hyphens, 2 to 63 characters.
|
letters and numbers joined by single dots or by hyphens, 2 to 63 characters.
|
||||||
Docker accepts every such name in the image name `upaas-<name>`; a dot needs a
|
Docker accepts every such name in the image name `upaas-<name>`; a dot needs a
|
||||||
@@ -27,6 +31,16 @@ regress.
|
|||||||
app forms check the same rule; browsers ignored their old pattern, which was
|
app forms check the same rule; browsers ignored their old pattern, which was
|
||||||
not a valid regular expression there (#260).
|
not a valid regular expression there (#260).
|
||||||
|
|
||||||
|
- 2026-10-02: On the Applications list, a long repository URL now wraps within
|
||||||
|
its column instead of making the table wider than its card, which cut off the
|
||||||
|
Actions column and the Deploy buttons. In a window too narrow for the table,
|
||||||
|
the card scrolls sideways instead of cutting the table off (#262).
|
||||||
|
|
||||||
|
- 2026-10-02: `docker build .` no longer sends git-ignored files, such as
|
||||||
|
`.env.local`, `*.key` files or upaasd's `data/` directory with its session
|
||||||
|
key, into the build stages and the build cache: `.dockerignore` now leaves out
|
||||||
|
everything `.gitignore` does, and `data/` is git-ignored (#266).
|
||||||
|
|
||||||
- 2026-10-02: `.dockerignore` leaves out `.git/config`, so a remote URL there
|
- 2026-10-02: `.dockerignore` leaves out `.git/config`, so a remote URL there
|
||||||
that carries a credential no longer goes into the Docker build; the image
|
that carries a credential no longer goes into the Docker build; the image
|
||||||
still shows the commit it was built from (#269).
|
still shows the commit it was built from (#269).
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package handlers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"sneak.berlin/go/upaas/internal/service/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestDashboardTableFitsCard checks that the card around the app table
|
||||||
|
// scrolls sideways instead of hiding what does not fit, and that a long
|
||||||
|
// repository URL wraps instead of pushing the action buttons out.
|
||||||
|
func TestDashboardTableFitsCard(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCtx := setupTestHandlers(t)
|
||||||
|
|
||||||
|
repoURL := "https://git.example.com/user/" +
|
||||||
|
"a-repository-name-long-enough-to-push-the-action-buttons-out.git"
|
||||||
|
|
||||||
|
_, err := testCtx.appSvc.CreateApp(t.Context(), app.CreateAppInput{
|
||||||
|
Name: "long-url-app",
|
||||||
|
RepoURL: repoURL,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
request := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
testCtx.handlers.HandleDashboard().ServeHTTP(recorder, request)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, recorder.Code)
|
||||||
|
|
||||||
|
body := recorder.Body.String()
|
||||||
|
|
||||||
|
beforeTable, _, found := strings.Cut(body, `<table class="table">`)
|
||||||
|
require.True(t, found, "dashboard has no app table")
|
||||||
|
|
||||||
|
cardTag := beforeTable[strings.LastIndex(beforeTable, "<div"):]
|
||||||
|
assert.Contains(t, cardTag, "overflow-x-auto")
|
||||||
|
assert.NotContains(t, cardTag, "overflow-hidden")
|
||||||
|
|
||||||
|
beforeURL, _, found := strings.Cut(body, ">"+repoURL+"</td>")
|
||||||
|
require.True(t, found, "dashboard has no repository cell")
|
||||||
|
|
||||||
|
cellTag := beforeURL[strings.LastIndex(beforeURL, "<td"):]
|
||||||
|
assert.Contains(t, cellTag, "whitespace-normal")
|
||||||
|
assert.Contains(t, cellTag, "break-all")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTopBarWrapsWhenNarrow checks that the top bar keeps a gap between the
|
||||||
|
// µPaaS title and the New App and Logout buttons, and puts the buttons on a
|
||||||
|
// second row in a window too narrow for one, instead of letting them meet.
|
||||||
|
func TestTopBarWrapsWhenNarrow(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCtx := setupTestHandlers(t)
|
||||||
|
|
||||||
|
request := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
testCtx.handlers.HandleDashboard().ServeHTTP(recorder, request)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, recorder.Code)
|
||||||
|
|
||||||
|
_, afterNav, found := strings.Cut(recorder.Body.String(), `<nav class="app-bar">`)
|
||||||
|
require.True(t, found, "dashboard has no top bar")
|
||||||
|
|
||||||
|
rowTag, _, _ := strings.Cut(strings.TrimSpace(afterNav), ">")
|
||||||
|
assert.Contains(t, rowTag, "flex-wrap")
|
||||||
|
assert.Contains(t, rowTag, "gap-")
|
||||||
|
}
|
||||||
+1
-1
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
{{define "nav"}}
|
{{define "nav"}}
|
||||||
<nav class="app-bar">
|
<nav class="app-bar">
|
||||||
<div class="max-w-6xl mx-auto flex justify-between items-center">
|
<div class="max-w-6xl mx-auto flex flex-wrap justify-between items-center gap-3">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<a href="/" class="text-xl font-medium text-gray-900 hover:text-primary-600 transition-colors">µPaaS</a>
|
<a href="/" class="text-xl font-medium text-gray-900 hover:text-primary-600 transition-colors">µPaaS</a>
|
||||||
<span class="text-sm text-gray-500">by <a href="https://sneak.berlin" class="text-primary-600 hover:text-primary-800">@sneak</a></span>
|
<span class="text-sm text-gray-500">by <a href="https://sneak.berlin" class="text-primary-600 hover:text-primary-800">@sneak</a></span>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if .AppStats}}
|
{{if .AppStats}}
|
||||||
<div class="card overflow-hidden">
|
<div class="card overflow-x-auto">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead class="table-header">
|
<thead class="table-header">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
{{.App.Name}}
|
{{.App.Name}}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-gray-500 font-mono text-xs">{{.App.RepoURL}}</td>
|
<td class="text-gray-500 font-mono text-xs whitespace-normal break-all">{{.App.RepoURL}}</td>
|
||||||
<td class="text-gray-500">{{.App.Branch}}</td>
|
<td class="text-gray-500">{{.App.Branch}}</td>
|
||||||
<td>
|
<td>
|
||||||
{{if eq .App.Status "running"}}
|
{{if eq .App.Status "running"}}
|
||||||
|
|||||||
Reference in New Issue
Block a user