From dfb7fbcc47b23d31a904d97a817bc92fd269dc0a Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Mon, 23 Dec 2024 15:45:44 +0100 Subject: [PATCH] Start for #587 --- ...olethPreAuthenticatedProcessingFilter.java | 5 + servicedesk-gui/package.json | 15 +- servicedesk-gui/src/App.css | 42 -- servicedesk-gui/src/App.jsx | 100 ++- servicedesk-gui/src/App.scss | 0 .../src/__tests__/locale/en.test.js | 30 + .../src/__tests__/stores/AppStore.test.js | 11 + .../src/__tests__/utils/Manage.test.js | 33 + .../src/__tests__/utils/Pagination.test.js | 12 + .../src/__tests__/utils/UserRole.test.js | 158 ++++ .../src/__tests__/utils/Utils.test.js | 21 + .../src/__tests__/utils/applications.json | 156 ++++ .../src/__tests__/utils/roles.json | 291 ++++++++ .../src/__tests__/utils/userRoles.json | 73 ++ .../src/__tests__/validations/regExps.test.js | 16 + servicedesk-gui/src/api/index.js | 262 +++++++ servicedesk-gui/src/icons/Owl_Emblem.svg | 79 ++ servicedesk-gui/src/icons/alarm_bell.svg | 4 + servicedesk-gui/src/icons/calendar-alt.svg | 4 + servicedesk-gui/src/icons/critical.svg | 32 + servicedesk-gui/src/icons/duplicate.svg | 14 + servicedesk-gui/src/icons/email_new.svg | 7 + servicedesk-gui/src/icons/image-not-found.svg | 5 + .../src/icons/landing/collaborate.svg | 285 +++++++ servicedesk-gui/src/icons/landing/happy.svg | 110 +++ servicedesk-gui/src/icons/landing/mail.svg | 49 ++ servicedesk-gui/src/icons/landing/screen.svg | 76 ++ servicedesk-gui/src/icons/landing/sketch.svg | 66 ++ .../landing/undraw_access_denied_re_awnf.svg | 113 +++ .../landing/undraw_authentication_re_svpt.svg | 51 ++ .../src/icons/landing/undraw_enter_uhqk.svg | 54 ++ .../icons/landing/undraw_startled_-8-p0r.svg | 212 ++++++ .../landing/undraw_subscriptions_re_k7jj.svg | 88 +++ servicedesk-gui/src/icons/locks.svg | 195 +++++ servicedesk-gui/src/icons/multi-role.svg | 8 + .../src/icons/network-information.svg | 15 + servicedesk-gui/src/icons/owl.wav | Bin 0 -> 6430 bytes servicedesk-gui/src/icons/persons.svg | 14 + .../icons/undraw_page_not_found_re_e9o6.svg | 66 ++ .../icons/undraw_people_search_re_5rre.svg | 109 +++ .../src/icons/undraw_void_-3-ggu.svg | 35 + servicedesk-gui/src/index.css | 68 -- servicedesk-gui/src/index.scss | 33 + servicedesk-gui/src/locale/I18n.js | 41 ++ servicedesk-gui/src/locale/en.js | 526 +++++++++++++ servicedesk-gui/src/locale/nl.js | 526 +++++++++++++ servicedesk-gui/src/main.jsx | 19 +- servicedesk-gui/src/pages/App.js | 125 ++++ servicedesk-gui/src/pages/App.scss | 15 + servicedesk-gui/src/pages/Login.js | 52 ++ servicedesk-gui/src/pages/Login.scss | 150 ++++ servicedesk-gui/src/pages/NotFound.js | 11 + servicedesk-gui/src/pages/NotFound.scss | 13 + servicedesk-gui/src/stores/AppStore.js | 19 + servicedesk-gui/src/styles/circle.scss | 75 ++ servicedesk-gui/src/styles/forms.scss | 34 + servicedesk-gui/src/styles/mixins.scss | 80 ++ servicedesk-gui/src/styles/responsive.scss | 33 + servicedesk-gui/src/styles/tooltip.scss | 5 + servicedesk-gui/src/styles/vars.scss | 5 + servicedesk-gui/yarn.lock | 693 +++++++++++++++++- 61 files changed, 5282 insertions(+), 157 deletions(-) delete mode 100644 servicedesk-gui/src/App.css create mode 100644 servicedesk-gui/src/App.scss create mode 100644 servicedesk-gui/src/__tests__/locale/en.test.js create mode 100644 servicedesk-gui/src/__tests__/stores/AppStore.test.js create mode 100644 servicedesk-gui/src/__tests__/utils/Manage.test.js create mode 100644 servicedesk-gui/src/__tests__/utils/Pagination.test.js create mode 100644 servicedesk-gui/src/__tests__/utils/UserRole.test.js create mode 100644 servicedesk-gui/src/__tests__/utils/Utils.test.js create mode 100644 servicedesk-gui/src/__tests__/utils/applications.json create mode 100644 servicedesk-gui/src/__tests__/utils/roles.json create mode 100644 servicedesk-gui/src/__tests__/utils/userRoles.json create mode 100644 servicedesk-gui/src/__tests__/validations/regExps.test.js create mode 100644 servicedesk-gui/src/api/index.js create mode 100644 servicedesk-gui/src/icons/Owl_Emblem.svg create mode 100644 servicedesk-gui/src/icons/alarm_bell.svg create mode 100644 servicedesk-gui/src/icons/calendar-alt.svg create mode 100644 servicedesk-gui/src/icons/critical.svg create mode 100644 servicedesk-gui/src/icons/duplicate.svg create mode 100644 servicedesk-gui/src/icons/email_new.svg create mode 100644 servicedesk-gui/src/icons/image-not-found.svg create mode 100644 servicedesk-gui/src/icons/landing/collaborate.svg create mode 100644 servicedesk-gui/src/icons/landing/happy.svg create mode 100644 servicedesk-gui/src/icons/landing/mail.svg create mode 100644 servicedesk-gui/src/icons/landing/screen.svg create mode 100644 servicedesk-gui/src/icons/landing/sketch.svg create mode 100644 servicedesk-gui/src/icons/landing/undraw_access_denied_re_awnf.svg create mode 100644 servicedesk-gui/src/icons/landing/undraw_authentication_re_svpt.svg create mode 100644 servicedesk-gui/src/icons/landing/undraw_enter_uhqk.svg create mode 100644 servicedesk-gui/src/icons/landing/undraw_startled_-8-p0r.svg create mode 100644 servicedesk-gui/src/icons/landing/undraw_subscriptions_re_k7jj.svg create mode 100644 servicedesk-gui/src/icons/locks.svg create mode 100644 servicedesk-gui/src/icons/multi-role.svg create mode 100644 servicedesk-gui/src/icons/network-information.svg create mode 100644 servicedesk-gui/src/icons/owl.wav create mode 100644 servicedesk-gui/src/icons/persons.svg create mode 100644 servicedesk-gui/src/icons/undraw_page_not_found_re_e9o6.svg create mode 100644 servicedesk-gui/src/icons/undraw_people_search_re_5rre.svg create mode 100644 servicedesk-gui/src/icons/undraw_void_-3-ggu.svg delete mode 100644 servicedesk-gui/src/index.css create mode 100644 servicedesk-gui/src/index.scss create mode 100644 servicedesk-gui/src/locale/I18n.js create mode 100644 servicedesk-gui/src/locale/en.js create mode 100644 servicedesk-gui/src/locale/nl.js create mode 100644 servicedesk-gui/src/pages/App.js create mode 100644 servicedesk-gui/src/pages/App.scss create mode 100644 servicedesk-gui/src/pages/Login.js create mode 100644 servicedesk-gui/src/pages/Login.scss create mode 100644 servicedesk-gui/src/pages/NotFound.js create mode 100644 servicedesk-gui/src/pages/NotFound.scss create mode 100644 servicedesk-gui/src/stores/AppStore.js create mode 100644 servicedesk-gui/src/styles/circle.scss create mode 100644 servicedesk-gui/src/styles/forms.scss create mode 100644 servicedesk-gui/src/styles/mixins.scss create mode 100644 servicedesk-gui/src/styles/responsive.scss create mode 100644 servicedesk-gui/src/styles/tooltip.scss create mode 100644 servicedesk-gui/src/styles/vars.scss diff --git a/myconext-server/src/main/java/myconext/shibboleth/ShibbolethPreAuthenticatedProcessingFilter.java b/myconext-server/src/main/java/myconext/shibboleth/ShibbolethPreAuthenticatedProcessingFilter.java index ea6497f6..75226b54 100644 --- a/myconext-server/src/main/java/myconext/shibboleth/ShibbolethPreAuthenticatedProcessingFilter.java +++ b/myconext-server/src/main/java/myconext/shibboleth/ShibbolethPreAuthenticatedProcessingFilter.java @@ -4,6 +4,7 @@ import myconext.manage.Manage; import myconext.model.User; import myconext.repository.UserRepository; +import org.apache.commons.lang3.stream.Streams; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.authentication.AuthenticationManager; @@ -28,6 +29,7 @@ public class ShibbolethPreAuthenticatedProcessingFilter extends AbstractPreAuthe public static final String SHIB_EMAIL = "Shib-InetOrgPerson-mail"; public static final String SHIB_UID = "uid"; public static final String SHIB_SCHAC_HOME_ORGANIZATION = "schacHomeOrganization"; + public static final String SHIB_MEMBERSHIPS = "is-member-of"; private final UserRepository userRepository; private final Manage serviceProviderResolver; @@ -51,6 +53,9 @@ protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) { String email = getHeader(SHIB_EMAIL, request); String givenName = getHeader(SHIB_GIVEN_NAME, request); String familyName = getHeader(SHIB_SUR_NAME, request); + Streams.of(getHeader(SHIB_MEMBERSHIPS, request).split(";")) + .map(String::trim) + .toList(); boolean valid = Stream.of(uid, schacHomeOrganization, email, givenName, familyName).allMatch(StringUtils::hasText); if (!valid) { diff --git a/servicedesk-gui/package.json b/servicedesk-gui/package.json index 643db6ee..c42ffbbc 100644 --- a/servicedesk-gui/package.json +++ b/servicedesk-gui/package.json @@ -12,7 +12,14 @@ "dependencies": { "@surfnet/sds": "^0.0.120", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "react-router-dom": "7.1.0", + "react-select": "^5.9.0", + "react-tooltip": "^5.28.0", + "dompurify": "^3.2.3", + "i18n-js": "^4.5.1", + "isomorphic-dompurify": "^2.19.0", + "zustand": "^5.0.2" }, "devDependencies": { "@eslint/js": "^9.17.0", @@ -25,6 +32,8 @@ "eslint-plugin-react-refresh": "^0.4.16", "globals": "^15.14.0", "sass": "^1.83.0", - "vite": "^6.0.4" - } + "vite": "^6.0.4", + "http-proxy-middleware": "^3.0.3" + }, + "proxy": "http://127.0.0.1:8888/" } diff --git a/servicedesk-gui/src/App.css b/servicedesk-gui/src/App.css deleted file mode 100644 index b9d355df..00000000 --- a/servicedesk-gui/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/servicedesk-gui/src/App.jsx b/servicedesk-gui/src/App.jsx index 78f95245..fcd09239 100644 --- a/servicedesk-gui/src/App.jsx +++ b/servicedesk-gui/src/App.jsx @@ -1,35 +1,73 @@ -import {useState} from 'react' +import {useEffect, useState} from 'react' import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' -function App() { - const [count, setCount] = useState(0) +import './App.scss' +import {useNavigate} from "react-router-dom"; +import {useAppStore} from "./stores/AppStore.js"; +import {configuration, csrf, me} from "./api/index.js"; - return ( - <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.jsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- - ) -} +export const App = () => { + + const [loading, setLoading] = useState(true); + const navigate = useNavigate(); + const {user} = useAppStore(state => state); + + useEffect(() => { + setLoading(true); + csrf().then(token => { + useAppStore.setState(() => ({csrfToken: token.token})); + configuration() + .then(res => { + useAppStore.setState(() => ({config: res})); + if (!res.authenticated) { + if (!res.name) { + const direction = window.location.pathname + window.location.search; + localStorage.setItem("location", direction); + } else if (!isEmpty(res.missingAttributes)) { + setLoading(false); + navigate("/missingAttributes"); + return; + } + const pathname = localStorage.getItem("location") || window.location.pathname; + const isInvitationAcceptFlow = window.location.pathname.startsWith("/invitation/accept"); + if (res.name && !pathname.startsWith("/invitation/accept") && !isInvitationAcceptFlow) { + setLoading(false); + navigate("/deadend"); + } else if (pathname === "/" || pathname.startsWith("/login") || pathname.startsWith("/invitation/accept") || isInvitationAcceptFlow) { + setLoading(false); + navigate(isInvitationAcceptFlow ? (window.location.pathname + window.location.search) : pathname); + } else { + //Bookmarked URL's trigger a direct login and skip the landing page + login(res); + } + } else { + me() + .then(res => { + useAppStore.setState(() => ({user: res, authenticated: true})); + setLoading(false); + const location = localStorage.getItem("location") || window.location.pathname + window.location.search; + const newLocation = location.startsWith("/login") ? "/home" : location; + localStorage.removeItem("location"); + navigate(newLocation); + }); + } + }) + .catch(() => { + setLoading(false); + navigate("/deadend"); + }) + }) + }, [reload, impersonator]); // eslint-disable-line react-hooks/exhaustive-deps -export default App + if (loading) { + return + } + + + return ( + <> + TODO + Routes -> with login + + ) +} diff --git a/servicedesk-gui/src/App.scss b/servicedesk-gui/src/App.scss new file mode 100644 index 00000000..e69de29b diff --git a/servicedesk-gui/src/__tests__/locale/en.test.js b/servicedesk-gui/src/__tests__/locale/en.test.js new file mode 100644 index 00000000..401c4d4a --- /dev/null +++ b/servicedesk-gui/src/__tests__/locale/en.test.js @@ -0,0 +1,30 @@ +import en from "../../locale/en"; +import nl from "../../locale/nl"; + +expect.extend({ + toContainKey(translation, key) { + return { + message: () => `Expected ${key} to be present in ${JSON.stringify(translation)}`, + pass: (translation !== undefined && translation[key] !== undefined) + }; + }, +}); + +test("All translations exists in all bundles", () => { + const contains = (translation, translationToVerify, keyCollection, parents) => { + Object.keys(translation).forEach(key => { + expect(translationToVerify).toContainKey(key); + const value = translation[key]; + keyCollection.push(parents + key); + if (typeof value === "object") { + contains(value, translationToVerify[key], keyCollection, parents + key + ".") + } + }); + }; + const keyCollectionEN = []; + contains(en, nl, keyCollectionEN, ''); + const keyCollectionNL = []; + contains(nl, en, keyCollectionNL, ''); + const positionalMismatches = keyCollectionEN.filter((item, index) => keyCollectionNL[index] !== item); + expect(positionalMismatches).toEqual([]) +}); diff --git a/servicedesk-gui/src/__tests__/stores/AppStore.test.js b/servicedesk-gui/src/__tests__/stores/AppStore.test.js new file mode 100644 index 00000000..0ddf5533 --- /dev/null +++ b/servicedesk-gui/src/__tests__/stores/AppStore.test.js @@ -0,0 +1,11 @@ +import {useAppStore} from "../../stores/AppStore"; + +test("Store outside functional component", () => { + const csrfToken = useAppStore.getState().csrfToken; + expect(csrfToken).toBeNull(); + + useAppStore.setState({csrfToken: "test"}); + + const updatedCsrfToken = useAppStore.getState().csrfToken; + expect(updatedCsrfToken).toEqual("test"); +}); \ No newline at end of file diff --git a/servicedesk-gui/src/__tests__/utils/Manage.test.js b/servicedesk-gui/src/__tests__/utils/Manage.test.js new file mode 100644 index 00000000..6737ad61 --- /dev/null +++ b/servicedesk-gui/src/__tests__/utils/Manage.test.js @@ -0,0 +1,33 @@ +import {mergeProvidersProvisioningsRoles, reduceApplicationFromUserRoles} from "../../utils/Manage"; +import applications from "./applications.json"; +import roles from "./roles.json"; +import userRoles from "./userRoles.json"; + +test("mergeProvidersProvisioningsRoles", () => { + const results = mergeProvidersProvisioningsRoles(applications.providers, applications.provisionings, roles); + expect(results.length).toEqual(6); +}); + +test("reduceApplicationFromUserRoles", () => { + const results = reduceApplicationFromUserRoles(userRoles, "en"); + const applicationNames = results.map(app => app.applicationName); + //Sorting alphabetically on applicationName + expect(applicationNames).toEqual([ + "Calendar EN (SURF bv)", + "Research EN (SURF bv)", + "Research EN (SURF bv)", + "Wiki EN (SURF bv)", + "Wiki EN (SURF bv)", + "Wiki EN (SURF bv)" + ]); + const roleNames = results + .filter(app => app.applicationName.startsWith("Wiki")) + .map(app => app.roleName); + //Sub-sorting alphabetically on roleName + expect(roleNames).toEqual([ + "Wiki 1 Role", + "Wiki 2 Role", + "Wiki Another Role (3) - Calendar (1)" + ]); +}); + diff --git a/servicedesk-gui/src/__tests__/utils/Pagination.test.js b/servicedesk-gui/src/__tests__/utils/Pagination.test.js new file mode 100644 index 00000000..70a82311 --- /dev/null +++ b/servicedesk-gui/src/__tests__/utils/Pagination.test.js @@ -0,0 +1,12 @@ +import {defaultPagination, paginationQueryParams} from "../../utils/Pagination"; + +test("paginationQueryParams defaults", () => { + const page = defaultPagination("desc", "DESC"); + const queryParams = paginationQueryParams(page, {custom: "val"}); + expect(queryParams).toEqual("custom=val&pageNumber=0&pageSize=10&sort=desc&sortDirection=DESC&"); +}); + +test("paginationQueryParams empty", () => { + const queryParams = paginationQueryParams({}); + expect(queryParams).toEqual(""); +}); diff --git a/servicedesk-gui/src/__tests__/utils/UserRole.test.js b/servicedesk-gui/src/__tests__/utils/UserRole.test.js new file mode 100644 index 00000000..8e55b5f6 --- /dev/null +++ b/servicedesk-gui/src/__tests__/utils/UserRole.test.js @@ -0,0 +1,158 @@ +import { + allowedAuthoritiesForInvitation, + allowedToDeleteInvitation, + allowedToEditRole, + allowedToRenewUserRole, + AUTHORITIES, + highestAuthority, + isUserAllowed +} from "../../utils/UserRole"; + +const applicationUsagesForManageId = manageId => { + return [{application: {manageId: manageId}}]; +} + +test("Test isUserAllowed", () => { + let user = {userRoles: [{authority: AUTHORITIES.GUEST}]} + expect(isUserAllowed(AUTHORITIES.INVITER, user)).toBeFalsy(); + + user = {superUser: true} + expect(isUserAllowed(AUTHORITIES.SUPER_USER, user)).toBeTruthy(); + + user = {userRoles: [{authority: AUTHORITIES.GUEST}, {authority: AUTHORITIES.MANAGER}]} + expect(isUserAllowed(AUTHORITIES.SUPER_USER, user)).toBeFalsy(); + expect(isUserAllowed(AUTHORITIES.MANAGER, user)).toBeTruthy(); + + expect(() => isUserAllowed("nope", user)).toThrow(Error); +}); + +test("Allowed authorities for invitation - superUser", () => { + const user = {superUser: true} + + const authorities = allowedAuthoritiesForInvitation(user, []); + expect(authorities).toEqual([AUTHORITIES.SUPER_USER, AUTHORITIES.MANAGER, AUTHORITIES.INVITER, AUTHORITIES.GUEST]); +}); + +test("Allowed authorities for invitation - manager", () => { + const researchUserRole = {authority: AUTHORITIES.MANAGER, role: {id: "1", manageId: "2"}}; + const wikiRole = {id: "2", manageId: "2"}; + const mailUserRole = {authority: AUTHORITIES.INVITER, role: {id: "3", manageId: "9"}}; + const user = {userRoles: [researchUserRole, mailUserRole]} + + let authorities = allowedAuthoritiesForInvitation(user, []); + expect(authorities).toEqual([AUTHORITIES.INVITER, AUTHORITIES.GUEST]); + + authorities = allowedAuthoritiesForInvitation(user, [wikiRole]); + expect(authorities).toEqual([]); + + authorities = allowedAuthoritiesForInvitation(user, [mailUserRole.role]); + expect(authorities).toEqual([AUTHORITIES.GUEST]); + +}); + +test("Allowed to renew UserRole", () => { + const research = { + authority: AUTHORITIES.MANAGER, + role: {id: "1", applicationUsages: applicationUsagesForManageId("2")} + }; + const mail = { + authority: AUTHORITIES.INVITER, + role: {id: "3", applicationUsages: applicationUsagesForManageId("9")} + }; + const calendar = { + authority: AUTHORITIES.GUEST, + role: {id: "3", applicationUsages: applicationUsagesForManageId("9")} + }; + let user = {superUser: true} + expect(allowedToRenewUserRole(user, null)).toBeTruthy(); + + user = {userRoles: [calendar]} + expect(allowedToRenewUserRole(user, calendar)).toBeFalsy(); + + user = {userRoles: [research]} + expect(allowedToRenewUserRole(user, {authority: AUTHORITIES.SUPER_USER})).toBeFalsy(); + expect(allowedToRenewUserRole(user, {authority: AUTHORITIES.MANAGER})).toBeFalsy(); + expect(allowedToRenewUserRole(user, { + authority: AUTHORITIES.INVITER, + role: {id: "9", applicationUsages: applicationUsagesForManageId("9")} + })).toBeFalsy(); + expect(allowedToRenewUserRole(user, { + authority: AUTHORITIES.INVITER, + role: {id: "1", applicationUsages: applicationUsagesForManageId("9")} + })).toBeTruthy(); + expect(allowedToRenewUserRole(user, { + authority: AUTHORITIES.INVITER, + role: {id: "9", applicationUsages: applicationUsagesForManageId("2")} + })).toBeTruthy(); + + user = {userRoles: [mail]} + expect(allowedToRenewUserRole(user, { + authority: AUTHORITIES.INVITER, + role: {id: "3", applicationUsages: applicationUsagesForManageId("9")} + })).toBeFalsy(); + expect(allowedToRenewUserRole(user, { + authority: AUTHORITIES.GUEST, + role: {id: "1", applicationUsages: applicationUsagesForManageId("11")} + })).toBeFalsy(); + expect(allowedToRenewUserRole(user, { + authority: AUTHORITIES.GUEST, + role: {id: "3", applicationUsages: applicationUsagesForManageId("11")} + })).toBeTruthy(); + + // An institution admin is allowed to CRUD for every role that is owned by the organization of the insitution admin + user = {institutionAdmin: true, applications: [{id: "2"}]} + expect(allowedToRenewUserRole(user, research)).toBeTruthy(); + expect(allowedToRenewUserRole(user, mail)).toBeFalsy(); + +}) + +test("Allowed to renew guestIncluded UserRole as Inviter", () => { + const calendar = { + authority: AUTHORITIES.INVITER, + role: {id: "1", applicationUsages: applicationUsagesForManageId("1")} + }; + const user = {userRoles: [calendar]} + expect(allowedToRenewUserRole(user, { + authority: AUTHORITIES.MANAGER, + role: {id: "1", applicationUsages: applicationUsagesForManageId("1")} + })).toBeFalsy(); + + expect(allowedToRenewUserRole(user, { + authority: AUTHORITIES.MANAGER, + role: {id: "1", applicationUsages: applicationUsagesForManageId("1")} + }, true, true)).toBeTruthy(); +}) + +test("Allowed to delete Invitation", () => { + const mail = { + authority: AUTHORITIES.INVITER, + role: {id: "1", applicationUsages: applicationUsagesForManageId("10")} + }; + const research = { + authority: AUTHORITIES.INVITER, + role: {id: "2", applicationUsages: applicationUsagesForManageId("11")} + }; + const user = {userRoles: [mail, research]} + const invitation = {intended_authority: AUTHORITIES.GUEST, roles: [mail, research]}; + expect(allowedToDeleteInvitation(user, invitation)).toBeTruthy(); + + invitation.intended_authority = AUTHORITIES.INVITER; + expect(allowedToDeleteInvitation(user, invitation)).toBeFalsy(); +}); + +test("Allowed to edit", () => { + const role = {id: 1, applicationUsages: applicationUsagesForManageId("1")}; + const user = { + institutionAdmin: true, + applications: [{id: "1"}], + userRoles: [{authority: AUTHORITIES.INVITER, role: role}] + } + expect(allowedToEditRole(user, role)).toBeTruthy(); +}); + +test("Highest authority", () => { + const user = { + institutionAdmin: true, + } + expect(highestAuthority(user, false)).toEqual(AUTHORITIES.INSTITUTION_ADMIN); +}); diff --git a/servicedesk-gui/src/__tests__/utils/Utils.test.js b/servicedesk-gui/src/__tests__/utils/Utils.test.js new file mode 100644 index 00000000..55c3f178 --- /dev/null +++ b/servicedesk-gui/src/__tests__/utils/Utils.test.js @@ -0,0 +1,21 @@ +import {distinctValues, sanitizeURL} from "../../utils/Utils"; + +test("Test sanitizeURL", () => { + expect(sanitizeURL("https://invite.test2.surfconext.nl")).toEqual("https://invite.test2.surfconext.nl"); +}); + +test("Test distinctValues", () => { + const res = distinctValues([{id: "1", val: "val1"}, {id: "1", val: "valX"}, {id: "2", val: "val2"}, { + id: "3", + val: "val3" + }], + "id"); + expect(res.length).toEqual(3); + + const resId = distinctValues([{id: 1, val: "val1"}, {id: 1, val: "valX"}, {id: 2, val: "val2"}, { + id: 3, + val: "val3" + }], + "id"); + expect(res.length).toEqual(3); +}); \ No newline at end of file diff --git a/servicedesk-gui/src/__tests__/utils/applications.json b/servicedesk-gui/src/__tests__/utils/applications.json new file mode 100644 index 00000000..df630b1e --- /dev/null +++ b/servicedesk-gui/src/__tests__/utils/applications.json @@ -0,0 +1,156 @@ +{ + "provisionings": [ + { + "scim_user": "user", + "scim_update_role_put_method": true, + "entityid": "https://scim", + "type": "provisioning", + "scim_password": "secret", + "name:en": "Scim", + "id": "7", + "scim_url": "http://localhost:8081/api/scim/v2", + "provisioning_type": "scim", + "_id": "7", + "applications": [ + { + "id": "1", + "type": "saml20_sp" + }, + { + "id": "5", + "type": "oidc10_rp" + } + ] + }, + { + "scim_user": "user", + "scim_update_role_put_method": false, + "entityid": "https://scim-patch", + "type": "provisioning", + "scim_password": "secret", + "name:en": "Scim-Patch", + "id": "8", + "scim_url": "http://localhost:8081/api/scim/v2", + "scim_user_identifier": "subject_id", + "provisioning_type": "scim", + "_id": "8", + "applications": [ + { + "id": "4", + "type": "saml20_sp" + } + ] + }, + { + "graph_client_id": "client_id", + "entityid": "https://graph", + "type": "provisioning", + "graph_tenant": "tenant", + "name:en": "graph", + "id": "9", + "graph_url": "http://localhost:8081/graph/users", + "provisioning_type": "graph", + "graph_secret": "secret", + "_id": "9", + "applications": [ + { + "id": "2", + "type": "saml20_sp" + }, + { + "id": "6", + "type": "oidc10_rp" + } + ] + }, + { + "entityid": "https://eva", + "type": "provisioning", + "name:en": "EVA", + "id": "10", + "eva_guest_account_duration": 30, + "eva_token": "secret", + "provisioning_type": "eva", + "eva_url": "http://localhost:8081/eva", + "_id": "10", + "applications": [ + { + "id": "3", + "type": "saml20_sp" + } + ] + } + ], + "providers": [ + { + "entityid": "https://calendar", + "type": "oidc10_rp", + "url": "https://default-url-calendar.org", + "name:nl": "Calendar NL", + "institutionGuid": "ad93daef-0911-e511-80d0-005056956c1a", + "OrganizationName:en": "SURF bv", + "logo": "https://static.surfconext.nl/media/idp/surfconext.png", + "name:en": "Calendar EN", + "id": "5", + "_id": "5" + }, + { + "entityid": "https://cloud", + "type": "oidc10_rp", + "url": "https://default-url-cloud.org", + "name:nl": "Cloud NL", + "OrganizationName:en": "SURF bv", + "logo": "https://static.surfconext.nl/media/idp/surfconext.png", + "name:en": "Cloud EN", + "id": "6", + "_id": "6" + }, + { + "entityid": "https://wiki", + "type": "saml20_sp", + "url": "https://default-url-wiki.org", + "name:nl": "Wiki NL", + "institutionGuid": "ad93daef-0911-e511-80d0-005056956c1a", + "OrganizationName:en": "SURF bv", + "name:en": "Wiki EN", + "id": "1", + "_id": "1" + }, + { + "entityid": "https://network", + "type": "saml20_sp", + "url": "https://default-url-network.org", + "name:nl": "Network NL", + "institutionGuid": "ad93daef-0911-e511-80d0-005056956c1a", + "OrganizationName:en": "SURF bv", + "logo": "https://static.surfconext.nl/media/idp/surfconext.png", + "name:en": "Network EN", + "id": "2", + "_id": "2" + }, + { + "entityid": "https://storage", + "type": "saml20_sp", + "url": "https://default-url-storage.org", + "name:nl": "Storage NL", + "OrganizationName:en": "SURF bv", + "logo": "https://static.surfconext.nl/media/idp/surfconext.png", + "name:en": "Storage EN", + "id": "3", + "_id": "3" + }, + { + "entityid": "https://research", + "type": "saml20_sp", + "url": "https://default-url-research.org", + "name:nl": "Research NL", + "OrganizationName:en": "SURF bv", + "logo": "https://static.surfconext.nl/media/idp/surfconext.png", + "name:en": "Research EN", + "id": "4", + "_id": "4" + } + ] +} + + diff --git a/servicedesk-gui/src/__tests__/utils/roles.json b/servicedesk-gui/src/__tests__/utils/roles.json new file mode 100644 index 00000000..8b54568a --- /dev/null +++ b/servicedesk-gui/src/__tests__/utils/roles.json @@ -0,0 +1,291 @@ +[ + { + "id": 3915, + "name": "Wiki", + "shortName": "wiki", + "description": "Wiki desc", + "defaultExpiryDays": 365, + "enforceEmailEquality": false, + "eduIDOnly": false, + "blockExpiryDate": false, + "overrideSettingsAllowed": false, + "teamsOrigin": false, + "identifier": "3e267427-e2cd-4b9e-9a78-9248bede1bc4", + "userRoleCount": 2, + "applicationUsages": [ + { + "id": 4574, + "landingPage": "http://landingpage.com", + "application": { + "id": 3901, + "manageId": "1", + "manageType": "SAML20_SP", + "landingPage": "http://landingpage.com" + } + } + ], + "auditable": { + "createdAt": 1721314647.000000000, + "createdBy": "ResourceCleaner" + }, + "applicationMaps": [ + { + "landingPage": "http://landingpage.com", + "entityid": "https://wiki", + "type": "saml20_sp", + "url": "https://default-url-wiki.org", + "name:nl": "Wiki NL", + "institutionGuid": "ad93daef-0911-e511-80d0-005056956c1a", + "OrganizationName:en": "SURF bv", + "name:en": "Wiki EN", + "id": "1", + "_id": "1" + } + ] + }, + { + "id": 3916, + "name": "Network", + "shortName": "network", + "description": "Network desc", + "defaultExpiryDays": 365, + "enforceEmailEquality": false, + "eduIDOnly": false, + "blockExpiryDate": false, + "overrideSettingsAllowed": false, + "teamsOrigin": false, + "identifier": "3cec7986-ca0c-4750-a6f3-86f2ff7fb82f", + "userRoleCount": 0, + "applicationUsages": [ + { + "id": 4575, + "landingPage": "http://landingpage.com", + "application": { + "id": 3902, + "manageId": "2", + "manageType": "SAML20_SP", + "landingPage": "http://landingpage.com" + } + } + ], + "auditable": { + "createdAt": 1721314647.000000000, + "createdBy": "ResourceCleaner" + }, + "applicationMaps": [ + { + "landingPage": "http://landingpage.com", + "entityid": "https://network", + "type": "saml20_sp", + "url": "https://default-url-network.org", + "name:nl": "Network NL", + "institutionGuid": "ad93daef-0911-e511-80d0-005056956c1a", + "OrganizationName:en": "SURF bv", + "logo": "https://static.surfconext.nl/media/idp/surfconext.png", + "name:en": "Network EN", + "id": "2", + "_id": "2" + } + ] + }, + { + "id": 3917, + "name": "Storage", + "shortName": "storage", + "description": "Storage desc", + "defaultExpiryDays": 365, + "enforceEmailEquality": false, + "eduIDOnly": false, + "blockExpiryDate": false, + "overrideSettingsAllowed": false, + "teamsOrigin": false, + "identifier": "9ac14a11-fd43-4f9c-abeb-8bf811f134df", + "userRoleCount": 1, + "applicationUsages": [ + { + "id": 4576, + "landingPage": "https://landingpage.com", + "application": { + "id": 3903, + "manageId": "3", + "manageType": "SAML20_SP", + "landingPage": "https://landingpage.com" + } + }, + { + "id": 4577, + "landingPage": "https://landingpage.com", + "application": { + "id": 3904, + "manageId": "6", + "manageType": "OIDC10_RP", + "landingPage": "https://landingpage.com" + } + } + ], + "auditable": { + "createdAt": 1721314647.000000000, + "createdBy": "ResourceCleaner" + }, + "applicationMaps": [ + { + "landingPage": "https://landingpage.com", + "entityid": "https://storage", + "type": "saml20_sp", + "url": "https://default-url-storage.org", + "name:nl": "Storage NL", + "OrganizationName:en": "SURF bv", + "logo": "https://static.surfconext.nl/media/idp/surfconext.png", + "name:en": "Storage EN", + "id": "3", + "_id": "3" + }, + { + "landingPage": "https://landingpage.com", + "entityid": "https://cloud", + "type": "oidc10_rp", + "url": "https://default-url-cloud.org", + "name:nl": "Cloud NL", + "OrganizationName:en": "SURF bv", + "logo": "https://static.surfconext.nl/media/idp/surfconext.png", + "name:en": "Cloud EN", + "id": "6", + "_id": "6" + } + ] + }, + { + "id": 3918, + "name": "Research", + "shortName": "research", + "description": "Research desc", + "defaultExpiryDays": 365, + "enforceEmailEquality": false, + "eduIDOnly": false, + "blockExpiryDate": false, + "overrideSettingsAllowed": false, + "teamsOrigin": false, + "identifier": "f2c99710-0cd1-4ea2-a20c-14da698a866b", + "userRoleCount": 1, + "applicationUsages": [ + { + "id": 4578, + "landingPage": "http://landingpage.com", + "application": { + "id": 3905, + "manageId": "4", + "manageType": "SAML20_SP", + "landingPage": "http://landingpage.com" + } + } + ], + "auditable": { + "createdAt": 1721314647.000000000, + "createdBy": "ResourceCleaner" + }, + "applicationMaps": [ + { + "landingPage": "http://landingpage.com", + "entityid": "https://research", + "type": "saml20_sp", + "url": "https://default-url-research.org", + "name:nl": "Research NL", + "OrganizationName:en": "SURF bv", + "logo": "https://static.surfconext.nl/media/idp/surfconext.png", + "name:en": "Research EN", + "id": "4", + "_id": "4" + } + ] + }, + { + "id": 3919, + "name": "Calendar", + "shortName": "calendar", + "description": "Calendar desc", + "defaultExpiryDays": 365, + "enforceEmailEquality": false, + "eduIDOnly": false, + "blockExpiryDate": false, + "overrideSettingsAllowed": false, + "teamsOrigin": false, + "identifier": "75e4e7b6-5f98-45bc-9d10-9795316e6be0", + "userRoleCount": 1, + "applicationUsages": [ + { + "id": 4579, + "landingPage": "http://landingpage.com", + "application": { + "id": 3906, + "manageId": "5", + "manageType": "OIDC10_RP", + "landingPage": "http://landingpage.com" + } + } + ], + "auditable": { + "createdAt": 1721314647.000000000, + "createdBy": "ResourceCleaner" + }, + "applicationMaps": [ + { + "landingPage": "http://landingpage.com", + "entityid": "https://calendar", + "type": "oidc10_rp", + "url": "https://default-url-calendar.org", + "name:nl": "Calendar NL", + "institutionGuid": "ad93daef-0911-e511-80d0-005056956c1a", + "OrganizationName:en": "SURF bv", + "logo": "https://static.surfconext.nl/media/idp/surfconext.png", + "name:en": "Calendar EN", + "id": "5", + "_id": "5" + } + ] + }, + { + "id": 3920, + "name": "Mail", + "shortName": "mail", + "description": "Mail desc", + "defaultExpiryDays": 365, + "enforceEmailEquality": false, + "eduIDOnly": false, + "blockExpiryDate": false, + "overrideSettingsAllowed": false, + "teamsOrigin": false, + "identifier": "a2e7d57c-652c-430c-98e6-d5ac7050d979", + "userRoleCount": 0, + "applicationUsages": [ + { + "id": 4580, + "landingPage": "http://landingpage.com", + "application": { + "id": 3906, + "manageId": "5", + "manageType": "OIDC10_RP", + "landingPage": "http://landingpage.com" + } + } + ], + "auditable": { + "createdAt": 1721314647.000000000, + "createdBy": "ResourceCleaner" + }, + "applicationMaps": [ + { + "landingPage": "http://landingpage.com", + "entityid": "https://calendar", + "type": "oidc10_rp", + "url": "https://default-url-calendar.org", + "name:nl": "Calendar NL", + "institutionGuid": "ad93daef-0911-e511-80d0-005056956c1a", + "OrganizationName:en": "SURF bv", + "logo": "https://static.surfconext.nl/media/idp/surfconext.png", + "name:en": "Calendar EN", + "id": "5", + "_id": "5" + } + ] + } +] diff --git a/servicedesk-gui/src/__tests__/utils/userRoles.json b/servicedesk-gui/src/__tests__/utils/userRoles.json new file mode 100644 index 00000000..61a51d1f --- /dev/null +++ b/servicedesk-gui/src/__tests__/utils/userRoles.json @@ -0,0 +1,73 @@ +[ + { + "role": { + "name": "Wiki Another Role (3) - Calendar (1)", + "description": "Wiki Another Role (3) - Calendar (1)", + "applicationMaps": [ + { + "name:nl": "Wiki NL", + "OrganizationName:en": "SURF bv", + "name:en": "Wiki EN" + }, + { + "name:nl": "Calendar NL", + "OrganizationName:en": "SURF bv", + "name:en": "Calendar EN" + } + ] + } + }, + { + "role": { + "name": "Wiki 2 Role", + "description": "Wiki 2 Role", + "applicationMaps": [ + { + "name:nl": "Wiki NL", + "OrganizationName:en": "SURF bv", + "name:en": "Wiki EN" + } + ] + }, + "authority": "INVITER" + }, + { + "role": { + "name": "Research ABC", + "description": "Research ABC Description", + "applicationMaps": [ + { + "name:nl": "Research NL", + "OrganizationName:en": "SURF bv", + "name:en": "Research EN" + } + ] + } + }, + { + "role": { + "name": "Wiki 1 Role", + "description": "Wiki 1 Role", + "applicationMaps": [ + { + "name:nl": "Wiki NL", + "OrganizationName:en": "SURF bv", + "name:en": "Wiki EN" + } + ] + } + }, + { + "role": { + "name": "Research XYZ", + "description": "Research XYZ Description", + "applicationMaps": [ + { + "name:nl": "Research NL", + "OrganizationName:en": "SURF bv", + "name:en": "Research EN" + } + ] + } + } +] \ No newline at end of file diff --git a/servicedesk-gui/src/__tests__/validations/regExps.test.js b/servicedesk-gui/src/__tests__/validations/regExps.test.js new file mode 100644 index 00000000..b7a61e6b --- /dev/null +++ b/servicedesk-gui/src/__tests__/validations/regExps.test.js @@ -0,0 +1,16 @@ +import {constructShortName, validEmailRegExp} from "../../validations/regExps"; + +test("Sanitize URN", () => { + const urn = constructShortName(" !@#$%^&*(9IIOO UU plp ") + expect(urn).toEqual("9iioo_uu_plp") +}); + +test("Emails formats", () => { + expect(validEmailRegExp.test("aa")).toBeFalsy(); + expect(validEmailRegExp.test("a!@a.c")).toBeFalsy(); + expect(validEmailRegExp.test("a!@a.c@")).toBeFalsy(); + + expect(validEmailRegExp.test("a@a")).toBeTruthy(); + expect(validEmailRegExp.test("a.x@a")).toBeTruthy(); + expect(validEmailRegExp.test("a@a.c")).toBeTruthy(); +}) \ No newline at end of file diff --git a/servicedesk-gui/src/api/index.js b/servicedesk-gui/src/api/index.js new file mode 100644 index 00000000..d4d5d18c --- /dev/null +++ b/servicedesk-gui/src/api/index.js @@ -0,0 +1,262 @@ +import {isEmpty} from "../utils/Utils"; +import I18n from "../locale/I18n"; +import {useAppStore} from "../stores/AppStore"; +import {paginationQueryParams} from "../utils/Pagination"; + +//Internal API +function validateResponse(showErrorDialog) { + return res => { + if (!res.ok) { + if (res.type === "opaqueredirect") { + setTimeout(() => window.location.reload(true), 100); + return res; + } + const error = new Error(res.statusText); + error.response = res; + if (showErrorDialog && res.status === 401) { + window.location.reload(true); + return; + } + if (showErrorDialog) { + setTimeout(() => { + throw error; + }, 250); + } + throw error; + } + const sessionAlive = res.headers.get("x-session-alive"); + if (sessionAlive !== "true") { + window.location.reload(true); + } + return res; + + }; +} + +// It is not allowed to put non ASCI characters in HTTP headers +function sanitizeHeader(s) { + if (typeof s === 'string' || s instanceof String) { + s = s.replace(/[^\x00-\x7F]/g, ""); // eslint-disable-line no-control-regex + } + return isEmpty(s) ? "NON_ASCII_ONLY" : s; +} + +function validFetch(path, options, headers = {}, showErrorDialog = true) { + + const contentHeaders = { + "Accept": "application/json", + "Content-Type": "application/json", + "Accept-Language": I18n.locale, + "X-CSRF-TOKEN": useAppStore.getState().csrfToken, + ...headers + }; + const impersonator = useAppStore.getState().impersonator; + if (impersonator) { + contentHeaders["X-IMPERSONATE-ID"] = sanitizeHeader(useAppStore.getState().user.id); + } + const fetchOptions = Object.assign({}, {headers: contentHeaders}, options, { + credentials: "same-origin", + redirect: "manual", + changeOrigin: false, + }); + return fetch(path, fetchOptions).then(validateResponse(showErrorDialog)) + +} + +function fetchJson(path, options = {}, headers = {}, showErrorDialog = true) { + return validFetch(path, options, headers, showErrorDialog) + .then(res => res.json()); +} + +function postPutJson(path, body, method, showErrorDialog = true) { + const jsonBody = JSON.stringify(body); + return fetchJson(path, {method: method, body: jsonBody}, {}, showErrorDialog); +} + +function fetchDelete(path, showErrorDialog = true) { + return validFetch(path, {method: "delete"}, {}, showErrorDialog); +} + +//Base +export function health() { + return fetchJson("/internal/health"); +} + +export function configuration() { + return fetchJson("/api/v1/users/config", {}, {}, false); +} + +//Users +export function me() { + return fetchJson("/api/v1/users/me", {}, {}, false); +} + +export function other(id) { + return fetchJson(`/api/v1/users/other/${id}`, {}, {}, false); +} + +export function searchUsers(pagination = {}) { + const queryPart = paginationQueryParams(pagination, {}) + return fetchJson(`/api/v1/users/search?${queryPart}`); +} + +export function searchUsersByApplication(pagination = {}) { + const queryPart = paginationQueryParams(pagination, {}) + return fetchJson(`/api/v1/users/search-by-application?${queryPart}`); +} + +export function csrf() { + return fetchJson("/api/v1/csrf", {}, {}, false); +} + +export function logout() { + return fetchJson("/api/v1/users/logout"); +} + +export function reportError(error) { + return postPutJson("/api/v1/users/error", error, "post"); +} + +//Invitations +export function invitationByHash(hash) { + return fetchJson(`/api/v1/invitations/public?hash=${hash}`, {}, {}, false); +} + +export function newInvitation(invitationRequest) { + return postPutJson("/api/v1/invitations", invitationRequest, "POST"); +} + +export function acceptInvitation(hash, invitationId) { + const body = {hash: hash, invitationId: invitationId}; + return postPutJson("/api/v1/invitations/accept", body, "POST", false); +} + +export function invitationsByRoleId(roleId) { + return fetchJson(`/api/v1/invitations/roles/${roleId}`, {}, {}, false); +} + +export function resendInvitation(invitationId) { + return postPutJson(`/api/v1/invitations/${invitationId}`, {}, "PUT"); +} + +export function deleteInvitation(invitationId) { + return fetchDelete(`/api/v1/invitations/${invitationId}`); +} + +export function allInvitations() { + return fetchJson(`/api/v1/invitations/all`, {}, {}, false); +} + +export function searchInvitations(roleId, pagination = {}) { + if (roleId) { + pagination.roleId = roleId; + } + const queryPart = paginationQueryParams(pagination, {}) + return fetchJson(`/api/v1/invitations/search?${queryPart}`, {}, {}, false); +} + +//Manage +export function allProviders() { + return fetchJson("/api/v1/manage/providers"); +} + +export function allApplications() { + return fetchJson("/api/v1/manage/applications") +} + +export function organizationGUIDValidation(organizationGUID) { + return fetchJson(`/api/v1/manage/organization-guid-validation/${organizationGUID}`, {}, {}, false); +} + +//Roles +export function rolesByApplication(force = true, pagination = {}) { + const queryPart = paginationQueryParams(pagination, {force: !!force}) + return fetchJson(`/api/v1/roles?${queryPart}`); +} + +export function rolesPerApplicationManageId(manageId) { + return fetchJson(`/api/v1/roles/application/${manageId}`); +} + +export function roleByID(roleID, showErrorDialog = true) { + return fetchJson(`/api/v1/roles/${roleID}`, {}, {}, showErrorDialog); +} + +export function createRole(role) { + return postPutJson("/api/v1/roles", role, "POST", false); +} + +export function updateRole(role) { + return postPutJson("/api/v1/roles", role, "PUT", false); +} + +export function deleteRole(role) { + return fetchDelete(`/api/v1/roles/${role.id}`, false); +} + +//User roles +export function managersByRoleId(roleId) { + return fetchJson(`/api/v1/user_roles/managers/${roleId}`, {}, {}, false); +} + +export function searchUserRolesByRoleId(roleId, isGuests, pagination = {}) { + const queryPart = paginationQueryParams(pagination, {}) + return fetchJson(`/api/v1/user_roles/search/${roleId}/${isGuests}?${queryPart}`, {}, {}, false); +} + +export function updateUserRoleEndData(userRoleId, endDate) { + return postPutJson("/api/v1/user_roles", {userRoleId, endDate}, "PUT"); +} + +export function consequencesRoleDeletion(roleId) { + return fetchJson(`/api/v1/user_roles//consequences/${roleId}`, {}, {}, true); +} + +export function deleteUserRole(userRoleId, isGuest) { + return fetchDelete(`/api/v1/user_roles/${userRoleId}/${isGuest}`, false); +} + +//API tokens +export function apiTokens() { + return fetchJson("/api/v1/tokens"); +} + +export function generateToken() { + return fetchJson("/api/v1/tokens/generate-token"); +} + +export function createToken(description) { + return postPutJson("/api/v1/tokens", {description: description}, "POST"); +} + +export function deleteToken(token) { + return fetchDelete(`/api/v1/tokens/${token.id}`); +} + + +//Validations +export function validate(type, value) { + return isEmpty(value) ? Promise.resolve({valid: true}) : + postPutJson("/api/v1/validations/validate", {type, value}, "POST"); +} + +//System +export function cronCleanup() { + return fetchJson("/api/v1/system/cron/cleanup") +} + +export function cronExpiryNotifications() { + return fetchJson("/api/v1/system/cron/expiry-notifications") +} + +export function expiryUserRoles() { + return fetchJson("/api/v1/system/expiry-user-roles") +} + +export function performanceSeed() { + return postPutJson("/api/v1/system/performance-seed", {}, "PUT") +} + +export function rolesUnknownInManage() { + return fetchJson("/api/v1/system/unknown-roles") +} diff --git a/servicedesk-gui/src/icons/Owl_Emblem.svg b/servicedesk-gui/src/icons/Owl_Emblem.svg new file mode 100644 index 00000000..0840719f --- /dev/null +++ b/servicedesk-gui/src/icons/Owl_Emblem.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/servicedesk-gui/src/icons/alarm_bell.svg b/servicedesk-gui/src/icons/alarm_bell.svg new file mode 100644 index 00000000..ab1d5eea --- /dev/null +++ b/servicedesk-gui/src/icons/alarm_bell.svg @@ -0,0 +1,4 @@ + + + diff --git a/servicedesk-gui/src/icons/calendar-alt.svg b/servicedesk-gui/src/icons/calendar-alt.svg new file mode 100644 index 00000000..dc757af7 --- /dev/null +++ b/servicedesk-gui/src/icons/calendar-alt.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/servicedesk-gui/src/icons/critical.svg b/servicedesk-gui/src/icons/critical.svg new file mode 100644 index 00000000..5868f553 --- /dev/null +++ b/servicedesk-gui/src/icons/critical.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/servicedesk-gui/src/icons/duplicate.svg b/servicedesk-gui/src/icons/duplicate.svg new file mode 100644 index 00000000..39e8b996 --- /dev/null +++ b/servicedesk-gui/src/icons/duplicate.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/servicedesk-gui/src/icons/email_new.svg b/servicedesk-gui/src/icons/email_new.svg new file mode 100644 index 00000000..bf6210d2 --- /dev/null +++ b/servicedesk-gui/src/icons/email_new.svg @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/servicedesk-gui/src/icons/image-not-found.svg b/servicedesk-gui/src/icons/image-not-found.svg new file mode 100644 index 00000000..b9506206 --- /dev/null +++ b/servicedesk-gui/src/icons/image-not-found.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/servicedesk-gui/src/icons/landing/collaborate.svg b/servicedesk-gui/src/icons/landing/collaborate.svg new file mode 100644 index 00000000..f2ea295f --- /dev/null +++ b/servicedesk-gui/src/icons/landing/collaborate.svg @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/servicedesk-gui/src/icons/landing/happy.svg b/servicedesk-gui/src/icons/landing/happy.svg new file mode 100644 index 00000000..97e6f39d --- /dev/null +++ b/servicedesk-gui/src/icons/landing/happy.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/servicedesk-gui/src/icons/landing/mail.svg b/servicedesk-gui/src/icons/landing/mail.svg new file mode 100644 index 00000000..b0675b54 --- /dev/null +++ b/servicedesk-gui/src/icons/landing/mail.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/servicedesk-gui/src/icons/landing/screen.svg b/servicedesk-gui/src/icons/landing/screen.svg new file mode 100644 index 00000000..4888f338 --- /dev/null +++ b/servicedesk-gui/src/icons/landing/screen.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/servicedesk-gui/src/icons/landing/sketch.svg b/servicedesk-gui/src/icons/landing/sketch.svg new file mode 100644 index 00000000..f6affef6 --- /dev/null +++ b/servicedesk-gui/src/icons/landing/sketch.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/servicedesk-gui/src/icons/landing/undraw_access_denied_re_awnf.svg b/servicedesk-gui/src/icons/landing/undraw_access_denied_re_awnf.svg new file mode 100644 index 00000000..93519348 --- /dev/null +++ b/servicedesk-gui/src/icons/landing/undraw_access_denied_re_awnf.svg @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/servicedesk-gui/src/icons/landing/undraw_authentication_re_svpt.svg b/servicedesk-gui/src/icons/landing/undraw_authentication_re_svpt.svg new file mode 100644 index 00000000..709de5f8 --- /dev/null +++ b/servicedesk-gui/src/icons/landing/undraw_authentication_re_svpt.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/servicedesk-gui/src/icons/landing/undraw_enter_uhqk.svg b/servicedesk-gui/src/icons/landing/undraw_enter_uhqk.svg new file mode 100644 index 00000000..ee873cd9 --- /dev/null +++ b/servicedesk-gui/src/icons/landing/undraw_enter_uhqk.svg @@ -0,0 +1,54 @@ + + enter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/servicedesk-gui/src/icons/landing/undraw_startled_-8-p0r.svg b/servicedesk-gui/src/icons/landing/undraw_startled_-8-p0r.svg new file mode 100644 index 00000000..3b1392d8 --- /dev/null +++ b/servicedesk-gui/src/icons/landing/undraw_startled_-8-p0r.svg @@ -0,0 +1,212 @@ + + + + + + + + + startled + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/servicedesk-gui/src/icons/landing/undraw_subscriptions_re_k7jj.svg b/servicedesk-gui/src/icons/landing/undraw_subscriptions_re_k7jj.svg new file mode 100644 index 00000000..3d37cf27 --- /dev/null +++ b/servicedesk-gui/src/icons/landing/undraw_subscriptions_re_k7jj.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/servicedesk-gui/src/icons/locks.svg b/servicedesk-gui/src/icons/locks.svg new file mode 100644 index 00000000..56ea3eee --- /dev/null +++ b/servicedesk-gui/src/icons/locks.svg @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + Openclipart + + + + Locks + + 2011-02-24T01:51:57 + + + https://openclipart.org/detail/122125/locks-by-dear_theophilus + + + + dear_theophilus + + + + + + lock + + locks + + padlock + + private + + public + + safe + + secure + + + + + + + + + + + + diff --git a/servicedesk-gui/src/icons/multi-role.svg b/servicedesk-gui/src/icons/multi-role.svg new file mode 100644 index 00000000..2320811f --- /dev/null +++ b/servicedesk-gui/src/icons/multi-role.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/servicedesk-gui/src/icons/network-information.svg b/servicedesk-gui/src/icons/network-information.svg new file mode 100644 index 00000000..72f2c94c --- /dev/null +++ b/servicedesk-gui/src/icons/network-information.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/servicedesk-gui/src/icons/owl.wav b/servicedesk-gui/src/icons/owl.wav new file mode 100644 index 0000000000000000000000000000000000000000..a36766656ab14f9245ff9cb42980bbe8ba0f2fc6 GIT binary patch literal 6430 zcmeHMcT^Kwx1WR(nivHU0!Bd4(2Ib;6$w&906~gMksu&Nq$2`SR6wLj3H^cy2-2IV zfEv1h(xpgML`6ZaG!4bfn?b$nuB-QZf4%jsx7OWL_Bs2v_nv)z=gef194BaLaq$4a z2{kj#v**3}u>b&=0tjG-7yz^|15gV9*q}2K()0)<;K6X##@3r2>WSk4z|O|o29E{+ z@U?UcWI|d9Kt?WPtIbPpcO4-nfYRF~b0a505A?vCktN!7WRVspP+PaxBL8|IU58LQ=XwGNu5kAqI;2LYvHD)+lV+SQQ&2;1^!d5>BtSi~ zZCTh!L_;t{UB{{k)oqXBjrJrn!WkwCYX17Ds3wIpxk&Gbe7(vut*OjxdKOT7jzTWJ`{F)t7=)4I5z&gm{3q zVJeosc{x~-ayp=r$IYl-!a~wGowXIn>bZMo{|p_>P+gO`gOU=EI9HZJr~ z_b`sJ*_zu~c!@kZFmyw%Fjie>Cgw#@^rgo*j3tgBuglNa9@~=l*^Du8Qoq~`b8v(wq`TG( zo7&)0957Gjx6(Sth2u$Xe^`jom{W48)p{&nKXyZ(->W@UsluS@jSh$-0W6wYYn~{P zl=>@lZMUxrRT85;6{{_5Qp?^JMtJZTZ#Plmd~J$H8oWDD0KOnhjc{kb(%wVhS8lQE zMA}SiF-%#i?mp_>etPJ0W?bHf{-U^Ww6m7NWm8{^PYuk@ z1#g%a`8`BE8Q>#TxNviDwQBUK6^p7drt)2AWf2MOBi1OElFmfI9GGJ+CpS~E=`FSE zoU`>4Huu|hi00xoJc99nMvmQFw1MdMOvl&T>0TZVcy6feF05N4*^xjPS`9S15+^2+ zgKs_j0lzRXUV`o;tIl*D=ZZq+IojG;i8ctNX2%G4#71`UrLZP*0D0X+F8N&^ujI_} zrcDm4?W;Nn@v8TAPM#}bqH!Uf#8TdOp$#UDUl|-Z>a8!M$uI}On2>JDl2%rOfm&gA zb^DCJ{0rkxiwO%2q$l7#wphuz4!Yk1=IG?_=;|wZm7bfg@b5@QXR{D`E>6b?DN^G+ z`ks++L(MwaCH^ye@uO1{4K=<3yMPcT7WnD-9djBE);}6F*Kt zBw>V1WR4IR@7TZ)L&Hs-KQH+|Tu!nMKqFZLg288)fsIV7;cm10c88zjb&R=G@2y!~ z>HX}C^$W9dLkb?=o`}kI(-SJBPvN6%Gdytfh&LN9nfSnc8s8lLP+anmYY4E-L`zvO zt{9iXjx@f8ATy*gJ^)@z@t-k0J{e<9sp#I7P*83Sb3o=ro@u0E$t`2+tl+4KhB4Xm zxN&KKnVT=wx{F-9a2^B)W`&iionP$WR1L3dOYbjr$vStDK;JZdbXF7f4A6dP_Sm$V zXqd^OAK~Z51nB0-C&q?7a6iOEQ(|wqzCvtff;qU#dzA&g=SFe-YK5{``uk{ix_HD) zIJ-s0v#|?4+?O60%fGB|PtAH{&p_F&{7%hULA;`WAJ${xQI1)pvR$Le@5e=)8Bup` zN+&<)^}y$>c=hdaCMH(ssLWg!(4Bl^jI}p%i4HVLa8TZvlY%^&KV5$cnZQk`6mC6R z8)B%@Az8ATFnZrlZHFu$rqr*Cx&NkAm`HNsCjWUTnh=-7+&oMX)s z3r}dx;8(_b#Uo$A&tE1KJEs)5%kxfgmhXBocVNE9@!*$%S56b|1-+R)E5V@gT|gL* zBnDK@$d-~X-JUJ!z0%e|(xG*b`HK_4t>-~JAN6tZyur@fMJ_@R*Nwmeq2L4 z3uSitM!#N7KFE5uf0N?xt*oCI zd>I63xD&sLlqvQfp+`^O6W~rv#7%n(ScFs|;yO4l2C7Q|7iF9`)sPs)W{E0Ynrzcy z&K!oWb9NLyhJ^jc^3yZVfo(2%23$=0U=B(!Ye#M_6SxuVJ#%13RqgIW33k98MU`_P z*lF*1X`<`7BgyiNz*qla5F~djX2>334UaZCc*rY%#u@~RLGW2*s1b)>nx$_l1|u2& z+8rbm-eIGsKXZ4Kne5TrZg(u{EW8df1T|PRnByZ5PI8~s613L3%U09=5cdz-&zOcY zQN%Q=4bn-{t9krRcd6Y1K?!F?8%kbE#9?Zcugg}PlIX)rwT9-O1^1`rlVpwipR&stYG=Gp}R6B3}HwdLbj*+RmuZWIl4xxR5@x_kyqb z#{z@S;(_85<|fHk*t&FA#3tWs|wdi0Iw&$lO8e7J`^NGI#K&1C|c zusV2GjBzx&PLJeFLbxV%SrqP4iV;@kMBhmlY!Cv{S?OwLN>$fF4`NP@cF#|BegxNw zY}G#nqJ4F=0=os@EYY|oAD7L;9H?MKE)1;oqv?!Q*Au2(DJgzK-6__YO^o$SS~R~o zkzrf9?Y1LsH)63WfI5IiStcRA^pb$3yhFNAubI2N<#X!1m%YnS^(bQG>j=6MpR@!NTYxWxId1W17RblEu+TWkLpRzu<+%Vy~WyMxev}zWd#!{;MD-Bk{NqR(NoYUyIOfhm& za>)Vx9&cD(Rr-Ap<*y{32+Tr+CpN0E&^=q-4^})lr6+Kh3%I<%p zs9Y2P0OnBBjgoTCIhcbBm_peBYU&Q#Xo00Wr*iV;x^;hV@9%qhl73;{+V0KZP4h7a z2W6I>>lT4yU;R7EgCEYBugb?tiKpE|3+_Xu7T&EXx4+BME?`H#Sb62u%v~+}U9nuX zoX+Fnx3r_Ci=Kzx2!uH}i$O?Is0!&VQn>-yKvq)Jd%pqpnUK+PU;9u`PN%kAToeAm zi%gVtk-0!hVK6vf#6V?0m8cxMu0XFe)m+Ax_uf}!JIBX_THVDb^9!urX5-QqyW%Lg z3FnQ~^Vaz-!-kZi1?^U9%-o01*n6AGBtQaBoUpve|2(=2xF%NHG`q{0MMQ}2l~XzB z<8NzuAEaz*)`h_w#PG-{Oi}r|o6|*$MsFt^C{FSv;xPVs+k}zV3=J7y*{J!@lapoj zcn=1)3t$HsTz(@hh7ayv1qO@5;u~Io*=PM6GQC|ZETw;c zix9lxGqS0%^*p$WLBtMF_n?Q~B)OjATON6C>YYkr*?oKEMey)(w``ib$?+;W1DUYr zODiI%B7Ql;YC}4IKvcwoK9#wK9Lo7t?ltyK)rpa}pC)9?N=_D+?S530yGtur^}s|U zu1d}|I$lKG;8u(d=MK4MkDbRGAL>3Fx0pUsZT0T8WNZGd)mxa}>+U^KsR(6!?acWu zK3y~9_TJv7#V`j&JdLuHC57iLA(1M6L?KOUIUc@>lRV<|LU9;1LBZyDBhI{$^(Y=A zkMkcNWR#oA>?HxtEWB^#-?|iR-@kNF`%D<=nNp%>-m(;S=L`q&iu~EiH~VW`lS&ud zNlGEcy%Ev`cpYT%0^RpNC`>k-z+%X;G6B+eyeo8^8x_Vl_Pbk&iPuOpeT@{%b!#E2 z43RSyPWv3g_W7{Wr4YjzkCJ8#w3R8Vofw2TvL-tF|jFjJ|#nwAonwYm3wp=SLX*PdOjb^AD6 zXH@R1tWz`5Fb9o%0QA|Ykea7=?MwYeVu}P!i8h7G?g!Oq!=7ZYD9yMJ3-DM}S$$204st6q*Gv9uU943T8h9w0OrXC){J zg8o1Pd-#{<{qc8dR+aYnt6@cEhZQb}NDu*kLO(F5P7Ln-*v9QxMd1 zPf%x*Zh<*?!nYKg@sWT-^qUmva~n;7AWh|3Ro=AYs<5_)_PM$wpT{z zTE$I4ovc&W1qs`2<|7ZNLJ0VbNP*RV6o`YeeW6%IM4}_`A07V-AjCmAvH|*D9|C>J z|GyJGg0YDn^aBFHKWV`)bvGYRZ+kC4ez&vy zrk-bPT=|XNd|d6kY;D}_z4#^gjqPo`yiRV zp->Qpf&@}O;H@!yOG8C%fe;Qt9)CK(ASBS`Q2+>GAPD1BXxp;?IDNyv^`I-jRsMjs z^iTM&j zMM3G|?qB6k14Q88i-Y}Oa$yDe{VE)G*mC=h|CtUpZpoGfp51>)@O@=&sbAIyR}KYl zsbAIqeNwm7FL9_0I389%;c)d^wQZ@NufH|A@5=|CAUMlkWvlFOV}r-|GyW|A#^A7T X=f5WSZ?)lf2Kape{@3G}uM6-$GZ(a) literal 0 HcmV?d00001 diff --git a/servicedesk-gui/src/icons/persons.svg b/servicedesk-gui/src/icons/persons.svg new file mode 100644 index 00000000..b9b09b56 --- /dev/null +++ b/servicedesk-gui/src/icons/persons.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/servicedesk-gui/src/icons/undraw_page_not_found_re_e9o6.svg b/servicedesk-gui/src/icons/undraw_page_not_found_re_e9o6.svg new file mode 100644 index 00000000..5442a706 --- /dev/null +++ b/servicedesk-gui/src/icons/undraw_page_not_found_re_e9o6.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/servicedesk-gui/src/icons/undraw_people_search_re_5rre.svg b/servicedesk-gui/src/icons/undraw_people_search_re_5rre.svg new file mode 100644 index 00000000..5c0d540d --- /dev/null +++ b/servicedesk-gui/src/icons/undraw_people_search_re_5rre.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/servicedesk-gui/src/icons/undraw_void_-3-ggu.svg b/servicedesk-gui/src/icons/undraw_void_-3-ggu.svg new file mode 100644 index 00000000..2288188e --- /dev/null +++ b/servicedesk-gui/src/icons/undraw_void_-3-ggu.svg @@ -0,0 +1,35 @@ + + void + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/servicedesk-gui/src/index.css b/servicedesk-gui/src/index.css deleted file mode 100644 index 6119ad9a..00000000 --- a/servicedesk-gui/src/index.css +++ /dev/null @@ -1,68 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/servicedesk-gui/src/index.scss b/servicedesk-gui/src/index.scss new file mode 100644 index 00000000..092629f5 --- /dev/null +++ b/servicedesk-gui/src/index.scss @@ -0,0 +1,33 @@ +@import "styles/tooltip"; +@import "styles/forms"; +@import "styles/responsive"; + +body:after { + position: fixed; + width: 109px; + height: 28px; + top: 15px; + right: -25px; + text-align: center; + font-size: 16px; + font-family: sans-serif; + text-transform: uppercase; + font-weight: bold; + color: white; + line-height: 30px; + transform: rotate(45deg); +} + +$min-width-pixels: 620px; + +.sds--card--big { + width: $min-width-pixels; + + @media (max-width: 680px) { + width: 100%; + } +} + + + + diff --git a/servicedesk-gui/src/locale/I18n.js b/servicedesk-gui/src/locale/I18n.js new file mode 100644 index 00000000..b1685fa1 --- /dev/null +++ b/servicedesk-gui/src/locale/I18n.js @@ -0,0 +1,41 @@ +import en from "./en"; +import nl from "./nl"; + +import {I18n as I18nRemote} from "i18n-js"; + +import {getParameterByName} from "../utils/QueryParameters"; +import {isEmpty} from "../utils/Utils"; +import Cookies from "js-cookie"; +import {reportError} from "../api"; +import {useAppStore} from "../stores/AppStore"; + +const I18n = new I18nRemote({ + en: en, + nl: nl, +}); + +// DetermineLanguage based on parameter, cookie and finally navigator +let parameterByName = getParameterByName("lang", window.location.search); + +if (isEmpty(parameterByName)) { + parameterByName = Cookies.get("lang"); +} + +if (isEmpty(parameterByName)) { + parameterByName = navigator.language.toLowerCase().substring(0, 2); +} +if (["nl", "en"].indexOf(parameterByName) === -1) { + parameterByName = "en"; +} +I18n.locale = parameterByName; + +I18n.missingTranslation.register("report-error", (i18n, scope) => { + const user = useAppStore.getState().user; + if (user && user.id) { + reportError({"Missing translation": scope}); + } + return `[missing "${scope}" translation]`; +}); +I18n.missingBehavior = "report-error"; + +export default I18n; diff --git a/servicedesk-gui/src/locale/en.js b/servicedesk-gui/src/locale/en.js new file mode 100644 index 00000000..f39f7765 --- /dev/null +++ b/servicedesk-gui/src/locale/en.js @@ -0,0 +1,526 @@ +const en = { + code: "EN", + name: "English", + select_locale: "Change language to English", + languages: { + language: "Language", + languageTooltip: "Choose the language of the invitation mail", + en: "English", + nl: "Dutch", + }, + landing: { + header: { + title: "Manage access to your applications", + login: "Log in", + sup: "SURFconext Invite is by invitation only.", + }, + works: "How does it work?", + adminFunction: "admin function", + info: [ + //Arrays of titles and info blocks and if a function is an admin function + ["Invites", "

