From f93158cb3ceffa2ea29a7d3633194af505bde734 Mon Sep 17 00:00:00 2001 From: mae Date: Wed, 4 Mar 2026 22:50:58 -0600 Subject: [PATCH 1/3] cmd/pkgserver: basic web ui --- cmd/pkgserver/main.go | 55 ++++++++++++++++++++ cmd/pkgserver/ui/index.html | 23 +++++++++ cmd/pkgserver/ui/static/_common.scss | 0 cmd/pkgserver/ui/static/dark.css | 6 +++ cmd/pkgserver/ui/static/dark.css.map | 7 +++ cmd/pkgserver/ui/static/dark.scss | 6 +++ cmd/pkgserver/ui/static/favicon.ico | Bin 0 -> 15086 bytes cmd/pkgserver/ui/static/index.js | 4 ++ cmd/pkgserver/ui/static/index.ts | 6 +++ cmd/pkgserver/ui/static/light.css | 6 +++ cmd/pkgserver/ui/static/light.css.map | 7 +++ cmd/pkgserver/ui/static/light.scss | 6 +++ internal/azalea/azalea.bnf | 0 internal/azalea/azalea.go | 69 ++++++++++++++++++++++++++ internal/azalea/generator.go | 36 ++++++++++++++ 15 files changed, 231 insertions(+) create mode 100644 cmd/pkgserver/main.go create mode 100644 cmd/pkgserver/ui/index.html create mode 100644 cmd/pkgserver/ui/static/_common.scss create mode 100644 cmd/pkgserver/ui/static/dark.css create mode 100644 cmd/pkgserver/ui/static/dark.css.map create mode 100644 cmd/pkgserver/ui/static/dark.scss create mode 100644 cmd/pkgserver/ui/static/favicon.ico create mode 100644 cmd/pkgserver/ui/static/index.js create mode 100644 cmd/pkgserver/ui/static/index.ts create mode 100644 cmd/pkgserver/ui/static/light.css create mode 100644 cmd/pkgserver/ui/static/light.css.map create mode 100644 cmd/pkgserver/ui/static/light.scss create mode 100644 internal/azalea/azalea.bnf create mode 100644 internal/azalea/azalea.go create mode 100644 internal/azalea/generator.go diff --git a/cmd/pkgserver/main.go b/cmd/pkgserver/main.go new file mode 100644 index 0000000..1cb0738 --- /dev/null +++ b/cmd/pkgserver/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "embed" + "fmt" + "net/http" +) + +//go:generate sh -c "sass ui/static/dark.scss ui/static/dark.css && sass ui/static/light.scss ui/static/light.css && tsc ui/static/index.ts" +//go:embed ui/* +var content embed.FS + +func serveWebUI(w http.ResponseWriter, r *http.Request) { + fmt.Printf("serveWebUI: %s\n", r.URL.Path) + w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") + w.Header().Set("Pragma", "no-cache") + w.Header().Set("Expires", "0") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("X-XSS-Protection", "1") + w.Header().Set("X-Frame-Options", "DENY") + + http.ServeFileFS(w, r, content, "ui/index.html") +} +func serveStaticContent(w http.ResponseWriter, r *http.Request) { + fmt.Printf("serveStaticContent: %s\n", r.URL.Path) + switch r.URL.Path { + case "/static/style.css": + darkTheme := r.CookiesNamed("dark_theme") + if len(darkTheme) > 0 && darkTheme[0].Value == "true" { + http.ServeFileFS(w, r, content, "ui/static/dark.css") + } else { + http.ServeFileFS(w, r, content, "ui/static/light.css") + } + break + case "/favicon.ico": + http.ServeFileFS(w, r, content, "ui/static/favicon.ico") + break + case "/static/index.js": + http.ServeFileFS(w, r, content, "ui/static/index.js") + break + default: + http.NotFound(w, r) + + } +} +func serveAPI(w http.ResponseWriter, r *http.Request) { + +} +func main() { + http.HandleFunc("GET /{$}", serveWebUI) + http.HandleFunc("GET /favicon.ico", serveStaticContent) + http.HandleFunc("GET /static/", serveStaticContent) + http.HandleFunc("GET /api/", serveAPI) + http.ListenAndServe(":8067", nil) +} diff --git a/cmd/pkgserver/ui/index.html b/cmd/pkgserver/ui/index.html new file mode 100644 index 0000000..6a832cb --- /dev/null +++ b/cmd/pkgserver/ui/index.html @@ -0,0 +1,23 @@ + + + + + + Hakurei PkgServer + + + +

