diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index 3dfc2f53..d1eb9b62 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -1000,7 +1000,7 @@ jobs: - name: Install hanami run: | - helm install --set docker.tag="${{ env.BRANCH_NAME }}" --set user.id=asdf --set user.name="test user" --set user.passphrase="asdfasdf" --set token.data="this is a test-token" --set api.domain=local-hanami ainari /tmp/ainari_helm_build_result/ainari-$HELM_VERSION.tgz + helm install --set docker.tag="${{ env.BRANCH_NAME }}" --set miko.user.id=asdf --set miko.user.name="test user" --set miko.user.passphrase="asdfasdf" --set miko.token.data="this is a test-token" ainari /tmp/ainari_helm_build_result/ainari-$HELM_VERSION.tgz - name: Sleep for 120 seconds uses: jakejarvis/wait-action@919fc193e07906705e5b7a50f90ea9e74d20b2b0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 788cfd63..1e61dc82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,23 @@ - image pull-policy can now be changed by the helm-values - support other namespaces thand "default" - support for multiple sakura instances +- dashboard: + - added error-popups + - validation of input-fields and error-message for invalid inputs + - added cluster-list without actions to overview-page + - automatic logout in case the token is expired ### Changed - changed the image-build process for the new vagrant-test-setup - sakura and onsen are now statefulsets within the kubernetes setup +- dashboard + - updated imports + - messages when api-requests failed + - new progress-bar for task-progress with live update of the current task progress + - hide admin-section in case that the user is not an admin + - user-logo in the right upper corner now is always white with the starting letter of the user-id + - gauge-charts of the overview-page now has animation ### Fixed diff --git a/src/dashboard/app/src/App.vue b/src/dashboard/app/src/App.vue index b659f9a6..96dce971 100644 --- a/src/dashboard/app/src/App.vue +++ b/src/dashboard/app/src/App.vue @@ -37,6 +37,7 @@
+ + {{ tokenExpireError }} +
diff --git a/src/dashboard/app/src/components/admin/project/project_delete_modal.vue b/src/dashboard/app/src/components/admin/project/project_delete_modal.vue index 4fa3375b..9794d48e 100644 --- a/src/dashboard/app/src/components/admin/project/project_delete_modal.vue +++ b/src/dashboard/app/src/components/admin/project/project_delete_modal.vue @@ -40,12 +40,18 @@ +
+ + {{ errorPopupMsg }} +
@@ -137,10 +156,16 @@ async function login() { background: rgba(0, 0, 0, 1); display: flex; justify-content: center; + align-items: center; } .login-modal { - height: 18rem; - width: 20rem; + width: 22rem; + margin-bottom: 5rem; +} + +/* is not found when I put this in one of the css files. Don't know why... */ +.invalid_input { + border-bottom: 2px solid #ff4d4f; } diff --git a/src/dashboard/app/src/components/overview.vue b/src/dashboard/app/src/components/overview.vue index 35b3ce08..4dfee99c 100644 --- a/src/dashboard/app/src/components/overview.vue +++ b/src/dashboard/app/src/components/overview.vue @@ -15,135 +15,218 @@ --> diff --git a/src/dashboard/app/src/components/sidebar.vue b/src/dashboard/app/src/components/sidebar.vue index c41a10b9..3f02186a 100644 --- a/src/dashboard/app/src/components/sidebar.vue +++ b/src/dashboard/app/src/components/sidebar.vue @@ -17,7 +17,7 @@ diff --git a/src/dashboard/app/src/config.ts b/src/dashboard/app/src/config.ts index de9ff039..ab46e08f 100644 --- a/src/dashboard/app/src/config.ts +++ b/src/dashboard/app/src/config.ts @@ -16,24 +16,41 @@ export type AppConfig = { apiUrl: string; }; +// Private configuration storage let config: AppConfig | null = null; -export async function loadConfig() { - console.log("Loading config.json..."); - const res = await fetch("/config.json", { cache: "no-store" }); - console.log("Fetch result:", res); - - if (!res.ok) { - throw new Error("Failed to load config.json"); +/** + * Loads the application configuration from config.json + * + * @throws {Error} If the configuration file cannot be loaded + */ +export async function loadConfig(): Promise { + try { + const response = await fetch("/config.json", { cache: "no-store" }); + if (!response.ok) { + throw new Error( + `Failed to load configuration: ${response.statusText}`, + ); + } + config = await response.json(); + } catch (error) { + console.error("Error loading configuration:", error); + throw error; // Re-throw to allow caller to handle } - - config = await res.json(); - console.log("Config loaded:", config); } +/** + * Retrieves the current application configuration + * + * @returns {AppConfig} The loaded configuration + * + * @throws {Error} If the configuration hasn't been loaded yet + */ export function getConfig(): AppConfig { if (config === null) { - throw new Error("Config not loaded yet"); + throw new Error( + "Configuration not loaded. Please call loadConfig() first.", + ); } return config; } diff --git a/src/dashboard/app/src/handleAxiosError.ts b/src/dashboard/app/src/handleAxiosError.ts new file mode 100644 index 00000000..cf428552 --- /dev/null +++ b/src/dashboard/app/src/handleAxiosError.ts @@ -0,0 +1,49 @@ +// Copyright 2022-2026 Tobias Anker + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import axios, { AxiosError } from "axios"; + +/** + * Handles and formats Axios errors into user-friendly messages + * + * @param err - The AxiosError to handle + * @param baseMessage - Base error message to use as attachment for the messages or as replacement when no specific message is available + * + * @returns Formatted error message string + */ +export function handleAxiosError( + err: AxiosError, + baseMessage = "An unexpected error occurred", +): string { + // Check if the error is an AxiosError + if (!axios.isAxiosError(err)) { + return baseMessage; + } + + // Handle response errors (when the request was made and the server responded) + if (err.response) { + const status = err.response.status; + const message = err.response.data?.message ?? baseMessage; + + return `${baseMessage}: API error ${status}: ${message}`; + } + + // Handle request errors (when the request was made but no response was received) + if (err.request) { + return `${baseMessage}: API did not respond`; + } + + // Handle other errors (when the request was not made) + return `${baseMessage}: ${err.message}`; +} diff --git a/src/dashboard/app/src/main.ts b/src/dashboard/app/src/main.ts index ab1739d2..771992d4 100644 --- a/src/dashboard/app/src/main.ts +++ b/src/dashboard/app/src/main.ts @@ -13,8 +13,8 @@ // limitations under the License. import { createApp } from "vue"; -import App from "./App.vue"; -import { loadConfig } from "./config"; +import App from "@/App.vue"; +import { loadConfig } from "@/config"; async function bootstrap() { await loadConfig(); diff --git a/src/dashboard/app/src/styles/card.css b/src/dashboard/app/src/styles/card.css index f4a70313..5bf72a38 100644 --- a/src/dashboard/app/src/styles/card.css +++ b/src/dashboard/app/src/styles/card.css @@ -27,6 +27,8 @@ limitations under the License. margin: 0.5rem; /* space around the whole overview */ margin-top: 1.5rem; margin-bottom: 2.5rem; + margin-right: 1rem; + margin-left: 1rem; } .card-label { diff --git a/src/dashboard/app/src/styles/modal.css b/src/dashboard/app/src/styles/modal.css index 44596707..6a078eba 100644 --- a/src/dashboard/app/src/styles/modal.css +++ b/src/dashboard/app/src/styles/modal.css @@ -24,9 +24,10 @@ limitations under the License. text-align: center; font-size: 1.5rem; - height: 5rem; - padding: 1rem 1rem; + height: 4rem; + padding: 1rem; } + .modal-content { display: flex; flex-direction: column; @@ -34,6 +35,10 @@ limitations under the License. gap: 1rem; padding: 1.5rem; + + height: auto; + min-height: auto; + overflow: visible; } .modal-bottombar { @@ -74,12 +79,13 @@ limitations under the License. display: flex; justify-content: center; z-index: 1000; + align-items: center; } .modal { background-color: var(--color-background); box-shadow: var(--box-shadow-header); - margin: 5% auto; + margin: 0; display: flex; flex-direction: column; gap: 1rem; diff --git a/src/dashboard/app/src/styles/other.css b/src/dashboard/app/src/styles/other.css index a38b4054..469228c5 100644 --- a/src/dashboard/app/src/styles/other.css +++ b/src/dashboard/app/src/styles/other.css @@ -21,14 +21,14 @@ limitations under the License. } .error-msg { - background-color: #ffc8c8; /* soft red box */ - color: #000; /* black text */ - border: 1px solid #ff4d4d; /* stronger border to match */ - padding: 0.5rem 0.75rem; /* comfy spacing */ - border-radius: 4px; /* cute rounded corners~ */ - margin-top: 0.5rem; /* some space above */ - width: 100%; /* full width inside the bottom bar */ - text-align: center; /* centered text, optional */ + background-color: #ffc8c8; + color: #000; + border: 1px solid #ff4d4d; + padding: 0.5rem 0.75rem; + border-radius: 4px; + margin-top: 0.5rem; + width: 100%; + text-align: center; box-sizing: border-box; } @@ -52,6 +52,10 @@ limitations under the License. max-width: 300px; } +.invalid_input { + border-bottom: 2px solid #ff4d4f; +} + /* little ✕ button in the corner */ .error-close-btn { position: absolute; diff --git a/src/dashboard/app/tsconfig.json b/src/dashboard/app/tsconfig.json index f3c5bc10..4ecb5c05 100644 --- a/src/dashboard/app/tsconfig.json +++ b/src/dashboard/app/tsconfig.json @@ -8,7 +8,11 @@ "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "useDefineForClassFields": true + "useDefineForClassFields": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] } diff --git a/src/dashboard/app/vite.config.ts b/src/dashboard/app/vite.config.ts index 91d95e55..4e8e2c27 100644 --- a/src/dashboard/app/vite.config.ts +++ b/src/dashboard/app/vite.config.ts @@ -14,10 +14,16 @@ import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; +import { fileURLToPath, URL } from "node:url"; export default defineConfig({ plugins: [vue()], root: ".", + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", import.meta.url)), + }, + }, server: { host: "0.0.0.0", port: 5173