SURF invites institution managers who can create roles for their applications.

" + + "

The application list consists of applications connected to SURFconext.

", true], + ["Roles", "

The application managers will invite colleagues for roles who can in turn invite users.

", true], + ["Join", "

Invited colleagues who accept the invitation are granted access rights for the applications.


", false], + ["Groups", "

The roles are actually group memberships that can be used in SURFconext authorisation rules, or provisioned as attributes or to external SCIM APIs.

", false] + ], + footer: "

SURFconext Invite offers access management to SURFconext-connected applications.

" + + "

Do you want to know more? Read more.

", + }, + header: { + title: "SURFconext Invite", + subTitle: "Everything will be owl right", + links: { + login: "Log in", + system: "System", + switchApp: "Go to {{app}}", + welcome: "Welcome", + access: "Invite", + help: "Help", + profile: "Profile", + logout: "Log out" + }, + }, + tabs: { + home: "Home", + applications: "Applications", + users: "Users", + applicationUsers: "Users", + maintainers: "Role managers & inviters", + guests: "User with this role", + invitations: "Invitations", + roles: "Access roles", + profile: "Profile", + userRoles: "Role managers & inviters", + guestRoles: "Users with this role", + cron: "Cron", + invite: "Invite", + tokens: "API tokens", + unknownRoles: "Missing applications", + expiredUserRoles: "User role expirations", + pendingInvitations: "Pending", + allPendingInvitations: "Pending invitations", + acceptedInvitations: "Accepted", + performanceSeed: "Seed", + seed: "Seed" + }, + home: { + access: "SURFconext Invite", + }, + impersonate: { + exit: "Stop impersonating", + impersonator: "You are impersonating {{name}} | {{role}}", + impersonatorTooltip: "You are really {{impersonator}}, but you are impersonating {{currentUser}}.", + flash: { + startedImpersonation: "You now impersonate {{name}}.", + clearedImpersonation: "Cleared your impersonation. You are you again." + }, + }, + access: { + SUPER_USER: "Super User", + INSTITUTION_ADMIN: "Institution admin", + MANAGER: "Manager", + INVITER: "Inviter", + GUEST: "User", + "No member": "No member" + }, + users: { + found: "{{count}} {{plural}} found", + moreResults: "There are more results than shown, please refine your search.", + applicationsSearchPlaceHolder: "Search for application...", + name_email: "Name / email", + name: "Name", + email: "Email", + highestAuthority: "Role", + createdAt: "Created", + schacHomeOrganization: "Institution", + lastActivity: "Last activity", + eduPersonPrincipalName: "EPPN", + sub: "Sub", + singleUser: "user", + multipleUsers: "users", + noEntities: "No users found", + new: "New invitation", + title: "Users", + roles: "Roles", + applications: "Applications", + noRolesInfo: "You have no roles (which means you must be super-user)", + noRolesInstitutionAdmin: "You are an institution admin and you have no roles (but you might have access to applications)", + noRolesNoApplicationsInstitutionAdmin: "You are an institution admin, but you have no roles and apparently your institution has also no access to applications", + guestRoleOnly: "You are not an administrator. Are you looking for for the apps you can access?", + rolesInfo: "You have the following roles", + applicationsInfo: "You have access to the following applications", + noRolesFound: "No roles are found.", + noApplicationsFound: "No applications are found.", + rolesInfoOther: "{{name}} has the following roles", + applicationsInfoOther: "{{name}} has access to the following applications", + landingPage: "Landing page", + access: "Access", + organisation: "Organisation", + noResults: "No users are found", + searchPlaceHolder: "Search for users...", + authority: "Authority", + endDate: "End date", + expiryDays: "Expiry days", + roleExpiryTooltip: "Sort on roles to see which roles will expire the soonest" + }, + role: { + copyUrn: "Copy urn", + userInfo: "{{nbr}} member(s) & valid for {{valid}} days", + roleInfo: "Role valid for {{days}} days", + roleInfoNoEndDate: "Role has no end date", + contactAdmin: "Contact manager(s)" + }, + roles: { + title: "Access Roles", + applicationName: "Application", + auditable: "Role {{name}} was created by {{createdBy}} at {{createdAt}}", + roleDetails: "Role details", + invitationDetails: "Invitation details", + applicationDetails: "Application(s) this role applies to", + addApplication: "Add application", + multiple: "Multiple applications", + applicationPlaceholder: "Choose an application...", + accessRole: "Name", + name: "Name", + namePlaceHolder: "The name of the role", + shortName: "Short name", + organizationGUID: "Organization GUID", + identityProvider: "Identity provider: {{name}}", + landingPage: "(Custom) landing page", + userRoleCount: "# Users", + landingPagePlaceHolder: "https://landingpage.com", + defaultExpiryDays: "Expiry days", + endDate: "End date", + noEndDate: "-", + authority: "Authority", + yourRole: "Your role", + description: "Description", + descriptionPlaceHolder: "The description of the role", + noResults: "No roles are found", + noMember: "No member", + searchPlaceHolder: "Search for roles...", + found: "{{count}} {{plural}} found", + singleRole: "role", + multipleRoles: "roles", + new: "Add new role", + edit: "Edit role {{name}}", + urn: "URN", + advanced: "Advanced settings", + showAdvancedSettings: "Show advanced invite settings", + hideAdvancedSettings: "Hide advanced invite settings", + override: "Override of settings allowed?", + manage: "Application", + manageMetaData: "SURFconext entity", + provisioning: "Provisioning", + deleteFlash: "Role {{name}} has been deleted", + deleteConfirmation: "Are you sure you want to delete this role?", + createFlash: "Role {{name}} has been created", + updateFlash: "Role {{name}} has been updated", + unknownInManage: "Unknown in Manage", + unknownInManageToolTip: "The application for this role has been removed from the SURF backend. Please contact support@surfconext.nl to resolve this.", + unknownInManageDisabled: "The application for this role has been removed from the SURF backend. Therefore, you can't invite new users. Contact support@surfconext.nl to resolve this.", + consequences: { + info: "The following users will lose their access:", + userInfo: "{{name}} ({{authority}}), last activity {{lastActivity}}", + andMore: "And {{nbr}} more.. Check the list of current users for more details." + } + }, + applications: { + title: "Access Roles for this application ({{nbr}})", + applicationFound: "Applications ({{nbr}})", + new: "New Access Role", + searchPlaceHolder: "Search for applications", + noResults: "No applications found...", + name: "Application name", + types: { + saml20_sp: "Service Provider", + oidc10_rp: "Relying Party" + }, + type: "Type", + organization: "Organization", + url: "URL", + roles: "Roles", + provisionings: "Provisionings", + accessRole: "Access Role", + }, + applicationRoles: { + searchPlaceHolder: "Search for Access Roles", + noEntities: "No access roles found", + }, + userRoles: { + found: "{{count}} {{plural}} found", + singleUserRole: "user role", + multipleUserRoles: "user roles", + searchPlaceHolder: "Search for users...", + noResults: "No user roles where found", + guestRoles: "{{count}} users", + managerRoles: "{{count}} managers & inviters", + notAllowed: "You're not allowed to delete this user role because of missing roles", + updateConfirmation: "Are you sure you want to change the end date of role {{roleName}} for {{userName}}?", + updateConfirmationRemoveEndDate: "Are you sure you want to remove the end date of role {{roleName}} for {{userName}}?", + updateFlash: "The end date for role {{roleName}} has been updated", + deleteConfirmation: "Are you sure you want to remove this role from this user(s)?", + deleteOneConfirmation: "Are you sure you want to remove this role from this user?", + deleteFlash: "User role(s) have been removed", + createdAt: "Date accepted", + delete: "Remove" + }, + invitations: { + title: "Invitations", + searchPlaceHolder: "Search for invitation...", + noResults: "No invitation where found", + inviter: "Invited by", + status: "Status", + pending: "pending", + open: "Open", + accepted: "Accepted", + expired: "Expired", + enforceEmailEquality: "Email equality", + eduIDOnly: "eduID only", + new: "Invite manager or inviter", + newInvitation: "Invite inviter", + newInvite: "New invite", + newGuest: "Invite new user", + invitees: "Invitees", + intendedRoles: "Roles", + inviteesPlaceholder: "Invitee email addresses", + requiredEmail: "At least one email address is required", + requiredRole: "At least one role is required for an invitation", + intendedAuthority: "Authority", + roles: "Roles", + inviterRoles: "Select the roles for the new invitation", + rolesPlaceHolder: "Choose one or more roles", + expiryDate: "Valid till", + acceptedAt: "Date accepted", + roleExpiryDate: "Role expiry date", + roleExpiryDateQuestion: "Set a custom role expiration period", + roleExpiryDateInfo: "This role will be removed from the user {{expiry}}", + customInviterDisplayNameQuestion: "Set a custom inviter name", + inviterDisplayName: "Custom inviter display name for invitations", + inviterDisplayNamePlaceholder: "e.g. working@home.university.nl", + inviterDisplayNameError: "Custom inviter name is required, unless you use the default inviter name", + customInviterDisplayNameInfoDefault: "Invitations for this role will show the name of the inviter as sender", + customInviterDisplayNameInfo: "Invitations for this role will show a custom name / email as sender", + expiryDateQuestion: "Set a custom invitation expiration period", + expiryDateInfo: "Default an invitation is valid for 1 month", + withinThreeMonths: "Within 1 month", + createdAt: "Sent", + message: "Personal note", + messagePlaceholder: "Add a personal note to your invitation", + invite: "Send invite", + guestRoleIncluded: "Add the user role?", + invalidEmails: "Invalid email addresses removed: {{emails}}.", + createFlash: "Invitation was sent", + delete: "Revoke", + resend: "Resend", + notAllowed: "You're not allowed to delete or resend this invitation because of missing roles", + deleteFlash: "Invitation(s) have been revoke", + deleteConfirmation: "Are you sure you want to revoke these invitations?", + deleteOneConfirmation: "Are you sure you want to revoke this invitation?", + resendConfirmation: "Are you sure you want to resend these invitations?", + resendConfirmationOne: "Are you sure you want to resend this invitation?", + resendFlash: "Invitation(s) have been resent", + inviterRole: { + title: "Send new invite", + roles: "For the following roles", + to: "To", + message: "Personal note", + settings: "Advanced invite settings" + }, + statuses: { + all: "All ({{nbr}})", + open: "Open", + accepted: "Accepted", + expired: "Expired", + mine: "Mine" + } + }, + forms: { + none: "None", + notApplicable: "N/A", + you: "You", + yes: "Yes", + no: "No", + ok: "OK", + or: "or ", + edit: "Edit", + cancel: "Cancel", + save: "Save", + specificDate: "Set specific date", + and: "and", + more: "More", + less: "Less", + alreadyExists: "The {{attribute}} '{{value}}' already exists", + alreadyExistsParent: "The {{attribute}} {{value}} already exists within {{parent}}", + required: "{{attribute}} is required", + invalid: "The value '{{value}}' is invalid for {{attribute}}", + error: "You can contact support@surfconext.nl for help.

