diff --git a/server/web/web.go b/server/web/web.go
index 14fa98bb8..898c013fe 100644
--- a/server/web/web.go
+++ b/server/web/web.go
@@ -37,7 +37,8 @@ func New() *gin.Engine {
e.Use(setupCache)
h := http.FileServer(web.HTTPFS())
- e.GET("/favicon.svg", gin.WrapH(h))
+ e.GET("/favicon.svg", redirect("/favicons/favicon-light-default.svg", http.StatusPermanentRedirect))
+ e.GET("/favicons/*filepath", gin.WrapH(h))
e.GET("/assets/*filepath", gin.WrapH(h))
e.NoRoute(handleIndex)
@@ -45,6 +46,18 @@ func New() *gin.Engine {
return e
}
+// redirect return gin helper to redirect a request
+func redirect(location string, status ...int) func(ctx *gin.Context) {
+ return func(ctx *gin.Context) {
+ code := http.StatusFound
+ if len(status) == 1 {
+ code = status[0]
+ }
+
+ http.Redirect(ctx.Writer, ctx.Request, location, code)
+ }
+}
+
func handleIndex(c *gin.Context) {
rw := c.Writer
data := web.MustLookup("index.html")
diff --git a/web/index.html b/web/index.html
index 263a92f7a..fecf3cb87 100644
--- a/web/index.html
+++ b/web/index.html
@@ -2,7 +2,8 @@
-
+
+
Woodpecker
diff --git a/web/public/favicons/favicon-dark-default.png b/web/public/favicons/favicon-dark-default.png
new file mode 100644
index 000000000..b3a29a2ee
Binary files /dev/null and b/web/public/favicons/favicon-dark-default.png differ
diff --git a/web/public/favicons/favicon-dark-default.svg b/web/public/favicons/favicon-dark-default.svg
new file mode 100644
index 000000000..79b2d60f8
--- /dev/null
+++ b/web/public/favicons/favicon-dark-default.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/favicons/favicon-dark-error.png b/web/public/favicons/favicon-dark-error.png
new file mode 100644
index 000000000..f83f4a374
Binary files /dev/null and b/web/public/favicons/favicon-dark-error.png differ
diff --git a/web/public/favicons/favicon-dark-error.svg b/web/public/favicons/favicon-dark-error.svg
new file mode 100644
index 000000000..334808ae2
--- /dev/null
+++ b/web/public/favicons/favicon-dark-error.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/public/favicons/favicon-dark-pending.png b/web/public/favicons/favicon-dark-pending.png
new file mode 100644
index 000000000..02576c9bc
Binary files /dev/null and b/web/public/favicons/favicon-dark-pending.png differ
diff --git a/web/public/favicons/favicon-dark-pending.svg b/web/public/favicons/favicon-dark-pending.svg
new file mode 100644
index 000000000..efa389fa1
--- /dev/null
+++ b/web/public/favicons/favicon-dark-pending.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/public/favicons/favicon-dark-success.png b/web/public/favicons/favicon-dark-success.png
new file mode 100644
index 000000000..2b7c9e9a4
Binary files /dev/null and b/web/public/favicons/favicon-dark-success.png differ
diff --git a/web/public/favicons/favicon-dark-success.svg b/web/public/favicons/favicon-dark-success.svg
new file mode 100644
index 000000000..92b8b6a1d
--- /dev/null
+++ b/web/public/favicons/favicon-dark-success.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/public/favicons/favicon-light-default.png b/web/public/favicons/favicon-light-default.png
new file mode 100644
index 000000000..d55a0bf2d
Binary files /dev/null and b/web/public/favicons/favicon-light-default.png differ
diff --git a/web/public/favicon.svg b/web/public/favicons/favicon-light-default.svg
similarity index 100%
rename from web/public/favicon.svg
rename to web/public/favicons/favicon-light-default.svg
diff --git a/web/public/favicons/favicon-light-error.png b/web/public/favicons/favicon-light-error.png
new file mode 100644
index 000000000..d7fd34752
Binary files /dev/null and b/web/public/favicons/favicon-light-error.png differ
diff --git a/web/public/favicons/favicon-light-error.svg b/web/public/favicons/favicon-light-error.svg
new file mode 100644
index 000000000..6bedff96e
--- /dev/null
+++ b/web/public/favicons/favicon-light-error.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/public/favicons/favicon-light-pending.png b/web/public/favicons/favicon-light-pending.png
new file mode 100644
index 000000000..80fc2119b
Binary files /dev/null and b/web/public/favicons/favicon-light-pending.png differ
diff --git a/web/public/favicons/favicon-light-pending.svg b/web/public/favicons/favicon-light-pending.svg
new file mode 100644
index 000000000..5e2b99af6
--- /dev/null
+++ b/web/public/favicons/favicon-light-pending.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/public/favicons/favicon-light-success.png b/web/public/favicons/favicon-light-success.png
new file mode 100644
index 000000000..fb0ba77b5
Binary files /dev/null and b/web/public/favicons/favicon-light-success.png differ
diff --git a/web/public/favicons/favicon-light-success.svg b/web/public/favicons/favicon-light-success.svg
new file mode 100644
index 000000000..e2932a2e0
--- /dev/null
+++ b/web/public/favicons/favicon-light-success.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/src/App.vue b/web/src/App.vue
index d1b49080d..960bde1fa 100644
--- a/web/src/App.vue
+++ b/web/src/App.vue
@@ -39,7 +39,7 @@ export default defineComponent({
const notifications = useNotifications();
// eslint-disable-next-line promise/prefer-await-to-callbacks
apiClient.setErrorHandler((err) => {
- notifications.notify({ title: err.message || 'An unkown error occurred', type: 'error' });
+ notifications.notify({ title: err.message || 'An unknown error occurred', type: 'error' });
});
const blank = computed(() => route.meta.blank);
diff --git a/web/src/compositions/useFavicon.ts b/web/src/compositions/useFavicon.ts
new file mode 100644
index 000000000..e3271caba
--- /dev/null
+++ b/web/src/compositions/useFavicon.ts
@@ -0,0 +1,54 @@
+import { computed, ref, watch } from 'vue';
+
+import { useDarkMode } from '~/compositions/useDarkMode';
+import { BuildStatus } from '~/lib/api/types';
+
+const darkMode = computed(() => (useDarkMode().darkMode.value ? 'dark' : 'light'));
+
+type Status = 'default' | 'success' | 'pending' | 'error';
+const faviconStatus = ref('default');
+
+watch(
+ [darkMode, faviconStatus],
+ () => {
+ const faviconPNG = document.getElementById('favicon-png');
+ if (faviconPNG) {
+ (faviconPNG as HTMLLinkElement).href = `/favicons/favicon-${darkMode.value}-${faviconStatus.value}.png`;
+ }
+
+ const faviconSVG = document.getElementById('favicon-svg');
+ if (faviconSVG) {
+ (faviconSVG as HTMLLinkElement).href = `/favicons/favicon-${darkMode.value}-${faviconStatus.value}.svg`;
+ }
+ },
+ { immediate: true },
+);
+
+function convertStatus(status: BuildStatus): Status {
+ if (['blocked', 'declined', 'error', 'failure', 'killed'].includes(status)) {
+ return 'error';
+ }
+
+ if (['started', 'running', 'pending'].includes(status)) {
+ return 'pending';
+ }
+
+ if (['success', 'declined', 'error', 'failure', 'killed'].includes(status)) {
+ return 'success';
+ }
+
+ // skipped
+ return 'default';
+}
+
+export function useFavicon() {
+ return {
+ updateStatus(status?: BuildStatus | 'default') {
+ if (status === undefined || status === 'default') {
+ faviconStatus.value = 'default';
+ } else {
+ faviconStatus.value = convertStatus(status);
+ }
+ },
+ };
+}
diff --git a/web/src/main.ts b/web/src/main.ts
index 61cbb2e17..69fc06ced 100644
--- a/web/src/main.ts
+++ b/web/src/main.ts
@@ -1,4 +1,5 @@
import 'windi.css';
+import '~/compositions/useFavicon';
import { createPinia } from 'pinia';
import { createApp } from 'vue';
diff --git a/web/src/views/repo/RepoBuild.vue b/web/src/views/repo/RepoBuild.vue
index 298f98c7d..6002b4e2f 100644
--- a/web/src/views/repo/RepoBuild.vue
+++ b/web/src/views/repo/RepoBuild.vue
@@ -69,7 +69,7 @@