diff --git a/.gitignore b/.gitignore
index 47939c7..5469a0f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,9 @@ go.work.sum
# go generate
/cmd/hakurei/LICENSE
/cmd/pkgserver/.sass-cache
+/cmd/pkgserver/ui/static/*.js
+/cmd/pkgserver/ui/static/*.css*
+/cmd/pkgserver/ui/static/*.css.map
/internal/pkg/testdata/testtool
/internal/rosa/hakurei_current.tar.gz
diff --git a/cmd/pkgserver/ui.go b/cmd/pkgserver/ui.go
index e6e5eb7..4c87f22 100644
--- a/cmd/pkgserver/ui.go
+++ b/cmd/pkgserver/ui.go
@@ -1,13 +1,6 @@
package main
-import (
- "embed"
- "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 -p ui/static"
-//go:embed ui/*
-var content embed.FS
+import "net/http"
func serveWebUI(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
diff --git a/cmd/pkgserver/ui/static/dark.css b/cmd/pkgserver/ui/static/dark.css
deleted file mode 100644
index 8a6ac8a..0000000
--- a/cmd/pkgserver/ui/static/dark.css
+++ /dev/null
@@ -1,6 +0,0 @@
-@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
deleted file mode 100644
index 9db2757..0000000
--- a/cmd/pkgserver/ui/static/dark.css.map
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-"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/index.js b/cmd/pkgserver/ui/static/index.js
deleted file mode 100644
index 6a64e8c..0000000
--- a/cmd/pkgserver/ui/static/index.js
+++ /dev/null
@@ -1,150 +0,0 @@
-class PackageIndexEntry {
- name;
- size;
- description;
- website;
- version;
- report;
-}
-function toHTML(entry) {
- let v = entry.version != null ? `${escapeHtml(entry.version)}` : "";
- let s = entry.size != null ? `
Size: ${toByteSizeString(entry.size)} (${entry.size})
` : "";
- let d = entry.description != null ? `${escapeHtml(entry.description)}
` : "";
- let w = entry.website != null ? `Website` : "";
- let r = entry.report ? `Log (View | Download)` : "";
- let row = (document.createElement('tr'));
- row.innerHTML = `
- ${escapeHtml(entry.name)} ${v}
- ${d}
- ${s}
- ${w}
- ${r}
- | `;
- return row;
-}
-function toByteSizeString(bytes) {
- if (bytes < 1024)
- return `${bytes}B`;
- if (bytes < Math.pow(1024, 2))
- return `${(bytes / 1024).toFixed(2)}kiB`;
- if (bytes < Math.pow(1024, 3))
- return `${(bytes / Math.pow(1024, 2)).toFixed(2)}MiB`;
- if (bytes < Math.pow(1024, 4))
- return `${(bytes / Math.pow(1024, 3)).toFixed(2)}GiB`;
- if (bytes < Math.pow(1024, 5))
- return `${(bytes / Math.pow(1024, 4)).toFixed(2)}TiB`;
- return "not only is it big, it's large";
-}
-const API_VERSION = 1;
-const ENDPOINT = `/api/v${API_VERSION}`;
-class InfoPayload {
- count;
- hakurei_version;
-}
-async function infoRequest() {
- const res = await fetch(`${ENDPOINT}/info`);
- const payload = await res.json();
- return payload;
-}
-class GetPayload {
- count;
- values;
-}
-var SortOrders;
-(function (SortOrders) {
- SortOrders[SortOrders["DeclarationAscending"] = 0] = "DeclarationAscending";
- SortOrders[SortOrders["DeclarationDescending"] = 1] = "DeclarationDescending";
- SortOrders[SortOrders["NameAscending"] = 2] = "NameAscending";
- SortOrders[SortOrders["NameDescending"] = 3] = "NameDescending";
-})(SortOrders || (SortOrders = {}));
-async function getRequest(limit, index, sort) {
- const res = await fetch(`${ENDPOINT}/get?limit=${limit}&index=${index}&sort=${sort.valueOf()}`);
- const payload = await res.json();
- return payload;
-}
-class State {
- entriesPerPage = 10;
- entryIndex = 0;
- maxEntries = 0;
- sort = SortOrders.DeclarationAscending;
- getEntriesPerPage() {
- return this.entriesPerPage;
- }
- setEntriesPerPage(entriesPerPage) {
- this.entriesPerPage = entriesPerPage;
- this.setEntryIndex(Math.floor(this.getEntryIndex() / entriesPerPage) * entriesPerPage);
- }
- getEntryIndex() {
- return this.entryIndex;
- }
- setEntryIndex(entryIndex) {
- this.entryIndex = entryIndex;
- this.updatePage();
- this.updateRange();
- this.updateListings();
- }
- getMaxEntries() {
- return this.maxEntries;
- }
- setMaxEntries(max) {
- this.maxEntries = max;
- }
- getSortOrder() {
- return this.sort;
- }
- setSortOrder(sortOrder) {
- this.sort = sortOrder;
- this.setEntryIndex(0);
- }
- updatePage() {
- let page = Math.ceil(((this.getEntryIndex() + this.getEntriesPerPage()) - 1) / this.getEntriesPerPage());
- document.getElementById("page-number").innerText = String(page);
- }
- updateRange() {
- let max = Math.min(this.getEntryIndex() + this.getEntriesPerPage(), this.getMaxEntries());
- document.getElementById("entry-counter").innerText = `${this.getEntryIndex() + 1}-${max} of ${this.getMaxEntries()}`;
- }
- updateListings() {
- getRequest(this.getEntriesPerPage(), this.getEntryIndex(), this.getSortOrder())
- .then(res => {
- let table = document.getElementById("pkg-list");
- table.innerHTML = '';
- for (let i = 0; i < res.count; i++) {
- table.appendChild(toHTML(res.values[i]));
- }
- });
- }
-}
-let STATE;
-function prevPage() {
- let index = STATE.getEntryIndex();
- STATE.setEntryIndex(Math.max(0, index - STATE.getEntriesPerPage()));
-}
-function nextPage() {
- let index = STATE.getEntryIndex();
- STATE.setEntryIndex(Math.min((Math.ceil(STATE.getMaxEntries() / STATE.getEntriesPerPage()) * STATE.getEntriesPerPage()) - STATE.getEntriesPerPage(), index + STATE.getEntriesPerPage()));
-}
-function escapeHtml(str) {
- return str
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''');
-}
-document.addEventListener("DOMContentLoaded", () => {
- STATE = new State();
- infoRequest()
- .then(res => {
- STATE.setMaxEntries(res.count);
- document.getElementById("hakurei-version").innerText = res.hakurei_version;
- STATE.updateRange();
- STATE.updateListings();
- });
- document.getElementById("count").addEventListener("change", (event) => {
- STATE.setEntriesPerPage(parseInt(event.target.value));
- });
- document.getElementById("sort").addEventListener("change", (event) => {
- STATE.setSortOrder(parseInt(event.target.value));
- });
-});
diff --git a/cmd/pkgserver/ui/static/light.css b/cmd/pkgserver/ui/static/light.css
deleted file mode 100644
index f275799..0000000
--- a/cmd/pkgserver/ui/static/light.css
+++ /dev/null
@@ -1,6 +0,0 @@
-@use 'common';
-html {
- background-color: #d3d3d3;
- color: black; }
-
-/*# sourceMappingURL=light.css.map */
diff --git a/cmd/pkgserver/ui/static/light.css.map b/cmd/pkgserver/ui/static/light.css.map
deleted file mode 100644
index 12bdf73..0000000
--- a/cmd/pkgserver/ui/static/light.css.map
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-"version": 3,
-"mappings": "AAAA,aAAa;AAEb,IAAK;EACH,gBAAgB,EAAE,OAAO;EACzB,KAAK,EAAE,KAAK",
-"sources": ["light.scss"],
-"names": [],
-"file": "light.css"
-}
diff --git a/cmd/pkgserver/ui_full.go b/cmd/pkgserver/ui_full.go
new file mode 100644
index 0000000..f9ca881
--- /dev/null
+++ b/cmd/pkgserver/ui_full.go
@@ -0,0 +1,9 @@
+//go:build frontend
+
+package main
+
+import "embed"
+
+//go:generate sh -c "sass ui/static/dark.scss ui/static/dark.css && sass ui/static/light.scss ui/static/light.css && tsc -p ui/static"
+//go:embed ui/*
+var content embed.FS
diff --git a/cmd/pkgserver/ui_stub.go b/cmd/pkgserver/ui_stub.go
new file mode 100644
index 0000000..bdd691d
--- /dev/null
+++ b/cmd/pkgserver/ui_stub.go
@@ -0,0 +1,7 @@
+//go:build !frontend
+
+package main
+
+import "testing/fstest"
+
+var content fstest.MapFS