" + + "The reference number for this exception is {{reference}}." + }, + profile: { + info: "The account of {{name}} was created on {{createdAt}}", + your: "Your account was created on {{createdAt}}" + }, + inviteOnly: { + welcome: "Welcome to SURFconext Invite", + roles: "You don't have any roles.", + info: "SURFconext Invite is by invitation only. Please contact support@surfconext.nl with questions.", + preLogin: "Or ", + login: "log in", + postLogin: " again with a different institution", + }, + missingAttributes: { + welcome: "Welcome to SURFconext Invite", + attributes: "Your institution has not provided all required personal data. The following are missing:", + info: "If you want more information, please contact support@surfconext.nl.", + preLogin: "Or ", + login: "login", + postLogin: " again with a different institution.", + sub: "sub", + email: "email", + givenName: "givenName", + familyName: "familyName", + schacHomeOrganization: "schacHomeOrganization" + }, + invitationAccept: { + hi: "Hi{{name}},", + nextStep: "Next: enjoy your new role", + expired: "This invitation has expired at {{expiryDate}}", + expiredInfo: "Please contact {{email}} and ask this person to send you a new invite.", + invited: "You have been invited to become {{authority}} for the {{plural}} {{roles}} by {{inviter}}.", + invitedNoRoles: "You have been invited to become {{authority}} by {{inviter}}", + enforceEmailEquality: " This invite can only be accepted by {{email}}.", + role: "role", + roles: "roles", + progress: "1", + info: "SURFconext Invite provides access to application based on your roles.", + infoLogin: "You can log in with your institution account or eduID.", + infoLoginEduIDOnly: "You need to log in with eduID.", + infoLoginAgain: "To accept the invitation you'll need to log in again.", + login: "Log in", + loginWithSub: "Log in", + emailMismatch: "The inviter has indicated that you must accept this invitation with email address {{email}}, " + + "but you have logged in with an account with a different email address. Please log in in with a different account." + }, + inviter: { + welcome: "Welcome, {{name}}", + info: "Manage who gets access to the educational applications at your institution.", + sendInvite: "Send new invite", + viewHistory: "view history", + manage: "You can manage users and send invites for", + details: "Show details", + history: "Invitation history" + }, + institutionAdmin: { + welcome: "Welcome institution administrator of {{name}}! You can start with creating your first role and subsequently invite managers.", + create: "Create access role" + }, + tokens: { + title: "API tokens", + new: "Add API token", + searchPlaceHolder: "Search for API tokens...", + noEntities: "No API tokens", + titleNew: "Create an API token for {{institutions}}", + backToOverview: "Back to all API tokens", + createdAt: "Created at", + secretDisclaimer: "You can view this API token only once. Copy it and store it somewhere safe.

