detached faf993e

Enhances container/image management ui and refactor updates notification functionality

- Implement dynamic message templating as const
- Refactor JS version check logic using arrow functions for cleaner syntax
- Introduce wiggle animation to the update notification nerdfont icon
- Improve user experience for the container and image list with added hover effects
- Correct HTMX response targeting to handle dynamic responses accurately
commit
faf993e85ee8726b865573bd59995a055b50ba75
author
bnema · · 2023-11-06 09:54 UTC
parent
efb1aec
signature
unsigned
6 files · +97 -76
M internal/templating/models/html/admin/manager.gohtml +0 -4
@@ -16,10 +16,8 @@
1616 hx-trigger="load"
1717 hx-swap="innerHTML"
1818 hx-swap-oob="true"
19- hx-target-error="container-manager-error"
2019 hx-poll="5s"
2120 ></div>
22- <div id="container-manager-error" hidden></div>
2321 <!-- List all the images -->
2422 <div
2523 id="image-manager"
@@ -27,10 +25,8 @@
2725 hx-trigger="load"
2826 hx-swap="innerHTML"
2927 hx-swap-oob="true"
30- hx-target-error="image-manager-error"
3128 hx-poll="5s"
3229 ></div>
33- <div id="image-manager-error" hidden></div>
3430 </div>
3531 </div>
3632
@@ -16,10 +16,8 @@
16 hx-trigger="load"16 hx-trigger="load"
17 hx-swap="innerHTML"17 hx-swap="innerHTML"
18 hx-swap-oob="true"18 hx-swap-oob="true"
19- hx-target-error="container-manager-error"
20 hx-poll="5s"19 hx-poll="5s"
21 ></div>20 ></div>
22- <div id="container-manager-error" hidden></div>
23 <!-- List all the images -->21 <!-- List all the images -->
24 <div22 <div
25 id="image-manager"23 id="image-manager"
@@ -27,10 +25,8 @@
27 hx-trigger="load"25 hx-trigger="load"
28 hx-swap="innerHTML"26 hx-swap="innerHTML"
29 hx-swap-oob="true"27 hx-swap-oob="true"
30- hx-target-error="image-manager-error"
31 hx-poll="5s"28 hx-poll="5s"
32 ></div>29 ></div>
33- <div id="image-manager-error" hidden></div>
34 </div>30 </div>
35 </div>31 </div>
36 32
M internal/templating/models/html/fragments/containerlist.gohtml +16 -18
@@ -1,10 +1,8 @@
1-<div id="actionResponse"></div>
2-
3-<div class="flex flex-col p-4" hx-boost="true">
1+<div class="flex flex-col p-4" hx-ext="response-targets">
42 <h1 class="text-lg md:text-xl font-bold text-gordonmascot-hblue">
53 {{index .Lang "manager" "content" "h1-containers"}}
64 </h1>
7- <table class="p-4 border-t-2 table-auto" hx-ext="response-targets">
5+ <table class="p-4 border-t-2 table-auto">
86 <thead>
97 <tr class="text-center text-gray-400">
108 <th>{{index .Lang "manager" "content" "thName"}}</th>
@@ -15,11 +13,11 @@
1513 </tr>
1614 </thead>
1715 <tbody>
18- {{ $Lang := .Lang }}
16+ {{ $Lang := .Lang }}
1917 {{
2018 range.containers
2119 }}
22- <tr id="container-{{.ID}}" class="text-center">
20+ <tr id="container-{{.ID}}" class="text-center hover:bg-slate-100">
2321 <td>
2422 <span
2523 title="{{.Container.ID}}"
@@ -31,11 +29,10 @@
3129 <td class="status-container">{{.Status}} (Up since {{.UpSince}})</td>
3230 <td style="color: {{.StateColor}};">{{.State}}</td>
3331 <td>
34- <!-- iconoir start -->
3532 <button
3633 id="start-button-{{.ID}}"
3734 hx-post="/htmx/container-manager/start/{{.ID}}"
38- hx-target="#actionResponse"
35+ hx-target="#actionResponse-{{.ID}}"
3936 hx-target-*="#error-message-{{.ID}}"
4037 hx-swap="outerHTML"
4138 class="py-2 px-4"
@@ -46,11 +43,12 @@
4643 <button
4744 id="edit-button-{{.ID}}"
4845 hx-get="/htmx/container-manager/edit/{{.ID}}"
49- hx-target="#actionResponse"
46+ hx-target="#actionResponse-{{.ID}}"
5047 hx-target-*="#error-message-{{.ID}}"
5148 hx-swap="innerHTML"
5249 class="py-2 px-4"
5350 data-active="false"
51+ _="on click toggle between .visible and .hidden on #actionResponse-{{.ID}}"
5452 >
5553 <span title="Edit the container" class="iconf text-2xl text-blue-600 hover:text-gordonmascot-hblue">󱩽</span>
5654 </button>
@@ -58,7 +56,7 @@
5856 <button
5957 id="stop-button-{{.ID}}"
6058 hx-post="/htmx/container-manager/stop/{{.ID}}"
61- hx-target="#actionResponse"
59+ hx-target="#actionResponse-{{.ID}}"
6260 hx-target-*="#error-message-{{.ID}}"
6361 hx-swap="innerHTML"
6462 hx-confirm="{{index $Lang "manager" "content" "confirm-container-stop"}}"
@@ -70,7 +68,7 @@
7068 <button
7169 id="remove-button-img-{{.ID}}"
7270 hx-delete="/htmx/container-manager/delete/{{.ID}}"
73- hx-target="#actionResponse"
71+ hx-target="#actionResponse-{{.ID}}"
7472 hx-target-*="#error-message-{{.ID}}"
7573 hx-swap="innerHTML"
7674 hx-confirm="{{index $Lang "manager" "content" "confirm-delete"}}"
@@ -80,20 +78,20 @@
8078 <span title="Remove the container" class="iconf text-xl text-red-600 hover:text-red-900"></span>
8179 </button>
8280 </td>
83- </tr>
84- <tr>
85- <td colspan="7">
86- <div id="edit-container-{{.ID}}"></div>
87- </td>
88- </tr>
81+ </tr>
8982 <tr>
9083 <td colspan="7">
84+ <div id="actionResponse-{{.ID}}" hidden></div>
9185 <div id="error-message-{{.ID}}" class="text-red-500"></div>
9286 </td>
9387 </tr>
9488 {{ else }}
9589 <tr>
96- <td colspan="7" class="mt-2">{{index $Lang "manager" "content" "no-containers"}}.</td>
90+ <td colspan="7" class="mt-2">
91+ <p class="text-center text-gray-400 text-xl">
92+ {{index $Lang "manager" "content" "no-containers"}}
93+ </p>
94+ </td>
9795 </tr>
9896 {{
9997 end
@@ -1,10 +1,8 @@
1-<div id="actionResponse"></div>1+<div class="flex flex-col p-4" hx-ext="response-targets">
2-
3-<div class="flex flex-col p-4" hx-boost="true">
4 <h1 class="text-lg md:text-xl font-bold text-gordonmascot-hblue">2 <h1 class="text-lg md:text-xl font-bold text-gordonmascot-hblue">
5 {{index .Lang "manager" "content" "h1-containers"}}3 {{index .Lang "manager" "content" "h1-containers"}}
6 </h1>4 </h1>
7- <table class="p-4 border-t-2 table-auto" hx-ext="response-targets">5+ <table class="p-4 border-t-2 table-auto">
8 <thead>6 <thead>
9 <tr class="text-center text-gray-400">7 <tr class="text-center text-gray-400">
10 <th>{{index .Lang "manager" "content" "thName"}}</th>8 <th>{{index .Lang "manager" "content" "thName"}}</th>
@@ -15,11 +13,11 @@
15 </tr>13 </tr>
16 </thead>14 </thead>
17 <tbody>15 <tbody>
18- {{ $Lang := .Lang }} 16+ {{ $Lang := .Lang }}
19 {{17 {{
20 range.containers18 range.containers
21 }}19 }}
22- <tr id="container-{{.ID}}" class="text-center">20+ <tr id="container-{{.ID}}" class="text-center hover:bg-slate-100">
23 <td>21 <td>
24 <span22 <span
25 title="{{.Container.ID}}"23 title="{{.Container.ID}}"
@@ -31,11 +29,10 @@
31 <td class="status-container">{{.Status}} (Up since {{.UpSince}})</td>29 <td class="status-container">{{.Status}} (Up since {{.UpSince}})</td>
32 <td style="color: {{.StateColor}};">{{.State}}</td>30 <td style="color: {{.StateColor}};">{{.State}}</td>
33 <td>31 <td>
34- <!-- iconoir start -->
35 <button32 <button
36 id="start-button-{{.ID}}"33 id="start-button-{{.ID}}"
37 hx-post="/htmx/container-manager/start/{{.ID}}"34 hx-post="/htmx/container-manager/start/{{.ID}}"
38- hx-target="#actionResponse"35+ hx-target="#actionResponse-{{.ID}}"
39 hx-target-*="#error-message-{{.ID}}"36 hx-target-*="#error-message-{{.ID}}"
40 hx-swap="outerHTML"37 hx-swap="outerHTML"
41 class="py-2 px-4"38 class="py-2 px-4"
@@ -46,11 +43,12 @@
46 <button43 <button
47 id="edit-button-{{.ID}}"44 id="edit-button-{{.ID}}"
48 hx-get="/htmx/container-manager/edit/{{.ID}}"45 hx-get="/htmx/container-manager/edit/{{.ID}}"
49- hx-target="#actionResponse"46+ hx-target="#actionResponse-{{.ID}}"
50 hx-target-*="#error-message-{{.ID}}"47 hx-target-*="#error-message-{{.ID}}"
51 hx-swap="innerHTML"48 hx-swap="innerHTML"
52 class="py-2 px-4"49 class="py-2 px-4"
53 data-active="false"50 data-active="false"
51+ _="on click toggle between .visible and .hidden on #actionResponse-{{.ID}}"
54 >52 >
55 <span title="Edit the container" class="iconf text-2xl text-blue-600 hover:text-gordonmascot-hblue">󱩽</span>53 <span title="Edit the container" class="iconf text-2xl text-blue-600 hover:text-gordonmascot-hblue">󱩽</span>
56 </button>54 </button>
@@ -58,7 +56,7 @@
58 <button56 <button
59 id="stop-button-{{.ID}}"57 id="stop-button-{{.ID}}"
60 hx-post="/htmx/container-manager/stop/{{.ID}}"58 hx-post="/htmx/container-manager/stop/{{.ID}}"
61- hx-target="#actionResponse"59+ hx-target="#actionResponse-{{.ID}}"
62 hx-target-*="#error-message-{{.ID}}"60 hx-target-*="#error-message-{{.ID}}"
63 hx-swap="innerHTML"61 hx-swap="innerHTML"
64 hx-confirm="{{index $Lang "manager" "content" "confirm-container-stop"}}"62 hx-confirm="{{index $Lang "manager" "content" "confirm-container-stop"}}"
@@ -70,7 +68,7 @@
70 <button68 <button
71 id="remove-button-img-{{.ID}}"69 id="remove-button-img-{{.ID}}"
72 hx-delete="/htmx/container-manager/delete/{{.ID}}"70 hx-delete="/htmx/container-manager/delete/{{.ID}}"
73- hx-target="#actionResponse"71+ hx-target="#actionResponse-{{.ID}}"
74 hx-target-*="#error-message-{{.ID}}"72 hx-target-*="#error-message-{{.ID}}"
75 hx-swap="innerHTML"73 hx-swap="innerHTML"
76 hx-confirm="{{index $Lang "manager" "content" "confirm-delete"}}"74 hx-confirm="{{index $Lang "manager" "content" "confirm-delete"}}"
@@ -80,20 +78,20 @@
80 <span title="Remove the container" class="iconf text-xl text-red-600 hover:text-red-900"></span>78 <span title="Remove the container" class="iconf text-xl text-red-600 hover:text-red-900"></span>
81 </button>79 </button>
82 </td>80 </td>
83- </tr>81+ </tr>
84- <tr>
85- <td colspan="7">
86- <div id="edit-container-{{.ID}}"></div>
87- </td>
88- </tr>
89 <tr>82 <tr>
90 <td colspan="7">83 <td colspan="7">
84+ <div id="actionResponse-{{.ID}}" hidden></div>
91 <div id="error-message-{{.ID}}" class="text-red-500"></div>85 <div id="error-message-{{.ID}}" class="text-red-500"></div>
92 </td>86 </td>
93 </tr>87 </tr>
94 {{ else }}88 {{ else }}
95 <tr>89 <tr>
96- <td colspan="7" class="mt-2">{{index $Lang "manager" "content" "no-containers"}}.</td>90+ <td colspan="7" class="mt-2">
91+ <p class="text-center text-gray-400 text-xl">
92+ {{index $Lang "manager" "content" "no-containers"}}
93+ </p>
94+ </td>
97 </tr>95 </tr>
98 {{96 {{
99 end97 end
M internal/templating/models/html/fragments/imagelist.gohtml +6 -6
@@ -9,19 +9,20 @@
99 hx-target="#upload-image"
1010 hx-swap="innerHTML"
1111 title="Manually upload a .tar container image"
12- _="on click toggle between .visible and .hidden on #upload-image then add .hidden to #create-container"
12+ _="on click toggle between .visible and .hidden on #upload-image then add .hidden to .create-container"
1313 >
1414
1515 <span class="iconf text-xl align-middle">󰭾 </span>
1616 Upload
1717 </button>
1818 </div>
19+ <div id="upload-image"></div>
1920 <table class="p-4 border-t-2">
2021 <tbody>
2122 {{
2223 range.Images
2324 }}
24- <tr id="img-{{.ID}}" hx-ext="response-targets">
25+ <tr id="img-{{.ID}}" hx-ext="response-targets" class=" hover:bg-slate-100">
2526 <td>
2627 <span title="{{.ImageSummary.ID}}">{{.Name}}</span>
2728 </td>
@@ -34,11 +35,11 @@
3435 <button
3536 id="add-button-img-{{.ID}}"
3637 hx-get="/htmx/create-container/{{.ID}}"
37- hx-target="#create-container"
38+ hx-target="#create-container-{{.ID}}"
3839 hx-target-*="#error-message-div-{{.ID}}"
3940 hx-swap="innerHTML"
4041 class="py-2 px-4"
41- _="on click toggle between .visible and .hidden on #create-container then add .hidden to #upload-image"
42+ _="on click toggle between .visible and .hidden on #create-container-{{.ID}} then add .hidden to #upload-image"
4243 >
4344 <span title="Deploy a container using this image" class="iconf text-xl text-blue-600 hover:text-gordonmascot-hblue"></span>
4445 </button>
@@ -59,6 +60,7 @@
5960 <t>
6061 <td colspan="4">
6162 <div id="error-message-div-{{.ID}}" class="text-red-500"></div>
63+ <div class="create-container" id="create-container-{{.ID}}"></div>
6264 </td>
6365 </tr>
6466 {{ else }}
@@ -70,6 +72,4 @@
7072 }}
7173 </tbody>
7274 </table>
73- <div id="upload-image"></div>
74- <div id="create-container"></div>
7575 </div>
@@ -9,19 +9,20 @@
9 hx-target="#upload-image"9 hx-target="#upload-image"
10 hx-swap="innerHTML"10 hx-swap="innerHTML"
11 title="Manually upload a .tar container image"11 title="Manually upload a .tar container image"
12- _="on click toggle between .visible and .hidden on #upload-image then add .hidden to #create-container"12+ _="on click toggle between .visible and .hidden on #upload-image then add .hidden to .create-container"
13 >13 >
14 14
15 <span class="iconf text-xl align-middle">󰭾 </span>15 <span class="iconf text-xl align-middle">󰭾 </span>
16 Upload16 Upload
17 </button>17 </button>
18 </div>18 </div>
19+ <div id="upload-image"></div>
19 <table class="p-4 border-t-2">20 <table class="p-4 border-t-2">
20 <tbody>21 <tbody>
21 {{22 {{
22 range.Images23 range.Images
23 }}24 }}
24- <tr id="img-{{.ID}}" hx-ext="response-targets">25+ <tr id="img-{{.ID}}" hx-ext="response-targets" class=" hover:bg-slate-100">
25 <td>26 <td>
26 <span title="{{.ImageSummary.ID}}">{{.Name}}</span>27 <span title="{{.ImageSummary.ID}}">{{.Name}}</span>
27 </td>28 </td>
@@ -34,11 +35,11 @@
34 <button35 <button
35 id="add-button-img-{{.ID}}"36 id="add-button-img-{{.ID}}"
36 hx-get="/htmx/create-container/{{.ID}}"37 hx-get="/htmx/create-container/{{.ID}}"
37- hx-target="#create-container"38+ hx-target="#create-container-{{.ID}}"
38 hx-target-*="#error-message-div-{{.ID}}"39 hx-target-*="#error-message-div-{{.ID}}"
39 hx-swap="innerHTML"40 hx-swap="innerHTML"
40 class="py-2 px-4"41 class="py-2 px-4"
41- _="on click toggle between .visible and .hidden on #create-container then add .hidden to #upload-image"42+ _="on click toggle between .visible and .hidden on #create-container-{{.ID}} then add .hidden to #upload-image"
42 >43 >
43 <span title="Deploy a container using this image" class="iconf text-xl text-blue-600 hover:text-gordonmascot-hblue"></span>44 <span title="Deploy a container using this image" class="iconf text-xl text-blue-600 hover:text-gordonmascot-hblue"></span>
44 </button>45 </button>
@@ -59,6 +60,7 @@
59 <t>60 <t>
60 <td colspan="4">61 <td colspan="4">
61 <div id="error-message-div-{{.ID}}" class="text-red-500"></div>62 <div id="error-message-div-{{.ID}}" class="text-red-500"></div>
63+ <div class="create-container" id="create-container-{{.ID}}"></div>
62 </td>64 </td>
63 </tr>65 </tr>
64 {{ else }}66 {{ else }}
@@ -70,6 +72,4 @@
70 }}72 }}
71 </tbody>73 </tbody>
72 </table>74 </table>
73- <div id="upload-image"></div>
74- <div id="create-container"></div>
75 </div>75 </div>
M internal/templating/models/html/fragments/menu.gohtml +23 -20
@@ -1,34 +1,38 @@
11 <script>
2- function checkVersion(versionData) {
3- var currentVersion = document
2+ const updateStrTemplate =
3+ "New version %VERSION% available, consider pulling the latest image";
4+
5+ const checkVersion = (versionData) => {
6+ const currentVersion = document
47 .getElementById("actual-version")
58 .textContent.trim();
6- // Extract the numeric part of the version string using a regular expression
7- var fetchedVersionNumber =
8- versionData["amd64"]["name"].match(/\d+\.\d+\.\d+/)[0];
9+ const fetchedVersionNumber =
10+ versionData?.amd64?.name.match(/\d+\.\d+\.\d+/)?.[0];
911
1012 if (currentVersion !== fetchedVersionNumber) {
11- console.log("New version available:", fetchedVersionNumber);
12- // Show the update-available span by removing the 'hidden' attribute
13- document.getElementById("update-available").removeAttribute("hidden");
13+ const updateStr = updateStrTemplate.replace(
14+ "%VERSION%",
15+ fetchedVersionNumber
16+ );
17+ console.log(updateStr);
18+ const updateElement = document.getElementById("update-available");
19+ updateElement.title = updateStr; // Set the title attribute
20+ updateElement.removeAttribute("hidden");
1421 } else {
1522 console.log(
1623 "No new version. Current version is up-to-date:",
1724 currentVersion
1825 );
1926 }
20- }
27+ };
2128
22- function fetchVersionInfo() {
29+ const fetchVersionInfo = () => {
2330 fetch("https://gordon-proxy.bnema.dev/version")
2431 .then((response) => response.json())
25- .then((data) => {
26- checkVersion(data); // Pass the parsed JSON data to checkVersion
27- })
32+ .then(checkVersion)
2833 .catch((error) => console.error("Error fetching version:", error));
29- }
34+ };
3035
31- // Run fetchVersionInfo when the window loads
3236 window.onload = fetchVersionInfo;
3337 </script>
3438 <div
@@ -41,16 +45,15 @@
4145 </h1>
4246 <span
4347 id="actual-version"
44- class="text-gordonmascot-mblue text-xs md:text-sm">
48+ class="text-gordonmascot-mblue text-xs md:text-sm pr-1 JetBrainsMonoNF">
4549 {{ .BuildVersion }}
4650 </span>
47- <!-- if .LatestVersion is > to BuildVersion show arrow up -->
4851 <span
4952 hidden
5053 id="update-available"
51- class="text-teal-200 text-xs md:text-sm"
52- title="A new version is available"
53- ><a href="{{ .LatestVersionURL }}">󰨦</a>
54+ class="text-green-300 text-xs md:text-sm JetBrainsMonoNF"
55+ title=""
56+ ><a href="https://github.com/bnema/gordon/releases">󰨦</a>
5457 </span>
5558 </div>
5659 <div class="w-full">
@@ -1,34 +1,38 @@
1 <script>1 <script>
2- function checkVersion(versionData) {2+ const updateStrTemplate =
3- var currentVersion = document3+ "New version %VERSION% available, consider pulling the latest image";
4+
5+ const checkVersion = (versionData) => {
6+ const currentVersion = document
4 .getElementById("actual-version")7 .getElementById("actual-version")
5 .textContent.trim();8 .textContent.trim();
6- // Extract the numeric part of the version string using a regular expression9+ const fetchedVersionNumber =
7- var fetchedVersionNumber =10+ versionData?.amd64?.name.match(/\d+\.\d+\.\d+/)?.[0];
8- versionData["amd64"]["name"].match(/\d+\.\d+\.\d+/)[0];
9 11
10 if (currentVersion !== fetchedVersionNumber) {12 if (currentVersion !== fetchedVersionNumber) {
11- console.log("New version available:", fetchedVersionNumber);13+ const updateStr = updateStrTemplate.replace(
12- // Show the update-available span by removing the 'hidden' attribute14+ "%VERSION%",
13- document.getElementById("update-available").removeAttribute("hidden");15+ fetchedVersionNumber
16+ );
17+ console.log(updateStr);
18+ const updateElement = document.getElementById("update-available");
19+ updateElement.title = updateStr; // Set the title attribute
20+ updateElement.removeAttribute("hidden");
14 } else {21 } else {
15 console.log(22 console.log(
16 "No new version. Current version is up-to-date:",23 "No new version. Current version is up-to-date:",
17 currentVersion24 currentVersion
18 );25 );
19 }26 }
20- }27+ };
21 28
22- function fetchVersionInfo() {29+ const fetchVersionInfo = () => {
23 fetch("https://gordon-proxy.bnema.dev/version")30 fetch("https://gordon-proxy.bnema.dev/version")
24 .then((response) => response.json())31 .then((response) => response.json())
25- .then((data) => {32+ .then(checkVersion)
26- checkVersion(data); // Pass the parsed JSON data to checkVersion
27- })
28 .catch((error) => console.error("Error fetching version:", error));33 .catch((error) => console.error("Error fetching version:", error));
29- }34+ };
30 35
31- // Run fetchVersionInfo when the window loads
32 window.onload = fetchVersionInfo;36 window.onload = fetchVersionInfo;
33 </script>37 </script>
34 <div38 <div
@@ -41,16 +45,15 @@
41 </h1>45 </h1>
42 <span46 <span
43 id="actual-version"47 id="actual-version"
44- class="text-gordonmascot-mblue text-xs md:text-sm">48+ class="text-gordonmascot-mblue text-xs md:text-sm pr-1 JetBrainsMonoNF">
45 {{ .BuildVersion }}49 {{ .BuildVersion }}
46 </span>50 </span>
47- <!-- if .LatestVersion is > to BuildVersion show arrow up -->
48 <span51 <span
49 hidden52 hidden
50 id="update-available"53 id="update-available"
51- class="text-teal-200 text-xs md:text-sm"54+ class="text-green-300 text-xs md:text-sm JetBrainsMonoNF"
52- title="A new version is available"55+ title=""
53- ><a href="{{ .LatestVersionURL }}">󰨦</a>56+ ><a href="https://github.com/bnema/gordon/releases">󰨦</a>
54 </span>57 </span>
55 </div>58 </div>
56 <div class="w-full">59 <div class="w-full">
M internal/webui/public/assets/css/custom.css +18 -0
@@ -80,6 +80,24 @@
8080 }
8181 }
8282
83+@keyframes wiggle {
84+
85+ 0%,
86+ 100% {
87+ transform: rotate(-3deg);
88+ }
89+
90+ 50% {
91+ transform: rotate(3deg);
92+ }
93+}
94+
95+#update-available {
96+ display: inline-block;
97+ /* Make sure the span is transformable */
98+ animation: wiggle 1s ease-in-out infinite;
99+}
100+
83101 .slide-it {
84102 view-transition-name: slide-it;
85103 }
@@ -80,6 +80,24 @@
80 }80 }
81 }81 }
82 82
83+@keyframes wiggle {
84+
85+ 0%,
86+ 100% {
87+ transform: rotate(-3deg);
88+ }
89+
90+ 50% {
91+ transform: rotate(3deg);
92+ }
93+}
94+
95+#update-available {
96+ display: inline-block;
97+ /* Make sure the span is transformable */
98+ animation: wiggle 1s ease-in-out infinite;
99+}
100+
83 .slide-it {101 .slide-it {
84 view-transition-name: slide-it;102 view-transition-name: slide-it;
85 }103 }
M internal/webui/public/assets/css/tailwind.css +34 -28
@@ -608,10 +608,6 @@ video {
608608 margin-left: 0.5rem;
609609 }
610610
611-.mr-2 {
612- margin-right: 0.5rem;
613-}
614-
615611 .mt-2 {
616612 margin-top: 0.5rem;
617613 }
@@ -772,6 +768,10 @@ video {
772768 border-radius: 0.125rem;
773769 }
774770
771+.rounded-lg {
772+ border-radius: 0.5rem;
773+}
774+
775775 .border {
776776 border-width: 1px;
777777 }
@@ -982,14 +982,14 @@ video {
982982 color: rgb(107 114 128 / var(--tw-text-opacity));
983983 }
984984
985-.text-gray-600 {
985+.text-gray-800 {
986986 --tw-text-opacity: 1;
987- color: rgb(75 85 99 / var(--tw-text-opacity));
987+ color: rgb(31 41 55 / var(--tw-text-opacity));
988988 }
989989
990-.text-gray-800 {
990+.text-green-300 {
991991 --tw-text-opacity: 1;
992- color: rgb(31 41 55 / var(--tw-text-opacity));
992+ color: rgb(134 239 172 / var(--tw-text-opacity));
993993 }
994994
995995 .text-red-500 {
@@ -1007,31 +1007,11 @@ video {
10071007 color: rgb(241 245 249 / var(--tw-text-opacity));
10081008 }
10091009
1010-.text-teal-600 {
1011- --tw-text-opacity: 1;
1012- color: rgb(13 148 136 / var(--tw-text-opacity));
1013-}
1014-
10151010 .text-white {
10161011 --tw-text-opacity: 1;
10171012 color: rgb(255 255 255 / var(--tw-text-opacity));
10181013 }
10191014
1020-.text-teal-300 {
1021- --tw-text-opacity: 1;
1022- color: rgb(94 234 212 / var(--tw-text-opacity));
1023-}
1024-
1025-.text-teal-400 {
1026- --tw-text-opacity: 1;
1027- color: rgb(45 212 191 / var(--tw-text-opacity));
1028-}
1029-
1030-.text-teal-200 {
1031- --tw-text-opacity: 1;
1032- color: rgb(153 246 228 / var(--tw-text-opacity));
1033-}
1034-
10351015 .shadow {
10361016 --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
10371017 --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
@@ -1140,6 +1120,23 @@ video {
11401120 }
11411121 }
11421122
1123+@keyframes wiggle {
1124+ 0%,
1125+ 100% {
1126+ transform: rotate(-3deg);
1127+ }
1128+
1129+ 50% {
1130+ transform: rotate(3deg);
1131+ }
1132+}
1133+
1134+#update-available {
1135+ display: inline-block;
1136+ /* Make sure the span is transformable */
1137+ animation: wiggle 1s ease-in-out infinite;
1138+}
1139+
11431140 .slide-it {
11441141 view-transition-name: slide-it;
11451142 }
@@ -1172,6 +1169,10 @@ video {
11721169 font-family: "JetBrainsMono-Nerd-Font-Bold", sans-serif;
11731170 }
11741171
1172+.hover\:rounded-lg:hover {
1173+ border-radius: 0.5rem;
1174+}
1175+
11751176 .hover\:bg-blue-700:hover {
11761177 --tw-bg-opacity: 1;
11771178 background-color: rgb(29 78 216 / var(--tw-bg-opacity));
@@ -1182,6 +1183,11 @@ video {
11821183 background-color: rgb(0 82 147 / var(--tw-bg-opacity));
11831184 }
11841185
1186+.hover\:bg-slate-100:hover {
1187+ --tw-bg-opacity: 1;
1188+ background-color: rgb(241 245 249 / var(--tw-bg-opacity));
1189+}
1190+
11851191 .hover\:text-gordonmascot-hblue:hover {
11861192 --tw-text-opacity: 1;
11871193 color: rgb(1 30 92 / var(--tw-text-opacity));
@@ -608,10 +608,6 @@ video {
608 margin-left: 0.5rem;608 margin-left: 0.5rem;
609 }609 }
610 610
611-.mr-2 {
612- margin-right: 0.5rem;
613-}
614-
615 .mt-2 {611 .mt-2 {
616 margin-top: 0.5rem;612 margin-top: 0.5rem;
617 }613 }
@@ -772,6 +768,10 @@ video {
772 border-radius: 0.125rem;768 border-radius: 0.125rem;
773 }769 }
774 770
771+.rounded-lg {
772+ border-radius: 0.5rem;
773+}
774+
775 .border {775 .border {
776 border-width: 1px;776 border-width: 1px;
777 }777 }
@@ -982,14 +982,14 @@ video {
982 color: rgb(107 114 128 / var(--tw-text-opacity));982 color: rgb(107 114 128 / var(--tw-text-opacity));
983 }983 }
984 984
985-.text-gray-600 {985+.text-gray-800 {
986 --tw-text-opacity: 1;986 --tw-text-opacity: 1;
987- color: rgb(75 85 99 / var(--tw-text-opacity));987+ color: rgb(31 41 55 / var(--tw-text-opacity));
988 }988 }
989 989
990-.text-gray-800 {990+.text-green-300 {
991 --tw-text-opacity: 1;991 --tw-text-opacity: 1;
992- color: rgb(31 41 55 / var(--tw-text-opacity));992+ color: rgb(134 239 172 / var(--tw-text-opacity));
993 }993 }
994 994
995 .text-red-500 {995 .text-red-500 {
@@ -1007,31 +1007,11 @@ video {
1007 color: rgb(241 245 249 / var(--tw-text-opacity));1007 color: rgb(241 245 249 / var(--tw-text-opacity));
1008 }1008 }
1009 1009
1010-.text-teal-600 {
1011- --tw-text-opacity: 1;
1012- color: rgb(13 148 136 / var(--tw-text-opacity));
1013-}
1014-
1015 .text-white {1010 .text-white {
1016 --tw-text-opacity: 1;1011 --tw-text-opacity: 1;
1017 color: rgb(255 255 255 / var(--tw-text-opacity));1012 color: rgb(255 255 255 / var(--tw-text-opacity));
1018 }1013 }
1019 1014
1020-.text-teal-300 {
1021- --tw-text-opacity: 1;
1022- color: rgb(94 234 212 / var(--tw-text-opacity));
1023-}
1024-
1025-.text-teal-400 {
1026- --tw-text-opacity: 1;
1027- color: rgb(45 212 191 / var(--tw-text-opacity));
1028-}
1029-
1030-.text-teal-200 {
1031- --tw-text-opacity: 1;
1032- color: rgb(153 246 228 / var(--tw-text-opacity));
1033-}
1034-
1035 .shadow {1015 .shadow {
1036 --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);1016 --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
1037 --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);1017 --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
@@ -1140,6 +1120,23 @@ video {
1140 }1120 }
1141 }1121 }
1142 1122
1123+@keyframes wiggle {
1124+ 0%,
1125+ 100% {
1126+ transform: rotate(-3deg);
1127+ }
1128+
1129+ 50% {
1130+ transform: rotate(3deg);
1131+ }
1132+}
1133+
1134+#update-available {
1135+ display: inline-block;
1136+ /* Make sure the span is transformable */
1137+ animation: wiggle 1s ease-in-out infinite;
1138+}
1139+
1143 .slide-it {1140 .slide-it {
1144 view-transition-name: slide-it;1141 view-transition-name: slide-it;
1145 }1142 }
@@ -1172,6 +1169,10 @@ video {
1172 font-family: "JetBrainsMono-Nerd-Font-Bold", sans-serif;1169 font-family: "JetBrainsMono-Nerd-Font-Bold", sans-serif;
1173 }1170 }
1174 1171
1172+.hover\:rounded-lg:hover {
1173+ border-radius: 0.5rem;
1174+}
1175+
1175 .hover\:bg-blue-700:hover {1176 .hover\:bg-blue-700:hover {
1176 --tw-bg-opacity: 1;1177 --tw-bg-opacity: 1;
1177 background-color: rgb(29 78 216 / var(--tw-bg-opacity));1178 background-color: rgb(29 78 216 / var(--tw-bg-opacity));
@@ -1182,6 +1183,11 @@ video {
1182 background-color: rgb(0 82 147 / var(--tw-bg-opacity));1183 background-color: rgb(0 82 147 / var(--tw-bg-opacity));
1183 }1184 }
1184 1185
1186+.hover\:bg-slate-100:hover {
1187+ --tw-bg-opacity: 1;
1188+ background-color: rgb(241 245 249 / var(--tw-bg-opacity));
1189+}
1190+
1185 .hover\:text-gordonmascot-hblue:hover {1191 .hover\:text-gordonmascot-hblue:hover {
1186 --tw-text-opacity: 1;1192 --tw-text-opacity: 1;
1187 color: rgb(1 30 92 / var(--tw-text-opacity));1193 color: rgb(1 30 92 / var(--tw-text-opacity));

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close