Hakurei PkgServer

+ + + +
StatusNameVersion
+

Showing entries .

+« PreviousNext » + + \ No newline at end of file diff --git a/cmd/pkgserver/ui/static/_common.scss b/cmd/pkgserver/ui/static/_common.scss new file mode 100644 index 0000000..e69de29 diff --git a/cmd/pkgserver/ui/static/dark.css b/cmd/pkgserver/ui/static/dark.css new file mode 100644 index 0000000..8a6ac8a --- /dev/null +++ b/cmd/pkgserver/ui/static/dark.css @@ -0,0 +1,6 @@ +@use 'common'; +html { + background-color: #2c2c2c; + color: ghostwhite; } + +/*# sourceMappingURL=dark.css.map */ diff --git a/cmd/pkgserver/ui/static/dark.css.map b/cmd/pkgserver/ui/static/dark.css.map new file mode 100644 index 0000000..9db2757 --- /dev/null +++ b/cmd/pkgserver/ui/static/dark.css.map @@ -0,0 +1,7 @@ +{ +"version": 3, +"mappings": "AAAA,aAAa;AAEb,IAAK;EACH,gBAAgB,EAAE,OAAO;EACzB,KAAK,EAAE,UAAU", +"sources": ["dark.scss"], +"names": [], +"file": "dark.css" +} diff --git a/cmd/pkgserver/ui/static/dark.scss b/cmd/pkgserver/ui/static/dark.scss new file mode 100644 index 0000000..8d7ea84 --- /dev/null +++ b/cmd/pkgserver/ui/static/dark.scss @@ -0,0 +1,6 @@ +@use 'common'; + +html { + background-color: #2c2c2c; + color: ghostwhite; +} \ No newline at end of file diff --git a/cmd/pkgserver/ui/static/favicon.ico b/cmd/pkgserver/ui/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..cc6750305fea9d207c16502e82256d0e931e09d9 GIT binary patch literal 15086 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 Date: Thu, 5 Mar 2026 00:32:25 -0600 Subject: [PATCH 2/3] cmd/pkgserver: pagination --- cmd/pkgserver/ui/index.html | 13 ++++--- cmd/pkgserver/ui/static/index.js | 63 ++++++++++++++++++++++++++++++ cmd/pkgserver/ui/static/index.ts | 66 ++++++++++++++++++++++++++++++-- 3 files changed, 134 insertions(+), 8 deletions(-) diff --git a/cmd/pkgserver/ui/index.html b/cmd/pkgserver/ui/index.html index 6a832cb..3ca3fc2 100644 --- a/cmd/pkgserver/ui/index.html +++ b/cmd/pkgserver/ui/index.html @@ -4,20 +4,23 @@ Hakurei PkgServer +

Hakurei PkgServer

+ + + +
StatusNameVersion
+

Showing entries .

+« Previous 1 Next » - - -
StatusNameVersion
-

Showing entries .

-« PreviousNext » +
© Hakurei. Licensed under the MIT license.
\ No newline at end of file diff --git a/cmd/pkgserver/ui/static/index.js b/cmd/pkgserver/ui/static/index.js index 26cdcb8..759c2c2 100644 --- a/cmd/pkgserver/ui/static/index.js +++ b/cmd/pkgserver/ui/static/index.js @@ -1,4 +1,67 @@ +"use strict"; +var PackageEntry = /** @class */ (function () { + function PackageEntry() { + } + return PackageEntry; +}()); +var State = /** @class */ (function () { + function State() { + this.entriesPerPage = 10; + this.currentPage = 1; + this.entryIndex = 0; + this.loadedEntries = []; + } + State.prototype.getEntriesPerPage = function () { + return this.entriesPerPage; + }; + State.prototype.setEntriesPerPage = function (entriesPerPage) { + this.entriesPerPage = entriesPerPage; + this.updateRange(); + }; + State.prototype.getCurrentPage = function () { + return this.currentPage; + }; + State.prototype.setCurrentPage = function (page) { + this.currentPage = page; + document.getElementById("page-number").innerText = String(this.currentPage); + this.updateRange(); + }; + State.prototype.getEntryIndex = function () { + return this.entryIndex; + }; + State.prototype.setEntryIndex = function (entryIndex) { + this.entryIndex = entryIndex; + this.updateRange(); + }; + State.prototype.getLoadedEntries = function () { + return this.loadedEntries; + }; + State.prototype.getMaxPage = function () { + return this.loadedEntries.length / this.entriesPerPage; + }; + State.prototype.updateRange = function () { + var max = Math.min(this.entryIndex + this.entriesPerPage, this.loadedEntries.length); + document.getElementById("entry-counter").innerText = "".concat(this.entryIndex, "-").concat(max, " of ").concat(this.loadedEntries.length); + }; + return State; +}()); +var STATE; function prevPage() { + var current = STATE.getCurrentPage(); + if (current > 1) { + STATE.setCurrentPage(STATE.getCurrentPage() - 1); + } } function nextPage() { + var current = STATE.getCurrentPage(); + if (current < STATE.getMaxPage()) { + STATE.setCurrentPage(STATE.getCurrentPage() + 1); + } } +document.addEventListener("DOMContentLoaded", function () { + STATE = new State(); + STATE.updateRange(); + document.getElementById("count").addEventListener("change", function (event) { + STATE.setEntriesPerPage(parseInt(event.target.value)); + }); +}); diff --git a/cmd/pkgserver/ui/static/index.ts b/cmd/pkgserver/ui/static/index.ts index 1253812..0301df0 100644 --- a/cmd/pkgserver/ui/static/index.ts +++ b/cmd/pkgserver/ui/static/index.ts @@ -1,6 +1,66 @@ -function prevPage() { +"use strict" + +class PackageEntry { } -function nextPage() { +class State { + entriesPerPage: number = 10 + currentPage: number = 1 + entryIndex: number = 0 + loadedEntries: PackageEntry[] = [] + getEntriesPerPage(): number { + return this.entriesPerPage + } + setEntriesPerPage(entriesPerPage: number) { + this.entriesPerPage = entriesPerPage + this.updateRange() + } + getCurrentPage(): number { + return this.currentPage + } + setCurrentPage(page: number) { + this.currentPage = page + document.getElementById("page-number").innerText = String(this.currentPage) + this.updateRange() + } + getEntryIndex(): number { + return this.entryIndex + } + setEntryIndex(entryIndex: number) { + this.entryIndex = entryIndex + this.updateRange() + } + getLoadedEntries(): PackageEntry[] { + return this.loadedEntries + } + getMaxPage(): number { + return this.loadedEntries.length / this.entriesPerPage + } + updateRange() { + let max = Math.min(this.entryIndex + this.entriesPerPage, this.loadedEntries.length) + document.getElementById("entry-counter").innerText = `${this.entryIndex}-${max} of ${this.loadedEntries.length}` + } +} -} \ No newline at end of file +let STATE: State + +function prevPage() { + let current = STATE.getCurrentPage() + if (current > 1) { + STATE.setCurrentPage(STATE.getCurrentPage() - 1) + } +} +function nextPage() { + let current = STATE.getCurrentPage() + if (current < STATE.getMaxPage()) { + STATE.setCurrentPage(STATE.getCurrentPage() + 1) + } +} + +document.addEventListener("DOMContentLoaded", () => { + STATE = new State() + STATE.updateRange() + document.getElementById("count").addEventListener("change", (event) => { + STATE.setEntriesPerPage(parseInt((event.target as HTMLSelectElement).value)) + }) +}) \ No newline at end of file From 1ae6a35bc8acebb237c9fb228364e3ad552aa2f8 Mon Sep 17 00:00:00 2001 From: mae Date: Thu, 5 Mar 2026 01:12:17 -0600 Subject: [PATCH 3/3] cmd/pkgserver: replace favicon --- cmd/pkgserver/ui/static/favicon.ico | Bin 15086 -> 16958 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/cmd/pkgserver/ui/static/favicon.ico b/cmd/pkgserver/ui/static/favicon.ico index cc6750305fea9d207c16502e82256d0e931e09d9..2fefdfd7efe2321bbb1cb85a6e7c308522ba088f 100644 GIT binary patch literal 16958 zcmZQzU}RuqaBu+83Je-f3=Con3=A3!3=9qo3=9nn5OIc4Fla(RQc{vZT3VV*N=k}R zN=k}ZLPCOR&^V3`4v~_QS}!Ff^&bg;kdl%DC0RPyKJZpZNlEQN@;fXX{0H7;^l^cd zlvEj-|6fZ=O0v+$_JOxWN=nKG&Ht+g-c|H-fs~XKADaJX(9ib4VUv`U)HkFsm@qh8 zML$_ ze&Ah2dlyJaNrBqDaZ*xJlhOQtoA$QT*9IvmDQ;*xcdnF_RFIUEl&+MN)CDwugVH_- z|C5rE@&}a*^mQd|Y=frny*T^|@-Hs@SV~Ijpp=wUwUm^UIc;1=dsC&Pq{7MYJ9?P> zkdl(Bl9G~groD@)YlD=Ol(3YPl(LkRR2mh+0hBicsOv)dnImI3gt_RUjoL z#YsPxQ^zJLDXBmz_#c)xqNSvyWU1pa+M6OJC8a1OC3TyM{s)D_S7`gmUP?+z666ce zcoK*nOc)eTQc_Y+sO)!;|B>-CDJiK-Qc_Z%rKF^qq@<)~Nl8hW4u)`$l9JjDe~|^+BYhqz(;0Iw+Nrl41wN0|?8=$binL zNlBfeFbtkbNl7h`l9DO}&5t1GZ@PJmNQw&xF*1#??V<2tZ+4I#7PZXRnx;7=x6QRK1jx6sX<@ zm0N`TM2fs8be!RZl#~?R`gQpHi!LW6C1nRqr=<7`m)e(TW@SoANi{&lK>1?O&521# zNlk~k50{@v5$}PT1&VKwKF}DW1GG*B^=Evc^P`fWwkCR934gUpwblB$K~2~Q{= zR3C!WPtQQtmVxS4P}%^EYuOOxAUZ38#ucaygzP6` z*`R$PpfymS@mtW`K4@Lo4rp5tW(Fuslv3@WQYX_Q#MgYq&czCmk6LFpURj15DNsA^yp)vGIVmZrby8AN zl~PhtAUPjU-+@|oj+!$X0=PopKLY%*hp>OhF&D`Tu{Ay&(EO$lUY)|Nn1b0CWEzfVcxB4hkWVdZ_!Dp)}Y- zAaRg~Ks3mMAoD;phz}D7dGHSd0~3^HbN>&vmjy(F%HE*XU;#1* l#0N?LKfv4wrXPSp<^TWx2heDk_ye%>{{R00l?Q8r5dcr}!IuC4 literal 15086 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