If the token is lost, delete it and create a new one.", + secret: "API token", + secretValue: "One-way hashed token", + secretTooltip: "The value to use in the X-API-TOKEN header", + description: "Description", + superUserToken: "Super User token", + organizationGUID: "Organization GUID", + descriptionPlaceHolder: "Description for this API token", + descriptionTooltip: "A description explaining the use of this API token", + deleteFlash: "API token has been deleted", + deleteConfirmation: "Are you sure you want to delete this API token?", + createFlash: "API token has been created", + submit: "Submit", + required: "The description is required for an API token", + }, + tooltips: { + userIcon: "User {{name}} provisioned at {{createdAt}} with last activity on {{lastActivity}}", + impersonateIcon: "Impersonate user {{name}}", + roleIcon: "Role {{name}} created at {{createdAt}}", + userRoleIcon: "User role accepted by {{name}} at {{createdAt}}", + invitationIcon: "Invitation for {{email}} sent at {{createdAt}} with expiration date {{expiryDate}}", + roleShortName: "The unique short name of the role within a provisioning. It is used to format the urn and therefore not all characters are allowed.", + organizationGUID: "The Manage organizational identifier to scope the visibility of roles of the institution admin. Only specify a value if you are creating or editing this role on behalf of an institution admin", + roleUrn: "The urn of the role. It is based on the sanitized name and the role identifier. It is used as the unique global identifier of this role and therefore not all characters are allowed.", + manageService: "The required application from SURFconext, with may have an optional provisioning", + defaultExpiryDays: "The default number of days the role will expire, from the moment a user has accepted the invitation for this role", + enforceEmailEqualityTooltip: "When checked the invitee must accept the invitation with an account with the email address where the invitation was sent to", + eduIDOnlyTooltip: "When checked the invitees will be required to log in with eduID", + roleExpiryDateTooltip: "The end date of this role. After this date the role is removed from the user.", + expiryDateTooltip: "The date on which this invitation expires", + inviterDisplayName: "The functional address which will used in the invitations of the role.

