From 3b3804fa6a0ef59f59ff66475dc88b604f0558ce Mon Sep 17 00:00:00 2001 From: Annaseli Date: Wed, 13 Aug 2025 22:50:20 +0300 Subject: [PATCH 1/7] added plugin support for login method selection feature --- .../DefaultLoginMethodSelectionPlugin.tsx | 11 ++ .../plugins/pluginLoginMethodSelection.ts | 17 ++ webui/src/extendable/plugins/pluginManager.ts | 13 +- webui/src/lib/components/navbar.jsx | 50 ++--- webui/src/pages/auth/login.tsx | 182 +++++++++++------- webui/src/styles/auth.css | 23 ++- 6 files changed, 192 insertions(+), 104 deletions(-) create mode 100644 webui/src/extendable/plugins/impls/DefaultLoginMethodSelectionPlugin.tsx create mode 100644 webui/src/extendable/plugins/pluginLoginMethodSelection.ts diff --git a/webui/src/extendable/plugins/impls/DefaultLoginMethodSelectionPlugin.tsx b/webui/src/extendable/plugins/impls/DefaultLoginMethodSelectionPlugin.tsx new file mode 100644 index 00000000000..4b13fcb4b6e --- /dev/null +++ b/webui/src/extendable/plugins/impls/DefaultLoginMethodSelectionPlugin.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import { PluginLoginMethodSelection, LoginConfig } from "../pluginLoginMethodSelection"; + +class DefaultLoginMethodSelectionPlugin implements PluginLoginMethodSelection { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + renderMethodSelection(_loginConfig: LoginConfig | undefined): React.ReactElement | null { + return null; + } +} + +export default new DefaultLoginMethodSelectionPlugin(); \ No newline at end of file diff --git a/webui/src/extendable/plugins/pluginLoginMethodSelection.ts b/webui/src/extendable/plugins/pluginLoginMethodSelection.ts new file mode 100644 index 00000000000..4c66c0c416d --- /dev/null +++ b/webui/src/extendable/plugins/pluginLoginMethodSelection.ts @@ -0,0 +1,17 @@ +import React from "react"; + +export interface LoginConfig { + login_url: string; + username_ui_placeholder: string; + password_ui_placeholder: string; + login_failed_message?: string; + fallback_login_url?: string; + fallback_login_label?: string; + login_cookie_names: string[]; + logout_url: string; + select_login_method?: boolean; +} + +export interface PluginLoginMethodSelection { + renderMethodSelection: (loginConfig: LoginConfig | undefined) => React.ReactElement | null; +} \ No newline at end of file diff --git a/webui/src/extendable/plugins/pluginManager.ts b/webui/src/extendable/plugins/pluginManager.ts index 37d084b250e..2550a87226c 100644 --- a/webui/src/extendable/plugins/pluginManager.ts +++ b/webui/src/extendable/plugins/pluginManager.ts @@ -2,10 +2,13 @@ import { PluginRepoCreationForm } from "./pluginRepoCreationForm"; import DefaultRepoCreationFormPlugin from "./impls/DefaultRepoCreationFormPlugin"; import { PluginCustomObjectRenderers } from "./pluginCustomObjectRenderers"; import DefaultCustomObjectRenderersPlugin from "./impls/DefaultCustomObjectRenderers"; +import { PluginLoginMethodSelection } from "./pluginLoginMethodSelection"; +import DefaultLoginMethodSelectionPlugin from "./impls/DefaultLoginMethodSelectionPlugin"; export class PluginManager { private _repoCreationForm: PluginRepoCreationForm = DefaultRepoCreationFormPlugin; private _customObjectRenderers: PluginCustomObjectRenderers = DefaultCustomObjectRenderersPlugin; + private _loginMethodSelection: PluginLoginMethodSelection = DefaultLoginMethodSelectionPlugin; overridePluginRepoCreationForm(pluginRepoCreationForm: PluginRepoCreationForm): void { this._repoCreationForm = pluginRepoCreationForm; @@ -22,4 +25,12 @@ export class PluginManager { get customObjectRenderers(): PluginCustomObjectRenderers { return this._customObjectRenderers; } -} + + overridePluginLoginMethodSelection(pluginLoginMethodSelection: PluginLoginMethodSelection): void { + this._loginMethodSelection = pluginLoginMethodSelection; + } + + get loginMethodSelection(): PluginLoginMethodSelection { + return this._loginMethodSelection; + } +} \ No newline at end of file diff --git a/webui/src/lib/components/navbar.jsx b/webui/src/lib/components/navbar.jsx index 7421facbae5..e67643742df 100644 --- a/webui/src/lib/components/navbar.jsx +++ b/webui/src/lib/components/navbar.jsx @@ -64,39 +64,31 @@ const TopNavLink = ({ href, children }) => { }; const TopNav = ({logged = true}) => { - if (!logged) { + if (logged) { return ( - - - - lakeFS - - + + + + lakeFS + + + + + + + + + + ); } - return ( - - - - lakeFS - - - - - - - - - - - - ); + return null; }; export default TopNav; diff --git a/webui/src/pages/auth/login.tsx b/webui/src/pages/auth/login.tsx index 4b33b97a150..6fdf4dcb70a 100644 --- a/webui/src/pages/auth/login.tsx +++ b/webui/src/pages/auth/login.tsx @@ -1,14 +1,19 @@ import React, {useState} from "react"; -import Row from "react-bootstrap/Row"; import Card from "react-bootstrap/Card"; import Form from "react-bootstrap/Form"; -import Col from "react-bootstrap/Col"; import Button from "react-bootstrap/Button"; import {auth, AuthenticationError, setup, SETUP_STATE_INITIALIZED} from "../../lib/api"; import {AlertError} from "../../lib/components/controls" import {useRouter} from "../../lib/hooks/router"; import {useAPI} from "../../lib/hooks/api"; import {useNavigate} from "react-router-dom"; +import {usePluginManager} from "../../extendable/plugins/pluginsContext"; + +interface SetupResponse { + state: string; + comm_prefs_missing?: boolean; + login_config?: LoginConfig; +} interface LoginConfig { login_url: string; @@ -19,111 +24,142 @@ interface LoginConfig { fallback_login_label?: string; login_cookie_names: string[]; logout_url: string; + select_login_method?: boolean; } const LoginForm = ({loginConfig}: {loginConfig: LoginConfig}) => { const router = useRouter(); const navigate = useNavigate(); - const [loginError, setLoginError] = useState(null); + const [loginError, setLoginError] = useState(null); const { next } = router.query; const usernamePlaceholder = loginConfig.username_ui_placeholder || "Access Key ID"; const passwordPlaceholder = loginConfig.password_ui_placeholder || "Secret Access Key"; + return ( - - - - -

Login

-
- -
{ - e.preventDefault() - try { - setLoginError(null); - await auth.login(e.target.username.value, e.target.password.value) - router.push(next || '/'); - navigate(0); - } catch(err) { - if (err instanceof AuthenticationError && err.status === 401) { - const contents = {__html: `${loginConfig.login_failed_message}` || - "Credentials don't match."}; - setLoginError(); - } +
+ + +
+ lakeFS +
+

Login

+
+ + { + e.preventDefault() + const form = e.target as HTMLFormElement; + const formData = new FormData(form); + try { + setLoginError(null); + const username = formData.get('username'); + const password = formData.get('password'); + if (typeof username === 'string' && typeof password === 'string') { + await auth.login(username, password); } - }}> - - - - - - - - - {(!!loginError) && } - - - -
- { loginConfig.fallback_login_url ? - - : "" + router.push(next || '/'); + navigate(0); + } catch(err) { + if (err instanceof AuthenticationError && err.status === 401) { + const contents = {__html: `${loginConfig.login_failed_message}` || + "Credentials don't match."}; + setLoginError(); } -
-
-
- - + } + }}> + + + + + + + + + {(!!loginError) && } + + + +
+ { loginConfig.fallback_login_url ? + + : "" + } +
+ + +
) } + const LoginPage = () => { const router = useRouter(); const { response, error, loading } = useAPI(() => setup.getState()); + const pluginManager = usePluginManager(); + if (loading) { return null; } // if we are not initialized, or we are not done with comm prefs, redirect to 'setup' page - if (!error && response && (response.state !== SETUP_STATE_INITIALIZED || response.comm_prefs_missing === true)) { - router.push({pathname: '/setup', query: router.query}) + if (!error && response && ((response as SetupResponse).state !== SETUP_STATE_INITIALIZED || (response as SetupResponse).comm_prefs_missing)) { + router.push({pathname: '/setup', params: {}, query: router.query as Record}) return null; } - const loginConfig = response?.login_config; + const setupResponse = response as SetupResponse | null; + const loginConfig = setupResponse?.login_config; + + const loginMethodSelectionComponent = loginConfig ? pluginManager.loginMethodSelection.renderMethodSelection(loginConfig) : null; + if (loginMethodSelectionComponent) { + if (router.query.method === 'local' && loginConfig) { + return ; + } + + return loginMethodSelectionComponent; + } + if (router.query.redirected) { if(!error && loginConfig?.login_url) { - window.location = loginConfig.login_url; + window.location.href = loginConfig.login_url; return null; } delete router.query.redirected; - router.push({pathname: '/auth/login', query: router.query}) + router.push({pathname: '/auth/login', params: {}, query: router.query as Record}) } + if (!loginConfig) { + return null; + } + return ( ); }; -export default LoginPage; +export default LoginPage; \ No newline at end of file diff --git a/webui/src/styles/auth.css b/webui/src/styles/auth.css index 6b8b904fbef..b91161eea91 100644 --- a/webui/src/styles/auth.css +++ b/webui/src/styles/auth.css @@ -91,4 +91,25 @@ body .auth-page .nav-pills .nav-link:hover { /* Direct style override for nav-pills */ .auth-page .nav-pills .nav-link.active { background-color: var(--success) !important; -} \ No newline at end of file +} + +.login-container { + height: 100vh; + overflow: hidden; + position: fixed; + top: 0; + left: 0; + width: 100%; + background-color: #f8f9fa; +} + +.login-card { + max-width: 600px; + width: 90%; +} + +.login-logo { + width: 170px; + height: 60px; + filter: brightness(0) saturate(100%); +} \ No newline at end of file From 30e9f98786083790ab6b487d65191ee8e9892dcd Mon Sep 17 00:00:00 2001 From: Annaseli Date: Thu, 14 Aug 2025 15:46:59 +0300 Subject: [PATCH 2/7] added in auth.css color changes for a button --- webui/src/styles/auth.css | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/webui/src/styles/auth.css b/webui/src/styles/auth.css index b91161eea91..b23c52cde07 100644 --- a/webui/src/styles/auth.css +++ b/webui/src/styles/auth.css @@ -112,4 +112,20 @@ body .auth-page .nav-pills .nav-link:hover { width: 170px; height: 60px; filter: brightness(0) saturate(100%); +} + +.btn.login-method-secondary { + background-color: #b0b6bf !important; + border-color: #b0b6bf !important; + color: white !important; +} + +.btn.login-method-secondary:hover { + background-color: #979a9f !important; + border-color: #979a9f !important; + color: white !important; +} + +.login-method-secondary .key-icon { + filter: grayscale(100%); } \ No newline at end of file From 538cea78d36f3d40e8d3fe08b6e14912791ff87e Mon Sep 17 00:00:00 2001 From: Annaseli Date: Thu, 14 Aug 2025 19:07:58 +0300 Subject: [PATCH 3/7] Removed code related to the plugin system imp for the login method selection page --- .../DefaultLoginMethodSelectionPlugin.tsx | 11 ------ .../plugins/pluginLoginMethodSelection.ts | 17 --------- webui/src/extendable/plugins/pluginManager.ts | 11 ------ webui/src/pages/auth/login.tsx | 37 +++---------------- webui/src/styles/auth.css | 16 -------- 5 files changed, 5 insertions(+), 87 deletions(-) delete mode 100644 webui/src/extendable/plugins/impls/DefaultLoginMethodSelectionPlugin.tsx delete mode 100644 webui/src/extendable/plugins/pluginLoginMethodSelection.ts diff --git a/webui/src/extendable/plugins/impls/DefaultLoginMethodSelectionPlugin.tsx b/webui/src/extendable/plugins/impls/DefaultLoginMethodSelectionPlugin.tsx deleted file mode 100644 index 4b13fcb4b6e..00000000000 --- a/webui/src/extendable/plugins/impls/DefaultLoginMethodSelectionPlugin.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; -import { PluginLoginMethodSelection, LoginConfig } from "../pluginLoginMethodSelection"; - -class DefaultLoginMethodSelectionPlugin implements PluginLoginMethodSelection { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - renderMethodSelection(_loginConfig: LoginConfig | undefined): React.ReactElement | null { - return null; - } -} - -export default new DefaultLoginMethodSelectionPlugin(); \ No newline at end of file diff --git a/webui/src/extendable/plugins/pluginLoginMethodSelection.ts b/webui/src/extendable/plugins/pluginLoginMethodSelection.ts deleted file mode 100644 index 4c66c0c416d..00000000000 --- a/webui/src/extendable/plugins/pluginLoginMethodSelection.ts +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react"; - -export interface LoginConfig { - login_url: string; - username_ui_placeholder: string; - password_ui_placeholder: string; - login_failed_message?: string; - fallback_login_url?: string; - fallback_login_label?: string; - login_cookie_names: string[]; - logout_url: string; - select_login_method?: boolean; -} - -export interface PluginLoginMethodSelection { - renderMethodSelection: (loginConfig: LoginConfig | undefined) => React.ReactElement | null; -} \ No newline at end of file diff --git a/webui/src/extendable/plugins/pluginManager.ts b/webui/src/extendable/plugins/pluginManager.ts index 2550a87226c..dd45cf28018 100644 --- a/webui/src/extendable/plugins/pluginManager.ts +++ b/webui/src/extendable/plugins/pluginManager.ts @@ -2,13 +2,10 @@ import { PluginRepoCreationForm } from "./pluginRepoCreationForm"; import DefaultRepoCreationFormPlugin from "./impls/DefaultRepoCreationFormPlugin"; import { PluginCustomObjectRenderers } from "./pluginCustomObjectRenderers"; import DefaultCustomObjectRenderersPlugin from "./impls/DefaultCustomObjectRenderers"; -import { PluginLoginMethodSelection } from "./pluginLoginMethodSelection"; -import DefaultLoginMethodSelectionPlugin from "./impls/DefaultLoginMethodSelectionPlugin"; export class PluginManager { private _repoCreationForm: PluginRepoCreationForm = DefaultRepoCreationFormPlugin; private _customObjectRenderers: PluginCustomObjectRenderers = DefaultCustomObjectRenderersPlugin; - private _loginMethodSelection: PluginLoginMethodSelection = DefaultLoginMethodSelectionPlugin; overridePluginRepoCreationForm(pluginRepoCreationForm: PluginRepoCreationForm): void { this._repoCreationForm = pluginRepoCreationForm; @@ -25,12 +22,4 @@ export class PluginManager { get customObjectRenderers(): PluginCustomObjectRenderers { return this._customObjectRenderers; } - - overridePluginLoginMethodSelection(pluginLoginMethodSelection: PluginLoginMethodSelection): void { - this._loginMethodSelection = pluginLoginMethodSelection; - } - - get loginMethodSelection(): PluginLoginMethodSelection { - return this._loginMethodSelection; - } } \ No newline at end of file diff --git a/webui/src/pages/auth/login.tsx b/webui/src/pages/auth/login.tsx index 6fdf4dcb70a..7d441451f02 100644 --- a/webui/src/pages/auth/login.tsx +++ b/webui/src/pages/auth/login.tsx @@ -7,13 +7,6 @@ import {AlertError} from "../../lib/components/controls" import {useRouter} from "../../lib/hooks/router"; import {useAPI} from "../../lib/hooks/api"; import {useNavigate} from "react-router-dom"; -import {usePluginManager} from "../../extendable/plugins/pluginsContext"; - -interface SetupResponse { - state: string; - comm_prefs_missing?: boolean; - login_config?: LoginConfig; -} interface LoginConfig { login_url: string; @@ -24,7 +17,6 @@ interface LoginConfig { fallback_login_label?: string; login_cookie_names: string[]; logout_url: string; - select_login_method?: boolean; } const LoginForm = ({loginConfig}: {loginConfig: LoginConfig}) => { @@ -116,47 +108,28 @@ const LoginForm = ({loginConfig}: {loginConfig: LoginConfig}) => { ) } - - const LoginPage = () => { const router = useRouter(); const { response, error, loading } = useAPI(() => setup.getState()); - const pluginManager = usePluginManager(); - if (loading) { return null; } // if we are not initialized, or we are not done with comm prefs, redirect to 'setup' page - if (!error && response && ((response as SetupResponse).state !== SETUP_STATE_INITIALIZED || (response as SetupResponse).comm_prefs_missing)) { - router.push({pathname: '/setup', params: {}, query: router.query as Record}) + if (!error && response && (response.state !== SETUP_STATE_INITIALIZED || response.comm_prefs_missing === true)) { + router.push({pathname: '/setup', query: router.query}) return null; } - const setupResponse = response as SetupResponse | null; - const loginConfig = setupResponse?.login_config; - - const loginMethodSelectionComponent = loginConfig ? pluginManager.loginMethodSelection.renderMethodSelection(loginConfig) : null; - if (loginMethodSelectionComponent) { - if (router.query.method === 'local' && loginConfig) { - return ; - } - - return loginMethodSelectionComponent; - } - + const loginConfig = response?.login_config; if (router.query.redirected) { if(!error && loginConfig?.login_url) { - window.location.href = loginConfig.login_url; + window.location = loginConfig.login_url; return null; } delete router.query.redirected; - router.push({pathname: '/auth/login', params: {}, query: router.query as Record}) + router.push({pathname: '/auth/login', query: router.query}) } - if (!loginConfig) { - return null; - } - return ( ); diff --git a/webui/src/styles/auth.css b/webui/src/styles/auth.css index b23c52cde07..b91161eea91 100644 --- a/webui/src/styles/auth.css +++ b/webui/src/styles/auth.css @@ -112,20 +112,4 @@ body .auth-page .nav-pills .nav-link:hover { width: 170px; height: 60px; filter: brightness(0) saturate(100%); -} - -.btn.login-method-secondary { - background-color: #b0b6bf !important; - border-color: #b0b6bf !important; - color: white !important; -} - -.btn.login-method-secondary:hover { - background-color: #979a9f !important; - border-color: #979a9f !important; - color: white !important; -} - -.login-method-secondary .key-icon { - filter: grayscale(100%); } \ No newline at end of file From fff98792cbffa8af99b1369882ebe81d9b50d46c Mon Sep 17 00:00:00 2001 From: Annaseli Date: Wed, 10 Sep 2025 20:25:16 +0300 Subject: [PATCH 4/7] Addressed the PR comments --- webui/src/pages/auth/login.tsx | 5 ++--- webui/src/styles/auth.css | 15 +++------------ 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/webui/src/pages/auth/login.tsx b/webui/src/pages/auth/login.tsx index 7d441451f02..d261e576036 100644 --- a/webui/src/pages/auth/login.tsx +++ b/webui/src/pages/auth/login.tsx @@ -28,13 +28,12 @@ const LoginForm = ({loginConfig}: {loginConfig: LoginConfig}) => { const passwordPlaceholder = loginConfig.password_ui_placeholder || "Secret Access Key"; return ( -
- +
+
lakeFS
-

Login

{ diff --git a/webui/src/styles/auth.css b/webui/src/styles/auth.css index b91161eea91..320ef1e2db4 100644 --- a/webui/src/styles/auth.css +++ b/webui/src/styles/auth.css @@ -93,23 +93,14 @@ body .auth-page .nav-pills .nav-link:hover { background-color: var(--success) !important; } -.login-container { - height: 100vh; - overflow: hidden; - position: fixed; - top: 0; - left: 0; - width: 100%; - background-color: #f8f9fa; -} - .login-card { max-width: 600px; width: 90%; + margin: 50px auto auto auto; } .login-logo { - width: 170px; - height: 60px; + width: 359px; + height: 82px; filter: brightness(0) saturate(100%); } \ No newline at end of file From 4f5a078eab10661b50386a7aeddfbb21c3121ed8 Mon Sep 17 00:00:00 2001 From: Annaseli Date: Mon, 15 Sep 2025 13:54:21 +0300 Subject: [PATCH 5/7] added rerouting for login page from all the remove an if condition before calling to auth.login() --- webui/src/pages/auth/login.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/webui/src/pages/auth/login.tsx b/webui/src/pages/auth/login.tsx index f15e3e153aa..99b4cffc701 100644 --- a/webui/src/pages/auth/login.tsx +++ b/webui/src/pages/auth/login.tsx @@ -52,9 +52,7 @@ const LoginForm = ({loginConfig}: {loginConfig: LoginConfig}) => { setLoginError(null); const username = formData.get('username'); const password = formData.get('password'); - if (typeof username === 'string' && typeof password === 'string') { - await auth.login(username, password); - } + await auth.login(username, password); router.push(next || '/'); navigate(0); } catch(err) { From ff6dc595e443f9996f8f6394eee7106127eaa7a8 Mon Sep 17 00:00:00 2001 From: Annaseli Date: Tue, 16 Sep 2025 11:04:14 +0300 Subject: [PATCH 6/7] made changes to the logo: reduced it's size, use a colored svg file and centered position --- webui/pub/logo.svg | 8 +------- webui/src/pages/auth/login.tsx | 4 ++-- webui/src/styles/auth.css | 5 ++--- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/webui/pub/logo.svg b/webui/pub/logo.svg index 6b60c1042f5..e3884f4b4d2 100644 --- a/webui/pub/logo.svg +++ b/webui/pub/logo.svg @@ -1,7 +1 @@ - - - - - - - +logo 1 \ No newline at end of file diff --git a/webui/src/pages/auth/login.tsx b/webui/src/pages/auth/login.tsx index be1bf5948f1..5be1999342c 100644 --- a/webui/src/pages/auth/login.tsx +++ b/webui/src/pages/auth/login.tsx @@ -39,8 +39,8 @@ const LoginForm = ({loginConfig}: {loginConfig: LoginConfig}) => {
-
- lakeFS +
+ lakeFS
diff --git a/webui/src/styles/auth.css b/webui/src/styles/auth.css index 320ef1e2db4..3d3eb5a7201 100644 --- a/webui/src/styles/auth.css +++ b/webui/src/styles/auth.css @@ -100,7 +100,6 @@ body .auth-page .nav-pills .nav-link:hover { } .login-logo { - width: 359px; - height: 82px; - filter: brightness(0) saturate(100%); + width: 269px; + height: 62px; } \ No newline at end of file From 9e6dd28dac0a9a876c55ed6f10ab8c54ce477897 Mon Sep 17 00:00:00 2001 From: Annaseli Date: Tue, 16 Sep 2025 19:23:55 +0300 Subject: [PATCH 7/7] changed code style --- webui/src/lib/components/navbar.jsx | 43 ++++++++++++++--------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/webui/src/lib/components/navbar.jsx b/webui/src/lib/components/navbar.jsx index e67643742df..21a1e337633 100644 --- a/webui/src/lib/components/navbar.jsx +++ b/webui/src/lib/components/navbar.jsx @@ -64,31 +64,28 @@ const TopNavLink = ({ href, children }) => { }; const TopNav = ({logged = true}) => { - if (logged) { - return ( - - - - lakeFS - - - + return logged && ( + + + + lakeFS + + + - + - - - - - - ); - } - return null; + + + + + + ); }; export default TopNav;