From d911697fb74c9b68438a1e348a137c7676ab409e Mon Sep 17 00:00:00 2001 From: Benjam <53127823+benjamsf@users.noreply.github.com> Date: Sat, 25 Jan 2025 19:47:13 +0200 Subject: [PATCH 1/7] Fix bug with LoginView button Now it does not look like deactivated if user navigates here not having a logincode --- src/views/login/LoginView.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/views/login/LoginView.tsx b/src/views/login/LoginView.tsx index 614e4510e..7806d6ca0 100644 --- a/src/views/login/LoginView.tsx +++ b/src/views/login/LoginView.tsx @@ -55,7 +55,7 @@ export function LoginView() { code: params.get("code")?.toUpperCase() ?? "", }, validationSchema: CodeSchema, - validateOnMount: true, + validateOnMount: !!params.get("code"), // Only validate on mount if `code` is in the URL onSubmit: (values) => { checkCode(values.code); }, @@ -66,7 +66,6 @@ export function LoginView() { const handleChange = (e: React.ChangeEvent) => { const upperCaseValue = e.target.value.toUpperCase(); - // Using void to intentionally ignore the Promise returned by setFieldValue, to suppress eslint void setFieldValue("code", upperCaseValue, false); }; @@ -149,7 +148,7 @@ export function LoginView() { width: "full", }} type="submit" - disabled={!isValid || isLoading} + disabled={!values.code && !isLoading} // Button only disabled when empty or loading styling={buttonStyle} >
From 4ce0abf989e6436a9c3905fabaf90a3c7c179002 Mon Sep 17 00:00:00 2001 From: regularjoe <53127823+benjamsf@users.noreply.github.com> Date: Sun, 2 Feb 2025 22:33:25 +0200 Subject: [PATCH 2/7] Fix loginview not handling manual code input correctly --- src/views/login/LoginView.tsx | 46 +++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/src/views/login/LoginView.tsx b/src/views/login/LoginView.tsx index 7806d6ca0..2b87e0ae1 100644 --- a/src/views/login/LoginView.tsx +++ b/src/views/login/LoginView.tsx @@ -33,16 +33,20 @@ export function LoginView() { const { t } = useTranslation(); const navigate = useNavigate(); const params = useQueryParams(); + // Retrieve the code from the URL (if provided) + const codeFromQuery = params.get("code"); const { setOtpVerified } = useContext(UserTypeContext); const loginCodeStore = useLoginCodeStore(); const { deployment } = useHealthcheck(); const [codeNotValid, setCodeNotValid] = useState(false); + // Prevent auto-submit from running more than once. const [hasAutoSubmitted, setHasAutoSubmitted] = useState(false); const protocol = window.location.protocol; const host = window.location.host; const mtlsUrl = `${protocol}//mtls.${host}/app/admin/`; const buttonStyle = "min-h-[70px]"; + // Validation schema using Yup. const CodeSchema = yup.object().shape({ code: yup .string() @@ -50,22 +54,31 @@ export function LoginView() { .matches(TOKEN_REGEX, t("code-is-wrong")), }); + // If the user arrives with a code in the URL, we want to auto-validate. + // Otherwise, we disable auto-validation (and error messages) until submit. + const autoValidate = !!codeFromQuery; + const formik = useFormik({ initialValues: { - code: params.get("code")?.toUpperCase() ?? "", + code: codeFromQuery ? codeFromQuery.toUpperCase() : "", }, validationSchema: CodeSchema, - validateOnMount: !!params.get("code"), // Only validate on mount if `code` is in the URL + // Only auto-validate if a code came in via the URL. + validateOnMount: autoValidate, + validateOnChange: autoValidate, + validateOnBlur: autoValidate, onSubmit: (values) => { checkCode(values.code); }, }); - // Destructure formik properties - const { values, isValid, submitForm, setFieldValue } = formik; + // Destructure useful Formik properties. + const { values, isValid, submitForm, setFieldValue, submitCount } = formik; const handleChange = (e: React.ChangeEvent) => { const upperCaseValue = e.target.value.toUpperCase(); + setCodeNotValid(false); // Clear error + formik.setErrors({}); // Reset all Formik errors when user starts typing void setFieldValue("code", upperCaseValue, false); }; @@ -100,12 +113,26 @@ export function LoginView() { }, }); + // Auto-submit only if a code is provided in the URL useEffect(() => { - if (values.code && isValid && !isLoading && !hasAutoSubmitted) { + if ( + codeFromQuery && // only auto-submit when arriving via URL + values.code && + isValid && + !isLoading && + !hasAutoSubmitted + ) { setHasAutoSubmitted(true); void submitForm(); } - }, [values.code, isValid, isLoading, hasAutoSubmitted, submitForm]); + }, [ + codeFromQuery, + values.code, + isValid, + isLoading, + hasAutoSubmitted, + submitForm, + ]); return ( @@ -130,7 +157,10 @@ export function LoginView() { onChange={handleChange} /> - + {/* + Show Formik's error only after the user has attempted a submission + */} + {submitCount > 0 && } {codeNotValid &&
{t("code-is-wrong")}
} {isError && (
@@ -148,7 +178,7 @@ export function LoginView() { width: "full", }} type="submit" - disabled={!values.code && !isLoading} // Button only disabled when empty or loading + disabled={!values.code || isLoading} styling={buttonStyle} >
From db4a6a6b3e8b5dfac1893db403ea36354561a2f2 Mon Sep 17 00:00:00 2001 From: regularjoe <53127823+benjamsf@users.noreply.github.com> Date: Mon, 3 Feb 2025 21:37:01 +0200 Subject: [PATCH 3/7] Fix automatically moving to mtls download view first implementation behaves weirdly sometimes --- src/views/login/EnrollmentView.tsx | 36 +++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/views/login/EnrollmentView.tsx b/src/views/login/EnrollmentView.tsx index 0d68f7abe..871b9fffc 100644 --- a/src/views/login/EnrollmentView.tsx +++ b/src/views/login/EnrollmentView.tsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import QRCode from "react-qr-code"; import { useOwnEnrollmentStatus } from "../../hook/api/useOwnEnrollmentStatus"; import { useNavigate } from "react-router-dom"; @@ -16,30 +16,39 @@ export function EnrollmentView() { const { isCopied, copyError, handleCopy } = useCopyToClipboard(); const callsign = localStorage.getItem("callsign") ?? undefined; const approveCode = localStorage.getItem("approveCode") ?? undefined; + + // Build the approval URL. const protocol = window.location.protocol; const host = window.location.host; const approvalUrl = `${protocol}//mtls.${host}/app/admin/user-management/approval?callsign=${ callsign ?? "" }&&approvalcode=${approveCode ?? ""}`; - // Redirect to login if approveCode or callsign is missing + // Local state to control polling behavior + const [shouldPoll, setShouldPoll] = useState(true); + + // Redirect to login if approveCode or callsign is missing. useEffect(() => { if (!approveCode || !callsign) { navigate("/login"); } }, [approveCode, callsign, navigate]); - // Check enrollment status periodically and navigate on success - useOwnEnrollmentStatus({ - onSuccess: (enrolled) => { - if (enrolled) { - navigate("/login/createmtls"); - } - }, - refetchInterval: 1000, + // Use the enrollment status hook. + const { data: enrolled, isLoading } = useOwnEnrollmentStatus({ + refetchInterval: shouldPoll ? 5000 : false, // Stop polling once enrolled }); - // Render the waiting for approval view + // Effect to navigate on successful enrollment + useEffect(() => { + if (enrolled && shouldPoll) { + setShouldPoll(false); // Stop polling to prevent infinite re-renders + + // Use hard redirect to force React to unmount the view + window.location.replace("/login/createmtls"); + } + }, [enrolled, shouldPoll]); + return ( @@ -110,6 +119,11 @@ export function EnrollmentView() { }, ]} /> + {isLoading && ( +
+ {t("checking-enrollment-status")} +
+ )}
From d7432d21ce2ce79bad75ef06255741b9558b0859 Mon Sep 17 00:00:00 2001 From: regularjoe <53127823+benjamsf@users.noreply.github.com> Date: Mon, 3 Feb 2025 22:06:36 +0200 Subject: [PATCH 4/7] At footer, show depl. version rather than rmapi version This also requires injecting the RELEASE_TAG var at docker compose level --- Dockerfile | 4 ++++ src/components/Footer.tsx | 14 ++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3e060b74c..9f4eb510d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,6 +39,10 @@ COPY . /app ARG VITE_ASSET_SET=neutral ENV VITE_ASSET_SET=$VITE_ASSET_SET +# Set release tag so we can show our deployment version to users +ARG VITE_RELEASE_TAG=Developing +ENV VITE_RELEASE_TAG=$VITE_RELEASE_TAG + RUN npm install \ && chmod a+x /docker-entrypoint.sh \ && npm run build \ diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 2e1bab02c..1ab2b1aab 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -2,13 +2,15 @@ import { Trans, useTranslation } from "react-i18next"; import { InfoModal } from "./InfoModal"; import { DropdownMenu } from "./DropdownMenu"; import { useLanguageChange } from "./Localization/LanguageChange"; -import useHealthCheck from "../hook/helpers/useHealthcheck"; export function Footer() { - // Use both common and dynamic namespaces const { t } = useTranslation(["common", "dynamic"]); const isMtls = window.location.origin.includes("mtls."); - const { version } = useHealthCheck(); + + // Read the deployment version from VITE_RELEASE_TAG + const deploymentVersion = + import.meta.env.VITE_RELEASE_TAG || t("footer.loading"); + const feedbackLink = t("footer.feedbackForm", { ns: "dynamic" }); const { changeLanguage, availableLanguages } = useLanguageChange(); @@ -21,9 +23,8 @@ export function Footer() { return (