Default the name of the inviter is show.", + rolesTooltip: "Select all the roles that the invitee will be granted after accepting the invitation", + intendedAuthorityTooltip: "The authority determines the rights the invitee will be granted on accepting the invitation", + inviteesTooltip: "Add email addresses separated by comma, space or semi-colon or on seperate lines. You can also paste a csv file with line-separated email addresses.", + removeInvitation: "Delete all selected invitations", + removeOneInvitation: "Delete this invitation", + resendInvitation: "Resend all selected invitations", + resendOneInvitation: "Resend this invitation", + inviter: "Send invitations to persons who will - once accepted - gain user access to the application", + overrideSettingsAllowed: "When checked, invitations for this role can override the advanced settings (e.g. email equality, eduID only and the role expiry end date)", + removeUserRole: "Remove all selected user roles", + removeOneUserRole: "Remove this user role", + guestRoleIncludedTooltip: "Do you also want to grant the invitees the user role when they accept the invitation?", + expiredUserRole: "This role will expire soon", + }, + confirmationDialog: { + title: "Confirm", + error: "Error", + subTitle: "This action requires a confirmation", + subTitleError: "An error has occurred", + confirm: "Confirm", + ok: "OK", + cancel: "Cancel", + }, + footer: { + terms: "Terms of Use", + termsLink: "https://support.surfconext.nl/terms-en", + privacy: "Privacy policy", + privacyLink: "https://support.surfconext.nl/privacy-en", + surfLink: "https://surf.nl", + }, + expirations: { + expires: "Expires {{relativeTime}}", + expired: "Expired {{relativeTime}}", + never: "Never expires", + activity: { + now: "Just now", + seconds: "Today", + minute: "Today", + minutes: "Today", + hour: "Today", + hours: "Today", + day: "Yesterday", + days: "This week", + week: "This week", + weeks: "This month", + month: "Last month", + months: "%s months ago", + year: "1 year ago", + years: "%s years ago" + }, + ago: { + now: "just now", + seconds: "%s seconds ago", + minute: "1 minute ago", + minutes: "%s minutes ago", + hour: "1 hour ago", + hours: "%s hours ago", + day: "1 day ago", + days: "%s days ago", + week: "1 week ago", + weeks: "%s weeks ago", + month: "1 month ago", + months: "%s months ago", + year: "1 year ago", + years: "%s years ago" + }, + in: { + now: "right now", + seconds: "after %s seconds", + minute: "after 1 minute", + minutes: "after %s minutes", + hour: "after 1 hour", + hours: "after %s hours", + day: "after 1 day", + days: "after %s days", + week: "after 1 week", + weeks: "after %s weeks", + month: "after 1 month", + months: "after %s months", + year: "after 1 year", + years: "after %s years" + } + }, + notFound: { + alt: "404 Page not found" + }, + system: { + trigger: "Trigger", + clear: "Clear", + cronInfo: "Trigger the cron job to cleanup resources like expired user-roles, orphaned users and in-active users", + cronNotificationsInfo: "Trigger the cron job to send notification mails for user-roles that will expire in X days", + noMails: "No notification mails for user-role expirations were send", + performanceSeedInfo: "The performance seed is dummy / generated data to test the performance of the system. It is deleted when you run the tests.", + performanceSeed: "Insert performance seed", + performanceLoadTime: "Inserted following entities in ~{{minutes}} minutes" + }, + unknownRoles: { + title: "Roles linked to applications unknown in Manage", + searchPlaceHolder: "Search...", + noRoles: "Yeah, no unknown manage applications" + }, + expiredUserRoles: { + title: "Roles to be expired the next month", + searchPlaceHolder: "Zoek...", + noResults: "Yeah, no user-roles to be expired within one month" + } +} + +export default en; diff --git a/servicedesk-gui/src/locale/nl.js b/servicedesk-gui/src/locale/nl.js new file mode 100644 index 00000000..670b9cb3 --- /dev/null +++ b/servicedesk-gui/src/locale/nl.js @@ -0,0 +1,526 @@ +const nl = { + code: "NL", + name: "Nederlands", + select_locale: "Wissel taal naar Nederlands", + languages: { + language: "Taal", + languageTooltip: "Kies de taal van de uitnodigingse-mail", + en: "Engels", + nl: "Nederlands", + }, + landing: { + header: { + title: "Toegang tot je applicaties", + login: "Inloggen", + sup: "SURFconext Invite is alleen op uitnodiging te gebruiken.", + }, + works: "Hoe werkt het?", + adminFunction: "beheerfunctie", + info: [ + //Arrays of titles and info blocks and if a function is an admin function + ["Uitnodigen", "

Instellingsbeheerders kunnen rollen aanmaken voor hun applicaties.

" + + "

De applicatielijst bestaat uit aan SURFconext gekoppeld applicaties.

", true], + ["Rollen", "

Applicatiebeheerders nodigen collega's uit die op hun beurt gebruikers kunnen uitnodigen.

", true], + ["Word lid", "

Uitgenodigde collega's die op de uitnodiging zijn ingegaan, krijgen toegang tot applicaties.


", false], + ["Groepen", "

De rollen zijn eigenlijk groepslidmaatschappen die gebruikt kunnen worden in SURFconext-autorisatieregels, doorgegeven als attributen of via externe SCIM API's.

", false] + ], + footer: "

SURFconext Invite biedt toegangsbeheer voor onderwijs en onderzoek.

" + + "

Meer weten? Lees verder.

