Procházet zdrojové kódy

Add disclaimer text box under the login form

This text box will only show up if a valid path to a disclaimer file is
passed to the `DISCLAIMER_PATH` environment variable.
Daniel Vincze před 1 rokem
rodič
revize
102f6ea947

+ 41 - 0
server/proxies/disclaimerProxy.ts

@@ -0,0 +1,41 @@
+/*
+Copyright (C) 2025  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import express from "express";
+import fs from "fs";
+
+const buildError = (message: any) => ({
+  error: { message },
+});
+
+export default (router: express.Router) => {
+  router.get("/disclaimer", async (_, res) => {
+    const path = process.env.DISCLAIMER_PATH;
+    if (!path) {
+      res.json(null);
+      return;
+    }
+    if (!fs.existsSync(path)) {
+      res
+        .status(500)
+        .json(buildError("Disclaimer path not configured properly"));
+      return;
+    }
+    try {
+      res.json(fs.readFileSync(path, "utf-8"));
+    } catch (err) {
+      res.status(500).json(buildError(err.message));
+    }
+  });
+};

+ 2 - 0
server/proxies/router.ts

@@ -16,6 +16,7 @@ import express from "express";
 import bodyParser from "body-parser";
 import metalHubProxy from "./metalHubProxy";
 import azureProxy from "./azureProxy";
+import disclaimerProxy from "./disclaimerProxy";
 
 const router = express.Router();
 
@@ -23,5 +24,6 @@ router.use(bodyParser.json());
 
 azureProxy(router);
 metalHubProxy(router);
+disclaimerProxy(router);
 
 export default router;

+ 29 - 0
src/components/smart/LoginPage/LoginPage.tsx

@@ -22,9 +22,11 @@ import LoginForm from "@src/components/modules/LoginModule/LoginForm";
 
 import userStore from "@src/stores/UserStore";
 import configStore from "@src/utils/Config";
+import disclaimerStore from "@src/stores/DisclaimerStore";
 
 import backgroundImage from "@src/components/ui/Images/star-bg.jpg";
 import cbsImage from "./images/cbsl-logo.svg";
+import { ThemeProps } from "@src/components/Theme";
 
 const Wrapper = styled.div<any>`
   background-image: url("${backgroundImage}");
@@ -80,6 +82,23 @@ const CbsLogo = styled.a`
   cursor: pointer;
 `;
 
+const Disclaimer = styled.div<any>`
+  background: rgba(27, 39, 51, 0.7);
+  border-radius: ${ThemeProps.borderRadius};
+  position: relative;
+  padding: 8px;
+  margin-top: 16px;
+  overflow-wrap: break-word;
+  overflow-y: auto;
+  overflow-x: hidden;
+  white-space: pre-wrap;
+  color: white;
+  width: fit-content;
+  height: fit-content;
+  max-width: 50%;
+  max-height: 25%;
+`;
+
 type Props = {
   history: any;
 };
@@ -102,6 +121,15 @@ class LoginPage extends React.Component<Props, State> {
 
   componentDidMount() {
     document.title = "Log In";
+
+    disclaimerStore.loadDisclaimer();
+  }
+
+  renderDisclaimer() {
+    if (!disclaimerStore.disclaimer) {
+      return null;
+    }
+    return <Disclaimer>{disclaimerStore.disclaimer}</Disclaimer>;
   }
 
   async handleFormSubmit(data: { username: string; password: string }) {
@@ -139,6 +167,7 @@ class LoginPage extends React.Component<Props, State> {
                 loginFailedResponse={userStore.loginFailedResponse}
                 showUserDomainInput={configStore.config.showUserDomainInput}
               />
+              {this.renderDisclaimer()}
             </Top>
             <Footer>
               <FooterText>Coriolis® is a service created by</FooterText>

+ 28 - 0
src/sources/DisclaimerSource.ts

@@ -0,0 +1,28 @@
+/*
+Copyright (C) 2025  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import apiCaller from "@src/utils/ApiCaller";
+
+class DisclaimerSource {
+  async loadDisclaimer(): Promise<string> {
+    return (
+      await apiCaller.send({
+        url: "/proxy/disclaimer",
+        quietError: true,
+      })
+    ).data;
+  }
+}
+
+export default new DisclaimerSource();

+ 47 - 0
src/stores/DisclaimerStore.ts

@@ -0,0 +1,47 @@
+/*
+Copyright (C) 2025  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import { observable, action, runInAction } from "mobx";
+
+import source from "@src/sources/DisclaimerSource";
+
+class DisclaimerStore {
+  @observable disclaimer = "";
+
+  @observable loadingDisclaimer = false;
+
+  @observable loadingDisclaimerError = "";
+
+  @action async loadDisclaimer() {
+    this.loadingDisclaimer = true;
+    try {
+      const disclaimer = await source.loadDisclaimer();
+      runInAction(() => {
+        this.disclaimer = disclaimer;
+        this.loadingDisclaimerError = "";
+      });
+    } catch (err) {
+      runInAction(() => {
+        this.loadingDisclaimerError =
+          err.data?.error?.message || err.message || "";
+      });
+    } finally {
+      runInAction(() => {
+        this.loadingDisclaimer = false;
+      });
+    }
+  }
+}
+
+export default new DisclaimerStore();