-
- RASENMAEHER {version || } -{" "} + Deploy App {deploymentVersion} -{" "}
)} -
-
{" "}
-
); From cd0a458c38714fb092670bf7ae0b7516d398b6b2 Mon Sep 17 00:00:00 2001 From: regularjoe <53127823+benjamsf@users.noreply.github.com> Date: Mon, 3 Feb 2025 22:20:20 +0200 Subject: [PATCH 5/7] missed a type --- src/components/Footer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 1ab2b1aab..8904dd952 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -9,7 +9,7 @@ export function Footer() { // Read the deployment version from VITE_RELEASE_TAG const deploymentVersion = - import.meta.env.VITE_RELEASE_TAG || t("footer.loading"); + (import.meta.env.VITE_RELEASE_TAG as string) || t("footer.loading"); const feedbackLink = t("footer.feedbackForm", { ns: "dynamic" }); const { changeLanguage, availableLanguages } = useLanguageChange(); From bd54ce64c413cfe8b232e631b1dd32c291b78a32 Mon Sep 17 00:00:00 2001 From: regularjoe <53127823+benjamsf@users.noreply.github.com> Date: Tue, 4 Feb 2025 00:22:54 +0200 Subject: [PATCH 6/7] access kc button in ManageUsersView --- src/assets/icons/kclogo.svg | 1 + src/assets/locale/en.json | 4 ++ src/assets/locale/fi.json | 4 ++ src/views/usermgmt/ManageUsersView.tsx | 92 ++++++++++++++++++++------ 4 files changed, 82 insertions(+), 19 deletions(-) create mode 100644 src/assets/icons/kclogo.svg diff --git a/src/assets/icons/kclogo.svg b/src/assets/icons/kclogo.svg new file mode 100644 index 000000000..e3d5bdf2a --- /dev/null +++ b/src/assets/icons/kclogo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/locale/en.json b/src/assets/locale/en.json index 1bf834e76..d79aa19ef 100644 --- a/src/assets/locale/en.json +++ b/src/assets/locale/en.json @@ -162,6 +162,10 @@ "manageUsersView.addUsers": "Add Users", "manageUsersView.approveUsers": "Approve Users", "manageUsersView.manageUsers": "Manage Users", + "manageUsersView.keycloakAccess": "Access Keycloak (Beta)", + "manageUsersView.keycloakAccessDesc1": "Keycloak is a backend service, which syncs user's roles and groups across services integrated to Deploy App. Managing groups is not necessary right now.", + "manageUsersView.keycloakAccessDesc2": "If you know how to use it, you may access it from the button below. Later on, user role & group management will be done directly here in Deploy App for maximum ease of use.", + "manageUsersView.openKeycloak": "Open Keycloak", "USERMANAGEMENTVIEW": "USERMANAGEMENTVIEW", "userManagement.rejectionSuccessMessage": "Success: The user has been removed.", diff --git a/src/assets/locale/fi.json b/src/assets/locale/fi.json index e2f4c963e..1a8721b73 100644 --- a/src/assets/locale/fi.json +++ b/src/assets/locale/fi.json @@ -163,6 +163,10 @@ "manageUsersView.addUsers": "Lisää käyttäjiä", "manageUsersView.approveUsers": "Hyväksy käyttäjiä", "manageUsersView.manageUsers": "Hallitse käyttäjiä", + "manageUsersView.keycloakAccess": "Avaa Keycloak (Beta)", + "manageUsersView.keycloakAccessDesc1": "Keycloak on taustapalvelu, joka synkronoi käyttäjien roolit ja ryhmät Deploy App-palvelimessasi mukana olevien palveluiden välillä. Sen käyttäminen ei ole tällä hetkellä välttämätöntä.", + "manageUsersView.keycloakAccessDesc2": "Jos osaat käyttää Keycloakia, voit avata sen alla olevasta painikkeesta. Myöhemmin käyttäjien rooli- ja ryhmähallinta tehdään suoraan Deploy Appissa, jotta käyttökokemus olisi mahdollisimman helppo.", + "manageUsersView.openKeycloak": "Avaa Keycloak", "USERMANAGEMENTVIEW": "USERMANAGEMENTVIEW", "userManagement.rejectionSuccessMessage": "Käyttäjän hylkääminen onnistui.", diff --git a/src/views/usermgmt/ManageUsersView.tsx b/src/views/usermgmt/ManageUsersView.tsx index 65df35da0..bc796200d 100644 --- a/src/views/usermgmt/ManageUsersView.tsx +++ b/src/views/usermgmt/ManageUsersView.tsx @@ -3,11 +3,12 @@ import android from "../../assets/icons/qrcode.svg"; import sanla from "../../assets/heroimages/ryhmä.jpeg"; import apple from "../../assets/icons/trooper3.png"; import windows from "../../assets/icons/useredi2.svg"; +import kcicon from "../../assets/icons/kclogo.svg"; import { Layout } from "../../components/Layout"; import { Card } from "../../components/Card"; import { CardsContainer } from "../../components/CardsContainer"; import { ServiceInfoCard } from "../../components/ServiceInfoCard"; -import { UnfoldableCard } from "../../components/UnfoldableCard"; +import { UnfoldableCard } from "../../components/UnfoldableCard2"; import { Trans } from "react-i18next"; export function ManageUsersView() { @@ -17,6 +18,12 @@ export function ManageUsersView() { window.scrollTo(0, 60); }, []); + // Construct the Keycloak URL dynamically: + // Remove a leading "mtls." from the hostname if present, + // then add the "kc." prefix, port, and fixed path. + const currentDomain = window.location.hostname.replace(/^mtls\./, ""); + const keycloakUrl = `https://kc.${currentDomain}:9443/admin/RASENMAEHER/console/`; + return (
} - description1={ - }} - /> - } - description2={ - }} - /> - } - description3={ - }} - /> - } + steps={[ + { + description: ( + }} + /> + ), + }, + { + description: ( + }} + /> + ), + }, + { + description: ( + }} + /> + ), + }, + ]} /> + +
+ } + styling="bg-backgroundLight w-full" + steps={[ + { + description: ( + }} + /> + ), + }, + { + imageSrc: kcicon, + imageClasses: "w-36 h-12 mx-auto", + description: ( + }} + /> + ), + }, + ]} + content={ +
+ + + +
+ } + /> +
From 4c8c38f0c5b921bb88b93136c156da13170acc16 Mon Sep 17 00:00:00 2001 From: regularjoe <53127823+benjamsf@users.noreply.github.com> Date: Tue, 4 Feb 2025 00:37:24 +0200 Subject: [PATCH 7/7] Update deps & vite config due to that --- package-lock.json | 18 +++++++++--------- src/assets/icons/kclogo.svg | 2 +- vite.config.ts | 7 +++++++ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 79076ec3f..9cbb75ff5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2658,9 +2658,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -4060,9 +4060,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", @@ -5511,9 +5511,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.5.tgz", - "integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==", + "version": "4.5.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz", + "integrity": "sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw==", "dev": true, "dependencies": { "esbuild": "^0.18.10", diff --git a/src/assets/icons/kclogo.svg b/src/assets/icons/kclogo.svg index e3d5bdf2a..4806156c4 100644 --- a/src/assets/icons/kclogo.svg +++ b/src/assets/icons/kclogo.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/vite.config.ts b/vite.config.ts index f4fb961b9..7cad2ad84 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -15,6 +15,13 @@ export default async ({ mode }) => { watch: { ignored: ["**/.env*"], }, + host: process.env.SERVER_DOMAIN || "localhost", + + allowedHosts: [ + process.env.SERVER_DOMAIN, // Dynamically allow the current domain + "localhost", // Always allow localhost for local dev + "0.0.0.0", // Allow any network access (useful in Docker) + ].filter(Boolean), // Remove undefined values proxy: { "/api": { target: "http://rmapi:8000",