", + }, + header: { + title: "SURFconext Invite", + subTitle: "Alles gaat uilstekend", + links: { + login: "Inloggen", + system: "Systeem", + switchApp: "Ga naar {{app}}", + welcome: "Welcome", + access: "Invite", + help: "Help", + profile: "Profiel", + logout: "Uitloggen" + }, + }, + tabs: { + home: "Home", + applications: "Applicaties", + users: "Gebruikers", + applicationUsers: "Gebruikers", + maintainers: "Rolmanagers & uitnodigers", + guests: "Gebruikers met deze rol", + invitations: "Uitnodigingen", + roles: "Toegangsrollen", + profile: "Profiel", + userRoles: "Rolmanagers & uitnodigers", + guestRoles: "Gebruikers", + cron: "Cron", + invite: "Uitnodiging", + tokens: "API-tokens", + unknownRoles: "Missing applications", + expiredUserRoles: "User role expirations", + pendingInvitations: "Open", + allPendingInvitations: "Open uitnodigingen", + acceptedInvitations: "Geaccepteerd", + performanceSeed: "Seed", + seed: "Seed" + }, + home: { + access: "SURFconext Invite", + }, + impersonate: { + exit: "Stop na-apen", + impersonator: "Je doet je voor als {{name}} | {{role}}", + impersonatorTooltip: "Je bent in werkelijkheid {{impersonator}}, maar je doet je voor als {{currentUser}}.", + flash: { + startedImpersonation: "Je doet je voor als {{name}}.", + clearedImpersonation: "Na-apen opgeheven. Je bent weer jezelf." + }, + }, + access: { + SUPER_USER: "Super User", + INSTITUTION_ADMIN: "Instellingsmanager", + MANAGER: "Manager", + INVITER: "Uitnodiger", + GUEST: "Gebruiker", + "No member": "Geen lid" + }, + users: { + found: "{{count}} {{plural}} gevonden", + moreResults: "Er zijn meer resultaten dan getoond kunnen worden, verfijn je zoekopdracht.", + applicationsSearchPlaceHolder: "Zoek applicaties...", + name_email: "Naam / e-mail", + name: "Naam", + email: "E-mail", + highestAuthority: "Rol", + createdAt: "Aangemaakt op", + schacHomeOrganization: "Instelling", + lastActivity: "Laatst actief", + eduPersonPrincipalName: "EPPN", + sub: "Sub", + singleUser: "gebruiker", + multipleUsers: "gebruikers", + noEntities: "Geen gebruikers gevonden", + new: "Nieuwe uitnodiging", + title: "Gebruikers", + roles: "Rollen", + applications: "Applicaties", + noRolesInfo: "Je hebt geen rollen (je bent een super-user)", + noRolesInstitutionAdmin: "Je bent instellingsmanager, maar hebt nog geen rollen (maar je hebt wel toegang tot je applicaties)", + noRolesNoApplicationsInstitutionAdmin: "Je bent instellingsmanager, maar je hebt geen rollen en je instelling heeft blijkbaar ook geen toegang tot applicaties.", + guestRoleOnly: "Je bent geen admin. Ben je op zoek naar de applicaties waar je toegang to hebt?", + rolesInfo: "Je hebt de volgende rollen", + applicationsInfo: "Je hebt toegang tot de volgende applicaties", + noRolesFound: "Geen rollen gevonden.", + noApplicationsFound: "geen applicaties gevonden.", + rolesInfoOther: "{{name}} heeft de volgende rollen", + applicationsInfoOther: "{{name}} heeft toegang tot de volgende applicaties", + landingPage: "Landingspagina", + access: "Access", + organisation: "Aanbieder", + noResults: "Geen gebruikers gevonden", + searchPlaceHolder: "Zoek gebruikers...", + authority: "Autoriteit", + endDate: "Einddatum", + expiryDays: "Verloopdagen", + roleExpiryTooltip: "Sorteer op rollen, om te zien welke rol het eerst zal verlopen" + }, + role: { + copyUrn: "Copy urn", + userInfo: "{{nbr}} leden & geldig voor {{valid}} dagen", + roleInfo: "Rol geldig voor {{days}} dagen", + roleInfoNoEndDate: "Rol heeft geen einddatum", + contactAdmin: "Contact manager(s)" + }, + roles: { + title: "Toegangsrollen", + applicationName: "Applicatie", + auditable: "Rol {{name}} is aangemaakt door {{createdBy}} op {{createdAt}}", + roleDetails: "Details rol", + invitationDetails: "Details uitnodiging", + applicationDetails: "Applicatie(s) voor deze rol", + addApplication: "Applicatie toevoegen", + multiple: "Meerdere applicaties", + applicationPlaceholder: "Kies een applicatie...", + accessRole: "Naam", + name: "Naam", + namePlaceHolder: "Naam van de rol", + shortName: "Korte naam", + organizationGUID: "Organization GUID", + identityProvider: "Identity provider: {{name}}", + landingPage: "(Aangepaste) landingspagina", + userRoleCount: "# Gebruikers", + landingPagePlaceHolder: "https://landingspagina.nl", + defaultExpiryDays: "Verloopdagen", + endDate: "Einddatum", + noEndDate: "-", + authority: "Autoriteit", + yourRole: "Jouw rol", + description: "Omschrijving", + descriptionPlaceHolder: "Omschrijving van het doel van de rol", + noResults: "Geen rollen gevonden", + noMember: "Geen lid", + searchPlaceHolder: "Zoek rollen...", + found: "{{count}} {{plural}} gevonden", + singleRole: "rol", + multipleRoles: "rollen", + new: "Rol toevoegen", + edit: "Bewerk rol {{name}}", + urn: "URN", + advanced: "Advanced settings", + showAdvancedSettings: "Toon geavanceerde configuratie uitnodiging", + hideAdvancedSettings: "Verberg geavanceerde configuratie uitnodiging", + override: "Kan de configuratie worden aangepast bij uitnodigen?", + manage: "Applicatie", + manageMetaData: "SURFconext entity", + provisioning: "Provisioning", + deleteFlash: "Rol {{name}} is verwijderd", + deleteConfirmation: "Weet je zeker dat je deze rol wil verwijderen?", + createFlash: "Rol {{name}} is aangemaakt", + updateFlash: "Rol {{name}} is bijgewerkt", + unknownInManage: "Onbekend in Manage", + unknownInManageToolTip: "De applicatie voor deze rol is verwijderd in de SURF backend. Neem contact op met support@surfconext.nl om dit op te lossen.", + unknownInManageDisabled: "De applicatie voor deze rol is verwijderd in de SURF backend. Daarom kan je geen nieuwe gebruikers uitnodigen. Neem contact op met support@surfconext.nl om dit op te lossen.", + consequences: { + info: "De volgende gebruikers verliezen toegang tot deze rol:", + userInfo: "{{name}} ({{authority}}), laatste activiteit {{lastActivity}}", + andMore: "En nog {{nbr}} meer.. Bekijk de lijst van huidige gebruikers voor meer details." + } + }, + applications: { + title: "Toegangsrollen voor deze applicatie ({{nbr}})", + applicationFound: "Applicaties ({{nbr}})", + new: "Nieuwe toegangsrol", + searchPlaceHolder: "Zoek naar applicaties", + noResults: "Geen applicaties gevonden...", + name: "Applicatienaam", + types: { + saml20_sp: "Service Provider", + oidc10_rp: "Relying Party" + }, + type: "Type", + organization: "Organisatie", + url: "URL", + roles: "Rollen", + provisionings: "Provisionings", + accessRole: "Toegangsrol", + }, + applicationRoles: { + searchPlaceHolder: "Zoek naar Access Rollen", + noEntities: "Geen access rollen gevonden", + }, + userRoles: { + found: "{{count}} {{plural}} gevonden", + singleUserRole: "gebruikersrol", + multipleUserRoles: "gebruikersrollen", + searchPlaceHolder: "Zoek gebruikers...", + noResults: "Geen gebruikersrollen gevonden", + guestRoles: "{{count}} gebruikers", + managerRoles: "{{count}} managers & uitnodigers", + notAllowed: "Je kunt deze gebruikersrol niet verwijderen vanwege ontbrekende rollen", + updateConfirmation: "Weet je zeker dat je de einddatum wil aanpassen van rol {{roleName}} voor {{userName}}?", + updateConfirmationRemoveEndDate: "Weet je zeker dat je de einddatum wil verwijderen van rol {{roleName}} voor {{userName}}?", + updateFlash: "De einddatum van rol {{roleName}} is bijgewerkt", + deleteConfirmation: "Weet je zeker dat je de rol van deze gebruiker(s) wil verwijderen?", + deleteOneConfirmation: "Weet je zeker dat je de rol van deze gebruiker wil verwijderen?", + deleteFlash: "Gebruikersrol(len) zijn verwijderd", + createdAt: "Datum geaccepteerd", + delete: "Verwijder" + }, + invitations: { + title: "Uitnodigingen", + searchPlaceHolder: "Zoek uitnodiging...", + noResults: "Geen uitnodigingen gevonden", + inviter: "Uitgenodigd door", + status: "Status", + pending: "open", + open: "Open", + accepted: "Geaccepteerd", + expired: "Verlopen", + enforceEmailEquality: "E-mailadres moet overeenkomen", + eduIDOnly: "Uitsluitend eduID", + new: "Nodig manager of uitnodiger uit", + newInvitation: "Nodig uitnodiger uit", + newInvite: "Nieuwe uitnodiging", + newGuest: "Gebruiker uitnodigen", + invitees: "Genodigden", + intendedRoles: "Rollen", + inviteesPlaceholder: "E-mailadressen genodigden", + requiredEmail: "Geef minimaal 1 e-mailadres op", + requiredRole: "Minimaal 1 rol is benodigd voor een uitnodiging", + intendedAuthority: "Autoriteit", + roles: "Rollen", + inviterRoles: "Selecteer de rollen voor de nieuwe uitnodiging", + rolesPlaceHolder: "Kies een of meer rollen", + expiryDate: "Geldig tot", + acceptedAt: "Datum geaccepteeerd", + roleExpiryDate: "Verloopdatum rol", + roleExpiryDateQuestion: "Zet een specifieke verloopdatum voor de rol", + roleExpiryDateInfo: "Deze rol wordt verwijderd van de gebruiker {{expiry}}", + customInviterDisplayNameQuestion: "Zet een specifieke naam voor de uitnodiger", + inviterDisplayName: "Specifieke naam uitnodiger voor in de uitnodiging", + inviterDisplayNamePlaceholder: "Bijv. werken@thuis.universiteit.nl", + inviterDisplayNameError: "Specifieke naam uitnodiger is verplicht, tenzij je ervoor kiest om de default uitnodiger naam te gebruiken", + customInviterDisplayNameInfoDefault: "Uitnodigingen voor deze rol zullen de naam van de uitnodiger tonen.", + customInviterDisplayNameInfo: "Uitnodigingen voor deze rol zullen deze specifieke uitnodiger naam tonen.", + expiryDateQuestion: "Zet een specifieke verloopdatum voor de uitnodiging", + expiryDateInfo: "Standaard verloopt een uitnodiging na 1 maand", + withinThreeMonths: "Binnen 1 maand", + createdAt: "Verstuurd", + message: "Persoonlijk bericht", + messagePlaceholder: "Voeg een persoonlijk bericht toe aan de uitnodiging", + invite: "Stuur uitnodiging", + guestRoleIncluded: "Voeg toe als gebruiker?", + invalidEmails: "Ongeldig e-mailadres verwijderd: {{emails}}.", + createFlash: "Uitnodiging is verstuurd", + delete: "Intrekken", + resend: "Stuur opnieuw", + notAllowed: "Je kunt deze uitnodiging niet opnieuw versturen of intrekken vanwege ontbrekende rollen", + deleteFlash: "Uitnodiging(en) ingetrokken", + deleteConfirmation: "Weet je zeker dat je deze uitnodiging(en) wil intrekken?", + deleteOneConfirmation: "Weet je zeker dat je deze uitnodiging wil intrekken?", + resendConfirmation: "Weet je zeker dat je deze uitnodiging(en) opnieuw wil versturen?", + resendConfirmationOne: "Weet je zeker dat je deze uitnodiging opnieuw wil versturen?", + resendFlash: "Uitnodiging(en) opnieuw verstuurd.", + inviterRole: { + title: "Verstuur nieuwe uitnodiging", + roles: "Voor de volgende rollen", + to: "Naar", + message: "Persoonlijke bericht", + settings: "Geavanceerde configuratie uitnodiging" + }, + statuses: { + all: "Alle ({{nbr}})", + open: "Open", + accepted: "Geaccepteerd", + expired: "Verlopen", + mine: "Mijn" + } + }, + forms: { + none: "Geen", + notApplicable: "n.v.t.", + you: "Jijzelf", + yes: "Ja", + no: "Nee", + ok: "Oké", + or: "of ", + edit: "Bewerken", + cancel: "Annuleren", + save: "Opslaan", + specificDate: "Zet specifieke datum", + and: "en", + more: "Meer", + less: "Minder", + alreadyExists: "Het {{attribute}} '{{value}}' bestaat al", + alreadyExistsParent: "Het {{attribute}} {{value}} bestaat al binnen {{parent}}", + required: "{{attribute}} is verplicht", + invalid: "De waarde '{{value}}' is niet geldig voor {{attribute}}", + error: "Je kunt contact opnemen met support@surfconext.nl voor hulp.

" + + "De foutcode is {{reference}}." + }, + profile: { + info: "Het account van {{name}} is aangemaakt op {{createdAt}}", + your: "Je account is aangemaakt op {{createdAt}}" + }, + inviteOnly: { + welcome: "Welkom bij SURFconext Invite", + roles: "Je hebt nog geen rollen.", + info: "SURFconext Invite is op uitnodiging. Neem bij vragen contact op met support@surfconext.nl.", + preLogin: "Of ", + login: "log in", + postLogin: " via een andere instelling", + }, + missingAttributes: { + welcome: "Welkom bij SURFconext Invite", + attributes: "Je instelling gaf niet alle benodigde persoonsgegevens vrij. De volgende ontbreken:", + info: "Hulp nodig? Neem contact met ons op.", + preLogin: "Of ", + login: "log in", + postLogin: " via een andere instelling", + sub: "sub", + email: "email", + givenName: "voornaam", + familyName: "achternaam", + schacHomeOrganization: "schacHomeOrganisatie" + }, + invitationAccept: { + hi: "Hoi{{name}},", + nextStep: "Volgende: veel plezier met deze rol", + expired: "Deze uitnodiging is verlopen op {{expiryDate}}", + expiredInfo: "Neem contact op met {{email}} en vraag om een nieuwe uitnodiging te sturen.", + invited: "Je bent uitgenodigd voor {{plural}} {{roles}} door {{inviter}} om {{authority}} te worden.", + invitedNoRoles: "Je bent uitgenodigd door {{inviter}} om {{authority}} te worden.", + enforceEmailEquality: " Deze uitnodiging kan alleen geaccepteerd worden door {{email}}.", + role: "rol", + roles: "rollen", + progress: "1", + info: "SURFconext Invite geeft toegang tot applicaties op basis van je rol.", + infoLogin: "Je kunt inloggen met je instellings-account of eduID.", + infoLoginEduIDOnly: "Je moet inloggen met eduID.", + infoLoginAgain: "Om de uitnodiging te accepteren moet je opnieuw inloggen.", + login: "Inloggen", + loginWithSub: "Inloggen", + emailMismatch: "De uitnodiger heeft aangegeven dat je de uitnodiging dient te accepteren met e-mailadres {{email}}, " + + "maar je bent ingelogd met een account met een ander e-mailadres. Log opnieuw in met een ander account." + }, + inviter: { + welcome: "Welkom, {{name}}", + info: "Manage wie toegang krijgt tot onderwijsapplicaties bij jouw instelling.", + sendInvite: "Verstuur een uitnodiging", + viewHistory: "toon geschiedenis", + manage: "Je kunt gebruikers beheren en uitnodigingen versturen voor", + details: "Toon details", + history: "Geschiedenis van uitnodigingen" + }, + institutionAdmin: { + welcome: "Welkom instellingsadmin van {{name}}! Je kan nu je eerste rol aanmaken en managers daarvoor uitnodigen.", + create: "Rol aanmaken" + }, + tokens: { + title: "API-tokens", + new: "API-token toevoegen", + searchPlaceHolder: "Zoek naar API-tokens...", + noEntities: "Geen API-tokens", + titleNew: "Maak een API-token aan voor {{institutions}}", + backToOverview: "Terug naar alle API-tokens", + createdAt: "Aangemaakt op", + secretDisclaimer: "Je kan het API-token maar één keer zien. Kopieer het en bewaar het ergens veilig.

Als je het token kwijtraakt, verwijder het dan en maak een nieuwe aan.", + secret: "API-token", + secretValue: "One-way hashed token", + secretTooltip: "De waarde die je gebruikt in de X-API-TOKEN header", + description: "Omschrijving", + superUserToken: "Super User token", + organizationGUID: "Organization GUID", + descriptionPlaceHolder: "Omschrijving voor dit API-token", + descriptionTooltip: "Een omschrijving die de reden voor dit API-token omschrijft", + deleteFlash: "API-token is verwijderd", + deleteConfirmation: "Weet je zeker dat je dit API-token wil verwijderen?", + createFlash: "API-token is aangemaakt", + submit: "Opslaan", + required: "Een omschrijving is verplicht voor een API-token", + }, + tooltips: { + userIcon: "Gebruiker {{name}} geprovisiond op {{createdAt}}, laatst actief op {{lastActivity}}", + impersonateIcon: "Doe gebruiker {{name}} na", + roleIcon: "Rol {{name}} aangemaakt op {{createdAt}}", + userRoleIcon: "Gebruikersrol geaccepteerd door {{name}} op {{createdAt}}", + invitationIcon: "Uitnodiging aan {{email}} verstuurd op {{createdAt}} met verloopdatum {{expiryDate}}", + roleShortName: "Een unieke korte naam voor de rol binnen een provisioning. Wordt gebruikt in de urn, daarom zijn niet alle tekens toegestaan.", + organizationGUID: "The Manage organizational identifier to scope the visibility of roles of the institution admin. Only specify a value if you are creating or editing this role on behalf of an institution admin", + roleUrn: "De urn van deze rol. Deze is gebaseerd op de opgeschoonde naam en de rol-identifier. Hij wordt gebruikt als de unieke globale identifier van deze rol en daarom zijn niet alle tekens toegestaan.", + manageService: "De vereiste applicatie uit SURFconext, die optioneel een provisioning heeft.", + defaultExpiryDays: "Het standaardaantal dagen waarna de rol verloopt, gerekend vanaf het moment dat de gebruiker de uitnodiging voor de rol accepteert.", + enforceEmailEqualityTooltip: "Indien ingeschakeld moet de genodigde de uitnodiging accepteren met een account dat hetzelfde e-mailadres voert als waarheen deze uitnodiging gestuurd is", + eduIDOnlyTooltip: "Indien ingeschakeld moeten de genodigden eduID gebruiken om in te loggen bij het accepteren", + roleExpiryDateTooltip: "De einddatum van deze rol. Na deze datum wordt de rol verwijderd bij de gebruiker.", + expiryDateTooltip: "De datum waarop deze uitnodiging verloopt", + inviterDisplayName: "De specifieke naam van de uitnodiger zal worden getoond in de uitnodiging.

Standaard tonen we de naam van de daadwerkelijke uitnodiger.", + rolesTooltip: "Alle rollen die de genodigden verkrijgen na het accepteren van de uitnodiging", + intendedAuthorityTooltip: "De autoriteit geeft de rechten aan die de genodigde verwerft na het accepteren van de uitnodiging", + inviteesTooltip: "Geef e-mailadressen op, gescheiden door komma, spatie of puntkomma, of op een eigen regel. Je kunt ook een csv-bestand plakken met daarin op elke regel een e-mailadres.", + removeInvitation: "Verwijder alle geselecteerde uitnodigingen", + removeOneInvitation: "Verwijder deze uitnodiging", + resendInvitation: "Verstuur al de geselecteerde uitnodigingen opnieuw", + resendOneInvitation: "Verstuur deze uitnodiging opnieuw", + inviter: "Verstuur uitnodigingen naar gebruikers die - na accepteren - en rol krijgen voor de applicatie", + overrideSettingsAllowed: "Indien ingeschakeld kunnen uitnodigingen voor de deze rol de geavanceerde configuratie aanpassen (waaronder of e-mailadres overeen moet komen, alleen accepteren met eduID en de verloop- en einddatum)", + removeUserRole: "Verwijder alle geselecteerde rollen", + removeOneUserRole: "Verwijder deze rol", + guestRoleIncludedTooltip: "Wil je dat de uitgenodigden ook gebruiker worden als ze de uitnodiging accepteren?", + expiredUserRole: "Deze rol gaat verlopen binnen een maand", + }, + confirmationDialog: { + title: "Bevestig", + error: "Fout", + subTitle: "Deze actie vereist bevestiging", + subTitleError: "Er is een fout opgetreden", + confirm: "Bevestig", + ok: "OK", + cancel: "Annuleer", + }, + footer: { + terms: "Gebruiksvoorwaarden", + termsLink: "https://support.surfconext.nl/terms-nl", + privacy: "Privacybeleid", + privacyLink: "https://support.surfconext.nl/privacy-nl", + surfLink: "https://surf.nl", + }, + expirations: { + expires: "Verloopt {{relativeTime}}", + expired: "Verlopen {{relativeTime}}", + never: "Verloopt niet", + activity: { + now: "Zojuist", + seconds: "Vandaag", + minute: "Vandaag", + minutes: "Vandaag", + hour: "Vandaag", + hours: "Vandaag", + day: "Gisteren", + days: "Deze week", + week: "Deze week", + weeks: "Deze maand", + month: "Vorige maand", + months: "%s maanden geleden", + year: "1 jaar geleden", + years: "%s jaar geleden" + }, + ago: { + now: "zojuist", + seconds: "%s seconden geleden", + minute: "1 minuut geleden", + minutes: "%s minuten geleden", + hour: "1 uur geleden", + hours: "%s uren geleden", + day: "1 dag geleden", + days: "%s dagen geleden", + week: "1 week geleden", + weeks: "%s weken geleden", + month: "1 maand geleden", + months: "%s maanden geleden", + year: "1 jaar geleden", + years: "%s jaren geleden" + }, + in: { + now: "zojuist", + seconds: "na %s seconden", + minute: "na 1 minuut", + minutes: "na %s minuten", + hour: "na 1 uur", + hours: "na %s uur", + day: "na 1 dag", + days: "na %s dagen", + week: "na 1 week", + weeks: "na %s weken", + month: "onaver 1 maand", + months: "na%s maanden", + year: "na 1 jaar", + years: "na %s jaar" + } + }, + notFound: { + alt: "404 Pagina niet gevonden" + }, + system: { + trigger: "Trigger", + clear: "Clear", + cronInfo: "Roep de cron job aan die resources opruimt, zoals verlopen gebruikersrollen, verweesde gebruikers en inactieve gebruikers", + cronNotificationsInfo: "Roep de cron job aan die notificatie mails verstuurd voor gebruikersrollen die verlopen in X dagen", + noMails: "Geen notificatie mails zijn vertstuurd voor bijna verlopen gebruikersrollen", + performanceSeedInfo: "The performance seed is dummy / generated data to test the performance of the system. It is deleted when you run the tests.", + performanceSeed: "Insert performance seed", + performanceLoadTime: "Inserted following entities in ~{{minutes}} minutes" + }, + unknownRoles: { + title: "Rollen gekoppeld aan applicaties die verwijderd zijn in Manage", + searchPlaceHolder: "Zoek...", + noRoles: "Yeah, no unknown manage applications" + }, + expiredUserRoles: { + title: "Roles to be expired the next month", + searchPlaceHolder: "Zoek...", + noResults: "Yeah, no user-roles to be expired within one month" + } +} + +export default nl; diff --git a/servicedesk-gui/src/main.jsx b/servicedesk-gui/src/main.jsx index 05582f32..db880dbe 100644 --- a/servicedesk-gui/src/main.jsx +++ b/servicedesk-gui/src/main.jsx @@ -1,13 +1,18 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' +import {createRoot} from 'react-dom/client' import App from './App.jsx' +import {BrowserRouter, Route, Routes} from "react-router-dom"; //Do not change the order of @surfnet.sds style imports import '@surfnet/sds/styles/sds.css'; import '@surfnet/sds/cjs/index.css'; +import "react-tooltip/dist/react-tooltip.css"; +//Always keep these two last +import './index.scss'; + createRoot(document.getElementById('root')).render( - - - , -) + + + }/> + + +); \ No newline at end of file diff --git a/servicedesk-gui/src/pages/App.js b/servicedesk-gui/src/pages/App.js new file mode 100644 index 00000000..a60d10e4 --- /dev/null +++ b/servicedesk-gui/src/pages/App.js @@ -0,0 +1,125 @@ +import './App.scss'; +import {Navigate, Route, Routes, useNavigate} from "react-router-dom"; +import {useEffect, useState} from "react"; +import {Loader} from "@surfnet/sds"; +import {useAppStore} from "../stores/AppStore"; +import {configuration, csrf, me} from "../api"; +import {Login} from "./Login"; +import {Home} from "./Home"; +import {Flash} from "../components/Flash"; +import {Header} from "../components/Header"; +import {Footer} from "../components/Footer"; +import {BreadCrumb} from "../components/BreadCrumb"; +import {Invitation} from "./Invitation"; +import {login} from "../utils/Login"; +import NotFound from "./NotFound"; +import {Impersonating} from "../components/Impersonating"; +import RefreshRoute from "./RefreshRoute"; +import {InviteOnly} from "./InviteOnly"; +import {Profile} from "./Profile"; +import {Role} from "./Role"; +import {RoleForm} from "./RoleForm"; +import {InvitationForm} from "./InvitationForm"; +import {isEmpty} from "../utils/Utils"; +import {MissingAttributes} from "./MissingAttributes"; +import {Inviter} from "./Inviter"; +import {Application} from "./Application"; +import {System} from "./System"; + + +export const App = () => { + + + useEffect(() => { + setLoading(true); + csrf().then(token => { + useAppStore.setState(() => ({csrfToken: token.token})); + configuration() + .then(res => { + useAppStore.setState(() => ({config: res})); + if (!res.authenticated) { + if (!res.name) { + const direction = window.location.pathname + window.location.search; + localStorage.setItem("location", direction); + } else if (!isEmpty(res.missingAttributes)) { + setLoading(false); + navigate("/missingAttributes"); + return; + } + const pathname = localStorage.getItem("location") || window.location.pathname; + const isInvitationAcceptFlow = window.location.pathname.startsWith("/invitation/accept"); + if (res.name && !pathname.startsWith("/invitation/accept") && !isInvitationAcceptFlow) { + setLoading(false); + navigate("/deadend"); + } else if (pathname === "/" || pathname.startsWith("/login") || pathname.startsWith("/invitation/accept") || isInvitationAcceptFlow) { + setLoading(false); + navigate(isInvitationAcceptFlow ? (window.location.pathname + window.location.search) : pathname); + } else { + //Bookmarked URL's trigger a direct login and skip the landing page + login(res); + } + } else { + me() + .then(res => { + useAppStore.setState(() => ({user: res, authenticated: true})); + setLoading(false); + const location = localStorage.getItem("location") || window.location.pathname + window.location.search; + const newLocation = location.startsWith("/login") ? "/home" : location; + localStorage.removeItem("location"); + navigate(newLocation); + }); + } + }) + .catch(() => { + setLoading(false); + navigate("/deadend"); + }) + }) + }, [reload, impersonator]); // eslint-disable-line react-hooks/exhaustive-deps + + if (loading) { + return + } + return ( +
+
+ +
+ {impersonator && } + + {authenticated && } + {authenticated && + + }/> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + {(user && user.superUser) && + }/> + } + }/> + } + {!authenticated && + + }/> + }/> + }/> + }/> + }/> + }/> + }/> + } +
+ {
} +
+ ); +} diff --git a/servicedesk-gui/src/pages/App.scss b/servicedesk-gui/src/pages/App.scss new file mode 100644 index 00000000..30f8b6f4 --- /dev/null +++ b/servicedesk-gui/src/pages/App.scss @@ -0,0 +1,15 @@ +.access { + display: flex; + flex-direction: column; + min-height: 100vh; + position: relative; + width: 100%; +} + +.sds--modal.sds--backdrop { + z-index: 99; +} + +.react-datepicker-popper { + z-index: 3 !important; +} \ No newline at end of file diff --git a/servicedesk-gui/src/pages/Login.js b/servicedesk-gui/src/pages/Login.js new file mode 100644 index 00000000..26b1d7cd --- /dev/null +++ b/servicedesk-gui/src/pages/Login.js @@ -0,0 +1,52 @@ +import {Button, ButtonSize, ButtonType} from "@surfnet/sds"; +import './Login.scss'; +import I18n from "../locale/I18n"; +import DOMPurify from "dompurify"; +import {LandingInfo} from "../components/LandingInfo"; +import HappyLogo from "../icons/landing/undraw_startled_-8-p0r.svg"; +import {login} from "../utils/Login"; +import {useAppStore} from "../stores/AppStore"; +import {isEmpty} from "../utils/Utils"; +import {useLocation} from "react-router-dom"; +import {useState} from "react"; + +export const Login = () => { + const location = useLocation(); + const [spin, setSpin] = useState(false); + + const config = useAppStore((state) => state.config); + + const doLogin = () => { + const force = location.state === "force"; + login(config, !isEmpty(force)); + } + + const toggleSpin = () => { + setSpin(true); + setTimeout(() => setSpin(false), 725); + } + + return ( +
+
+
+
+

+

+
+ toggleSpin()} src={HappyLogo} alt="logo"/> +
+
+
+ +
+ ); + +} \ No newline at end of file diff --git a/servicedesk-gui/src/pages/Login.scss b/servicedesk-gui/src/pages/Login.scss new file mode 100644 index 00000000..57d77569 --- /dev/null +++ b/servicedesk-gui/src/pages/Login.scss @@ -0,0 +1,150 @@ +@import "../styles/vars.scss"; + +.top-container { + + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + flex-grow: 2; + background-color: $background; + + .mod-login-container { + background-color: white; + + @media (max-width: $medium) { + padding: 0 15px; + } + + .header-title { + margin-bottom: 60px; + } + + &.bottom { + background-color: $background; + } + + .mod-login { + margin: 60px auto 0; + max-width: 768px; + width: 100%; + display: flex; + padding-bottom: 35px; + + @media (max-width: $medium) { + flex-direction: column; + } + + &.reversed { + flex-direction: row-reverse; + @media (max-width: $medium) { + flex-direction: column; + } + } + + &.bottom { + flex-direction: column; + margin: 30px auto 0; + } + + &.info { + background-color: white; + border-radius: 16px; + padding: 25px 40px; + margin: 30px auto 0; + min-height: 244px; + + @media (max-width: $medium) { + padding: 15px 20px; + } + } + + .header-left { + margin-right: 40px; + width: 50%; + + button.sds--btn--primary { + width: 280px; + padding: 15px 60px; + font-size: 22px; + } + + @media (max-width: $medium) { + width: auto; + margin: 25px auto; + } + + &.info { + width: 50%; + @media (max-width: $medium) { + width: auto; + margin: 25px auto; + } + + } + } + + .header-right { + width: 48%; + @media (max-width: $medium) { + width: auto; + margin: 25px auto; + } + + @keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + + img { + width: 310px; + height: auto; + + &.spin { + animation: logo-spin 1 0.5s linear; + } + + @media (max-width: $medium) { + width: 180px; + } + + } + + &.info { + display: flex; + width: 55%; + @media (max-width: $medium) { + width: auto; + margin: 25px auto; + } + + img { + margin: 0 0 0 20px; + width: 190px; + height: auto; + + &.reversed { + margin: 0 40px 0 0; + } + + @media (max-width: $medium) { + width: 180px; + } + + } + } + } + } + + p.sup { + margin-top: 20px; + font-size: 18px; + line-height: 22px; + } + + } +} diff --git a/servicedesk-gui/src/pages/NotFound.js b/servicedesk-gui/src/pages/NotFound.js new file mode 100644 index 00000000..93e3f202 --- /dev/null +++ b/servicedesk-gui/src/pages/NotFound.js @@ -0,0 +1,11 @@ +import "./NotFound.scss"; +import React from "react"; +import NotFoundLogo from "../icons/undraw_page_not_found_re_e9o6.svg"; +import I18n from "../locale/I18n"; + +const NotFound = () => ( +
+ {I18n.t("notFound.alt")}/ +
+); +export default NotFound; \ No newline at end of file diff --git a/servicedesk-gui/src/pages/NotFound.scss b/servicedesk-gui/src/pages/NotFound.scss new file mode 100644 index 00000000..ca6f8318 --- /dev/null +++ b/servicedesk-gui/src/pages/NotFound.scss @@ -0,0 +1,13 @@ +@import "../styles/vars.scss"; + +div.not-found { + display: flex; + height: 50vh; + + img { + margin: 25px auto 25px auto; + width: 460px; + height: auto; + + } +} \ No newline at end of file diff --git a/servicedesk-gui/src/stores/AppStore.js b/servicedesk-gui/src/stores/AppStore.js new file mode 100644 index 00000000..dc78c85c --- /dev/null +++ b/servicedesk-gui/src/stores/AppStore.js @@ -0,0 +1,19 @@ +import {create} from 'zustand' + +export const useAppStore = create(set => ({ + csrfToken: null, + authenticated: false, + reload: false, + config: {}, + user: {}, + flash: {msg: "", className: "hide", type: "info"}, + setFlash: (message, type) => { + set({flash: {msg: message, type: type || "info"}}); + if (!type || type === "info") { + setTimeout(() => set({flash: {}}), 5000); + } + }, + clearFlash: () => set({flash: {}}), + //[{path: "/roles/4", value: role.name}] + breadcrumbPath: [] +})); diff --git a/servicedesk-gui/src/styles/circle.scss b/servicedesk-gui/src/styles/circle.scss new file mode 100644 index 00000000..83af1166 --- /dev/null +++ b/servicedesk-gui/src/styles/circle.scss @@ -0,0 +1,75 @@ +$width: 70px; + +.circle { + display: inline-block; + position: relative; + text-align: center; + width: $width; + min-width: $width; + height: $width; + margin-right: 20px; + + + @mixin colored-borders { + content: ''; + display: inline-block; + width: $width; + height: $width; + border-radius: 50%; + border: solid 5px var(--sds--color--gray--200); + position: absolute; + top: 0; + left: 0; + transform: rotate(45deg); + } + + &.one-third:before { + @include colored-borders; + border: solid 5px transparent; + border-top-color: var(--sds--palette--main-color--400); + z-index: 2; + } + + &.one-third:after { + @include colored-borders; + border-top-color: var(--sds--palette--main-color--400); + transform: rotate(78deg); + } + + &.two-third:before { + @include colored-borders; + border: solid 5px transparent; + border-top-color: var(--sds--palette--main-color--400); + z-index: 2; + } + + &.two-third:after { + @include colored-borders; + border-top-color: var(--sds--palette--main-color--400); + border-right-color: var(--sds--palette--main-color--400); + transform: rotate(102deg); + } + + &.two-quarters:before { + @include colored-borders; + border-top-color: var(--sds--palette--main-color--400); + border-right-color: var(--sds--palette--main-color--400); + } + + span { + display: inline-block; + margin-top: 25px; + } + + &.full { + border: solid 5px var(--sds--palette--main-color--400); + border-radius: 50%; + + span { + margin-top: 20px; + } + + } + + +} diff --git a/servicedesk-gui/src/styles/forms.scss b/servicedesk-gui/src/styles/forms.scss new file mode 100644 index 00000000..8f0c68c7 --- /dev/null +++ b/servicedesk-gui/src/styles/forms.scss @@ -0,0 +1,34 @@ +.sds--loading { + position: fixed; + top: 35%; + left: 49%; +} + +.cut-of-lines { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + word-break: break-word; +} + +.sds--card { + margin-bottom: 25px; + background-color: var(--sds--color--white); + border-radius: 0.175rem; + box-shadow: 0 0.1rem 0.1rem 0 var(--sds--color--pitch-black--dimmed--25); +} + +button.sds--btn span { + text-align: center; +} + +section.actions { + border-top: 2px solid #e0e0df; + display: flex; + gap: 25px; + justify-content: flex-end; + margin-top: 30px; + padding: 30px 0; + position: relative; +} diff --git a/servicedesk-gui/src/styles/mixins.scss b/servicedesk-gui/src/styles/mixins.scss new file mode 100644 index 00000000..ccd65591 --- /dev/null +++ b/servicedesk-gui/src/styles/mixins.scss @@ -0,0 +1,80 @@ +@import "../styles/vars"; + + +@mixin input-field { + label { + display: inline-block; + min-width: 210px; + color: black; + position: relative; + margin: 12px 0 var(--sds--space--1); + } +} + +@mixin page { + max-width: $medium; + width: 100%; + margin: 0 auto; + + @media (max-width: $medium) { + padding: 0 15px; + } + +} + +@mixin form { + grid-column-gap: 50px; + -webkit-column-gap: 50px; + column-gap: 50px; + display: grid; + grid-template-columns: [first] 1fr [second] 1fr; + + .input-field, .select-field, .date-field, .error-indication, .sds--checkbox-container { + grid-column-start: first; + } + + .input-field, .select-field, .date-field, .sds--checkbox-container { + margin-top: 20px; + + &.inner-switch { + margin-top: 0; + padding-bottom: 15px; + border-bottom: 1px solid var(--sds--color--gray--200); + } + } + + .user-role .sds--checkbox-container { + margin-top: 0; + @media (max-width: $medium) { + margin-bottom: 20px; + } + } + + .actions { + grid-column-start: first; + margin-top: 30px; + padding: 25px 0; + border-top: 2px solid var(--sds--color--gray--200); + display: flex; + justify-content: flex-end; + position: relative; + + button:not(:first-child) { + margin-left: 25px; + } + + button.sds--btn--delete, button.sds--btn--delete--secondary { + margin-right: auto; + margin-left: 0; + } + + span.error { + position: absolute; + top: 72px + } + + } + @media (max-width: $medium) { + grid-template-columns: [first] 1fr; + } +} diff --git a/servicedesk-gui/src/styles/responsive.scss b/servicedesk-gui/src/styles/responsive.scss new file mode 100644 index 00000000..57a1db1d --- /dev/null +++ b/servicedesk-gui/src/styles/responsive.scss @@ -0,0 +1,33 @@ +@import "../styles/vars"; + +table { + + @media (max-width: $medium) { + + thead { + display: none; + } + + tr { + margin-bottom: 15px; + display: block; + border-bottom: 3px solid var(--sds--color--gray--300); + } + + td { + display: flex; + text-align: right; + border-bottom: 1px dotted var(--sds--color--blue--100); + + padding: 8px 0; + + &:before { + content: attr(data-label); + margin-right: auto; + font-weight: bold; + font-size: 14px; + color: black; + } + } + } +} diff --git a/servicedesk-gui/src/styles/tooltip.scss b/servicedesk-gui/src/styles/tooltip.scss new file mode 100644 index 00000000..2d2fe0b8 --- /dev/null +++ b/servicedesk-gui/src/styles/tooltip.scss @@ -0,0 +1,5 @@ +:root { + --rt-color-dark: var(--sds--color--gray--500); + --rt-color-white: var(--sds--color--white); + --rt-opacity: 0.98; +} diff --git a/servicedesk-gui/src/styles/vars.scss b/servicedesk-gui/src/styles/vars.scss new file mode 100644 index 00000000..7cf678b0 --- /dev/null +++ b/servicedesk-gui/src/styles/vars.scss @@ -0,0 +1,5 @@ +$background: #eaebf0; +$br: 6px; +// Screen +$medium: 1280px; +$tablet-max: 824px; diff --git a/servicedesk-gui/yarn.lock b/servicedesk-gui/yarn.lock index c9d1d0a2..afa57caf 100644 --- a/servicedesk-gui/yarn.lock +++ b/servicedesk-gui/yarn.lock @@ -10,7 +10,7 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== @@ -67,7 +67,7 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-module-imports@^7.25.9": +"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== @@ -133,6 +133,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.25.9" +"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" @@ -163,6 +170,94 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" +"@emotion/babel-plugin@^11.13.5": + version "11.13.5" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz#eab8d65dbded74e0ecfd28dc218e75607c4e7bc0" + integrity sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/serialize" "^1.3.3" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + +"@emotion/cache@^11.14.0", "@emotion/cache@^11.4.0": + version "11.14.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.14.0.tgz#ee44b26986eeb93c8be82bb92f1f7a9b21b2ed76" + integrity sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA== + dependencies: + "@emotion/memoize" "^0.9.0" + "@emotion/sheet" "^1.4.0" + "@emotion/utils" "^1.4.2" + "@emotion/weak-memoize" "^0.4.0" + stylis "4.2.0" + +"@emotion/hash@^0.9.2": + version "0.9.2" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b" + integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== + +"@emotion/memoize@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" + integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== + +"@emotion/react@^11.8.1": + version "11.14.0" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.14.0.tgz#cfaae35ebc67dd9ef4ea2e9acc6cd29e157dd05d" + integrity sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.13.5" + "@emotion/cache" "^11.14.0" + "@emotion/serialize" "^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.2.0" + "@emotion/utils" "^1.4.2" + "@emotion/weak-memoize" "^0.4.0" + hoist-non-react-statics "^3.3.1" + +"@emotion/serialize@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.3.tgz#d291531005f17d704d0463a032fe679f376509e8" + integrity sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA== + dependencies: + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/unitless" "^0.10.0" + "@emotion/utils" "^1.4.2" + csstype "^3.0.2" + +"@emotion/sheet@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c" + integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== + +"@emotion/unitless@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.10.0.tgz#2af2f7c7e5150f497bdabd848ce7b218a27cf745" + integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg== + +"@emotion/use-insertion-effect-with-fallbacks@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz#8a8cb77b590e09affb960f4ff1e9a89e532738bf" + integrity sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg== + +"@emotion/utils@^1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.2.tgz#6df6c45881fcb1c412d6688a311a98b7f59c1b52" + integrity sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA== + +"@emotion/weak-memoize@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6" + integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== + "@esbuild/aix-ppc64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz#b57697945b50e99007b4c2521507dc613d4a648c" @@ -341,6 +436,26 @@ dependencies: levn "^0.4.1" +"@floating-ui/core@^1.6.0": + version "1.6.8" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.8.tgz#aa43561be075815879305965020f492cdb43da12" + integrity sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA== + dependencies: + "@floating-ui/utils" "^0.2.8" + +"@floating-ui/dom@^1.0.1", "@floating-ui/dom@^1.6.1": + version "1.6.12" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.12.tgz#6333dcb5a8ead3b2bf82f33d6bc410e95f54e556" + integrity sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w== + dependencies: + "@floating-ui/core" "^1.6.0" + "@floating-ui/utils" "^0.2.8" + +"@floating-ui/utils@^0.2.8": + version "0.2.8" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62" + integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig== + "@humanfs/core@^0.19.1": version "0.19.1" resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" @@ -612,21 +727,50 @@ dependencies: "@babel/types" "^7.20.7" +"@types/cookie@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" + integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA== + "@types/estree@1.0.6", "@types/estree@^1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== +"@types/http-proxy@^1.17.15": + version "1.17.15" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.15.tgz#12118141ce9775a6499ecb4c01d02f90fc839d36" + integrity sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ== + dependencies: + "@types/node" "*" + "@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/node@*": + version "22.10.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.2.tgz#a485426e6d1fdafc7b0d4c7b24e2c78182ddabb9" + integrity sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ== + dependencies: + undici-types "~6.20.0" + +"@types/parse-json@^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== + "@types/react-dom@^19.0.2": version "19.0.2" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.0.2.tgz#ad21f9a1ee881817995fd3f7fd33659c87e7b1b7" integrity sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg== +"@types/react-transition-group@^4.4.0": + version "4.4.12" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" + integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== + "@types/react@^19.0.2": version "19.0.2" resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.2.tgz#9363e6b3ef898c471cb182dd269decc4afc1b4f6" @@ -634,6 +778,11 @@ dependencies: csstype "^3.0.2" +"@types/trusted-types@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" + integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== + "@vitejs/plugin-react@^4.3.4": version "4.3.4" resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz#c64be10b54c4640135a5b28a2432330e88ad7c20" @@ -655,6 +804,11 @@ acorn@^8.14.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== +agent-base@^7.1.0, agent-base@^7.1.2: + version "7.1.3" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1" + integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw== + ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -754,6 +908,11 @@ arraybuffer.prototype.slice@^1.0.3: is-array-buffer "^3.0.4" is-shared-array-buffer "^1.0.2" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + available-typed-arrays@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" @@ -761,11 +920,25 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" +babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +bignumber.js@*: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -827,6 +1000,11 @@ chokidar@^4.0.0: dependencies: readdirp "^4.0.1" +classnames@^2.3.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -839,16 +1017,44 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +convert-source-map@^1.5.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.2.tgz#27360701532116bd3f1f9416929d176afe1e4610" + integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== + +cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" @@ -858,11 +1064,26 @@ cross-spawn@^7.0.6: shebang-command "^2.0.0" which "^2.0.1" +cssstyle@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-4.1.0.tgz#161faee382af1bafadb6d3867a92a19bcb4aea70" + integrity sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA== + dependencies: + rrweb-cssom "^0.7.1" + csstype@^3.0.2: version "3.1.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== +data-urls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde" + integrity sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg== + dependencies: + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" + data-view-buffer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" @@ -890,6 +1111,13 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" is-data-view "^1.0.1" +debug@4, debug@^4.3.4, debug@^4.3.6: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + debug@^4.1.0, debug@^4.3.1, debug@^4.3.2: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" @@ -897,6 +1125,11 @@ debug@^4.1.0, debug@^4.3.1, debug@^4.3.2: dependencies: ms "^2.1.3" +decimal.js@^10.4.3: + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -920,6 +1153,11 @@ define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + detect-libc@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" @@ -932,11 +1170,38 @@ doctrine@^2.1.0: dependencies: esutils "^2.0.2" +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + +dompurify@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.2.3.tgz#05dd2175225324daabfca6603055a09b2382a4cd" + integrity sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA== + optionalDependencies: + "@types/trusted-types" "^2.0.7" + electron-to-chromium@^1.5.41: version "1.5.50" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.50.tgz#d9ba818da7b2b5ef1f3dd32bce7046feb7e93234" integrity sha512-eMVObiUQ2LdgeO1F/ySTXsvqvxb6ZH2zPGaMYsWzRDdOddUa77tdmI0ltg+L16UpbWdhPmuF3wIQYyQq65WfZw== +entities@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2, es-abstract@^1.23.3: version "1.23.3" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" @@ -1218,6 +1483,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -1247,6 +1517,11 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + find-up@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -1268,6 +1543,11 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== +follow-redirects@^1.0.0: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -1275,6 +1555,15 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +form-data@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" + integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" @@ -1403,6 +1692,73 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: dependencies: function-bind "^1.1.2" +hoist-non-react-statics@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +html-encoding-sniffer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz#696df529a7cfd82446369dc5193e590a3735b448" + integrity sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ== + dependencies: + whatwg-encoding "^3.1.1" + +http-proxy-agent@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + +http-proxy-middleware@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz#dc1313c75bd00d81e103823802551ee30130ebd1" + integrity sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg== + dependencies: + "@types/http-proxy" "^1.17.15" + debug "^4.3.6" + http-proxy "^1.18.1" + is-glob "^4.0.3" + is-plain-object "^5.0.0" + micromatch "^4.0.8" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +https-proxy-agent@^7.0.5: + version "7.0.6" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" + integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== + dependencies: + agent-base "^7.1.2" + debug "4" + +i18n-js@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/i18n-js/-/i18n-js-4.5.1.tgz#12ea3d6333552ff75be0904ea50705f5a263d172" + integrity sha512-n7jojFj1WC0tztgr0I8jqTXuIlY1xNzXnC3mjKX/YjJhimdM+jXM8vOmn9d3xQFNC6qDHJ4ovhdrGXrRXLIGkA== + dependencies: + bignumber.js "*" + lodash "*" + make-plural "*" + +iconv-lite@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ignore@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" @@ -1443,6 +1799,11 @@ is-array-buffer@^3.0.4: call-bind "^1.0.2" get-intrinsic "^1.2.1" +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + is-async-function@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" @@ -1477,6 +1838,13 @@ is-core-module@^2.13.0: dependencies: hasown "^2.0.2" +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + is-data-view@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" @@ -1539,6 +1907,16 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -1610,6 +1988,14 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +isomorphic-dompurify@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/isomorphic-dompurify/-/isomorphic-dompurify-2.19.0.tgz#e55493bf8ae4d57bcde7336ee61ef0063227eefe" + integrity sha512-ppcgeRlEwOQ+v/JDctcjnOsBwEoJlAWVDH5+LisLHphQFeWCrBiVvK6XF4wF0MJM5tJA6RxJSlpbmthnmonxOQ== + dependencies: + dompurify "^3.2.3" + jsdom "^25.0.1" + iterator.prototype@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.3.tgz#016c2abe0be3bbdb8319852884f60908ac62bf9c" @@ -1633,6 +2019,33 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsdom@^25.0.1: + version "25.0.1" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-25.0.1.tgz#536ec685c288fc8a5773a65f82d8b44badcc73ef" + integrity sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw== + dependencies: + cssstyle "^4.1.0" + data-urls "^5.0.0" + decimal.js "^10.4.3" + form-data "^4.0.0" + html-encoding-sniffer "^4.0.0" + http-proxy-agent "^7.0.2" + https-proxy-agent "^7.0.5" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.12" + parse5 "^7.1.2" + rrweb-cssom "^0.7.1" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^5.0.0" + w3c-xmlserializer "^5.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^3.1.1" + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" + ws "^8.18.0" + xml-name-validator "^5.0.0" + jsesc@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" @@ -1643,6 +2056,11 @@ json-buffer@3.0.1: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -1683,6 +2101,11 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -1695,6 +2118,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash@*: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -1709,7 +2137,17 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -micromatch@^4.0.5: +make-plural@*: + version "7.4.0" + resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-7.4.0.tgz#fa6990dd550dea4de6b20163f74e5ed83d8a8d6d" + integrity sha512-4/gC9KVNTV6pvYg2gFeQYTW3mWaoJt7WZE5vrp1KnQDgW92JtYZnzmZT81oj/dUTqAIu0ufI2x3dkgu3bB1tYg== + +memoize-one@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" + integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== + +micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -1717,6 +2155,18 @@ micromatch@^4.0.5: braces "^3.0.3" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -1749,6 +2199,11 @@ node-releases@^2.0.18: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== +nwsapi@^2.2.12: + version "2.2.16" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.16.tgz#177760bba02c351df1d2644e220c31dfec8cdb43" + integrity sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ== + object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -1835,6 +2290,23 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse5@^7.1.2: + version "7.2.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.2.1.tgz#8928f55915e6125f430cc44309765bf17556a33a" + integrity sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ== + dependencies: + entities "^4.5.0" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -1850,6 +2322,11 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + picocolors@^1.0.0, picocolors@^1.1.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" @@ -1879,7 +2356,7 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prop-types@^15.8.1: +prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -1888,7 +2365,7 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -1900,7 +2377,7 @@ react-dom@^19.0.0: dependencies: scheduler "^0.25.0" -react-is@^16.13.1: +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -1910,6 +2387,56 @@ react-refresh@^0.14.2: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== +react-router-dom@7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.1.0.tgz#5e48f6499a1b788edd5aacb4009d929d5f3dd4e1" + integrity sha512-F4/nYBC9e4s0/ZjxM8GkZ9a68DpX76LN1a9W9mfPl2GfbDJ9/vzJro6MThNR5qGBH6KkgcK1BziyEzXhHV46Xw== + dependencies: + react-router "7.1.0" + +react-router@7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.1.0.tgz#85911e27aae8c4a57482f2404aef65ac9ca68a10" + integrity sha512-VcFhWqkNIcojDRYaUO8qV0Jib52s9ULpCp3nkBbmrvtoCVFRp6tmk3tJ2w9BZauVctA1YRnJlFYDn9iJRuCpGA== + dependencies: + "@types/cookie" "^0.6.0" + cookie "^1.0.1" + set-cookie-parser "^2.6.0" + turbo-stream "2.4.0" + +react-select@^5.9.0: + version "5.9.0" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.9.0.tgz#41325b7bfe8452a8d401b82fc4fb7d44a03e29c5" + integrity sha512-nwRKGanVHGjdccsnzhFte/PULziueZxGD8LL2WojON78Mvnq7LdAMEtu2frrwld1fr3geixg3iiMBIc/LLAZpw== + dependencies: + "@babel/runtime" "^7.12.0" + "@emotion/cache" "^11.4.0" + "@emotion/react" "^11.8.1" + "@floating-ui/dom" "^1.0.1" + "@types/react-transition-group" "^4.4.0" + memoize-one "^6.0.0" + prop-types "^15.6.0" + react-transition-group "^4.3.0" + use-isomorphic-layout-effect "^1.2.0" + +react-tooltip@^5.28.0: + version "5.28.0" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.28.0.tgz#c7b5343ab2d740a428494a3d8315515af1f26f46" + integrity sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg== + dependencies: + "@floating-ui/dom" "^1.6.1" + classnames "^2.3.0" + +react-transition-group@^4.3.0: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@^19.0.0: version "19.0.0" resolved "https://registry.yarnpkg.com/react/-/react-19.0.0.tgz#6e1969251b9f108870aa4bff37a0ce9ddfaaabdd" @@ -1933,6 +2460,11 @@ reflect.getprototypeof@^1.0.4: globalthis "^1.0.3" which-builtin-type "^1.1.3" +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + regexp.prototype.flags@^1.5.2: version "1.5.3" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz#b3ae40b1d2499b8350ab2c3fe6ef3845d3a96f42" @@ -1943,11 +2475,25 @@ regexp.prototype.flags@^1.5.2: es-errors "^1.3.0" set-function-name "^2.0.2" +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve@^1.19.0: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^2.0.0-next.5: version "2.0.0-next.5" resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" @@ -1984,6 +2530,11 @@ rollup@^4.23.0: "@rollup/rollup-win32-x64-msvc" "4.28.0" fsevents "~2.3.2" +rrweb-cssom@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz#c73451a484b86dd7cfb1e0b2898df4b703183e4b" + integrity sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg== + safe-array-concat@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" @@ -2003,6 +2554,11 @@ safe-regex-test@^1.0.3: es-errors "^1.3.0" is-regex "^1.1.4" +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + sass@^1.83.0: version "1.83.0" resolved "https://registry.yarnpkg.com/sass/-/sass-1.83.0.tgz#e36842c0b88a94ed336fd16249b878a0541d536f" @@ -2014,6 +2570,13 @@ sass@^1.83.0: optionalDependencies: "@parcel/watcher" "^2.4.1" +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + dependencies: + xmlchars "^2.2.0" + scheduler@^0.25.0: version "0.25.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0.tgz#336cd9768e8cceebf52d3c80e3dcf5de23e7e015" @@ -2024,6 +2587,11 @@ semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +set-cookie-parser@^2.6.0: + version "2.7.1" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz#3016f150072202dfbe90fadee053573cc89d2943" + integrity sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ== + set-function-length@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" @@ -2073,6 +2641,11 @@ side-channel@^1.0.4, side-channel@^1.0.6: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== +source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + string.prototype.matchall@^4.0.11: version "4.0.11" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" @@ -2132,6 +2705,11 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -2144,6 +2722,23 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +tldts-core@^6.1.69: + version "6.1.69" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.69.tgz#079ffcac8a4407bc74567e292aecf30b943674e1" + integrity sha512-nygxy9n2PBUFQUtAXAc122gGo+04/j5qr5TGQFZTHafTKYvmARVXt2cA5rgero2/dnXUfkdPtiJoKmrd3T+wdA== + +tldts@^6.1.32: + version "6.1.69" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.69.tgz#0fe1fcb1ad09510459693e72f96062cee2411f1f" + integrity sha512-Oh/CqRQ1NXNY7cy9NkTPUauOWiTro0jEYZTioGbOmcQh6EC45oribyIMJp0OJO3677r13tO6SKdWoGZUx2BDFw== + dependencies: + tldts-core "^6.1.69" + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -2151,6 +2746,25 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tough-cookie@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.0.0.tgz#6b6518e2b5c070cf742d872ee0f4f92d69eac1af" + integrity sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q== + dependencies: + tldts "^6.1.32" + +tr46@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec" + integrity sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g== + dependencies: + punycode "^2.3.1" + +turbo-stream@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/turbo-stream/-/turbo-stream-2.4.0.tgz#1e4fca6725e90fa14ac4adb782f2d3759a5695f0" + integrity sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -2212,6 +2826,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + update-browserslist-db@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" @@ -2227,6 +2846,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +use-isomorphic-layout-effect@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz#afb292eb284c39219e8cb8d3d62d71999361a21d" + integrity sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w== + vite@^6.0.4: version "6.0.4" resolved "https://registry.yarnpkg.com/vite/-/vite-6.0.4.tgz#fe7cfaedff7c701d5582be5c4ed6a2150538ea9d" @@ -2238,6 +2862,38 @@ vite@^6.0.4: optionalDependencies: fsevents "~2.3.3" +w3c-xmlserializer@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c" + integrity sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA== + dependencies: + xml-name-validator "^5.0.0" + +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + +whatwg-encoding@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" + integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== + dependencies: + iconv-lite "0.6.3" + +whatwg-mimetype@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" + integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== + +whatwg-url@^14.0.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.1.0.tgz#fffebec86cc8e6c2a657e50dc606207b870f0ab3" + integrity sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w== + dependencies: + tr46 "^5.0.0" + webidl-conversions "^7.0.0" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -2300,12 +2956,37 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== +ws@^8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + +xml-name-validator@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673" + integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zustand@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.2.tgz#f7595ada55a565f1fd6464f002a91e701ee0cfca" + integrity sha512-8qNdnJVJlHlrKXi50LDqqUNmUbuBjoKLrYQBnoChIbVph7vni+sY+YpvdjXG9YLd/Bxr6scMcR+rm5H3aSqPaw==