From e31d6d5e4e2982f10853d99b0c67f177c987f565 Mon Sep 17 00:00:00 2001 From: Richard Boisvert Date: Wed, 29 Apr 2026 02:51:23 +0000 Subject: [PATCH] feat(ui): implement dark theme and refactor color usage Add a system-aware dark theme with a header toggle (light, dark, system) that persists in localStorage. All colors now flow through `src/theme/tokens.ts` into both the antd component library and styled-components, replacing hardcoded hex across `styled.ts` files and inline styles. Light mode is preserved the same as before. https://github.com/apache/incubator-devlake/issues/8863 --- backend/server/api/auth/auth_test.go | 2 +- config-ui/src/app/store.ts | 3 +- config-ui/src/components/block/styled.ts | 2 +- config-ui/src/components/inspector/styled.ts | 6 +- config-ui/src/components/loading/styled.ts | 2 +- config-ui/src/components/message/index.tsx | 2 +- .../src/components/page-header/styled.ts | 2 +- config-ui/src/components/tip-layout/styled.ts | 4 +- config-ui/src/features/index.ts | 1 + config-ui/src/features/theme/index.ts | 19 ++ config-ui/src/features/theme/slice.ts | 87 ++++++++ config-ui/src/index.css | 12 +- config-ui/src/main.tsx | 16 +- .../connection-form/fields/styled.ts | 2 +- .../data-scope-remote/search-local.tsx | 2 +- .../components/data-scope-remote/styled.ts | 2 +- .../register/argocd/transformation.tsx | 2 +- .../azure/connection-fields/styled.ts | 2 +- .../plugins/register/azure/transformation.tsx | 2 +- .../register/circleci/transformation.tsx | 2 +- .../gh-copilot/connection-fields/styled.ts | 2 +- .../github/connection-fields/styled.ts | 10 +- .../github/connection-fields/token.tsx | 6 +- .../register/github/transformation.tsx | 6 +- .../register/gitlab/transformation.tsx | 2 +- .../register/jenkins/transformation.tsx | 2 +- .../register/jira/connection-fields/styled.ts | 2 +- .../jira/transformation-fields/styled.ts | 6 +- .../opsgenie/connection-fields/styled.ts | 10 +- .../connection-fields/aws-credentials.tsx | 15 +- .../connection-fields/connection-test.tsx | 2 +- .../identity-center-config.tsx | 4 +- .../q-dev/connection-fields/s3-config.tsx | 2 +- .../src/plugins/register/webhook/styled.ts | 6 +- .../detail/components/sync-policy/styled.ts | 2 +- .../src/routes/blueprint/detail/styled.ts | 2 +- config-ui/src/routes/blueprint/home/index.tsx | 6 +- config-ui/src/routes/blueprint/home/styled.ts | 2 +- .../src/routes/connection/connections.tsx | 2 +- config-ui/src/routes/connection/styled.ts | 16 +- config-ui/src/routes/db-migrate/index.tsx | 2 +- config-ui/src/routes/error/index.tsx | 4 +- config-ui/src/routes/layout/layout.tsx | 28 ++- config-ui/src/routes/not-found/index.tsx | 2 +- config-ui/src/routes/onboard/index.tsx | 2 +- config-ui/src/routes/onboard/step-0.tsx | 2 +- config-ui/src/routes/onboard/styled.ts | 22 +- config-ui/src/routes/pipeline/styled.ts | 32 +-- config-ui/src/routes/project/home/index.tsx | 2 +- config-ui/src/theme/ThemeProvider.tsx | 83 ++++++++ config-ui/src/theme/index.ts | 20 ++ config-ui/src/theme/styled.d.ts | 27 +++ config-ui/src/theme/tokens.ts | 188 ++++++++++++++++++ 53 files changed, 577 insertions(+), 114 deletions(-) create mode 100644 config-ui/src/features/theme/index.ts create mode 100644 config-ui/src/features/theme/slice.ts create mode 100644 config-ui/src/theme/ThemeProvider.tsx create mode 100644 config-ui/src/theme/index.ts create mode 100644 config-ui/src/theme/styled.d.ts create mode 100644 config-ui/src/theme/tokens.ts diff --git a/backend/server/api/auth/auth_test.go b/backend/server/api/auth/auth_test.go index 75d09ab47d0..7235b91442b 100644 --- a/backend/server/api/auth/auth_test.go +++ b/backend/server/api/auth/auth_test.go @@ -39,7 +39,7 @@ func TestSafeReturnURL(t *testing.T) { {"missing leading slash", "projects", "/"}, {"javascript scheme", "javascript:alert(1)", "/"}, {"data scheme", "data:text/html,x", "/"}, - {"unparseable", "://", "/"}, + {"unparsable", "://", "/"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { diff --git a/config-ui/src/app/store.ts b/config-ui/src/app/store.ts index 8d2ce0c3429..6c3cfa3a55d 100644 --- a/config-ui/src/app/store.ts +++ b/config-ui/src/app/store.ts @@ -18,11 +18,12 @@ import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit'; -import { connectionsSlice } from '@/features'; +import { connectionsSlice, themeSlice } from '@/features'; export const store = configureStore({ reducer: { connections: connectionsSlice.reducer, + theme: themeSlice.reducer, }, }); diff --git a/config-ui/src/components/block/styled.ts b/config-ui/src/components/block/styled.ts index 291e5b3e977..b66fa2ca8a9 100644 --- a/config-ui/src/components/block/styled.ts +++ b/config-ui/src/components/block/styled.ts @@ -24,7 +24,7 @@ export const Label = styled.label` `; export const LabelInfo = styled.i` - color: #ff8b8b; + color: ${({ theme }) => theme.colors.secondary}; `; export const subLabel = styled.p` diff --git a/config-ui/src/components/inspector/styled.ts b/config-ui/src/components/inspector/styled.ts index c4bc25cb003..5526875a90c 100644 --- a/config-ui/src/components/inspector/styled.ts +++ b/config-ui/src/components/inspector/styled.ts @@ -20,7 +20,7 @@ import styled from 'styled-components'; export const Wrapper = styled.div` padding: 16px 24px; - background-color: #f3f3f3; + background-color: ${({ theme }) => theme.colors.bgElevated}; .title { display: flex; @@ -34,7 +34,7 @@ export const Wrapper = styled.div` span { font-size: 10px; - color: #aaaaaa; + color: ${({ theme }) => theme.colors.textFaint}; } } @@ -46,7 +46,7 @@ export const Wrapper = styled.div` .content { padding: 10px; max-height: 600; - background-color: #ffff; + background-color: ${({ theme }) => theme.colors.bgContainer}; border-radius: 4px; box-shadow: 1px 1px 3px 0px rgb(0 0 0 / 20%) inset; overflow-y: auto; diff --git a/config-ui/src/components/loading/styled.ts b/config-ui/src/components/loading/styled.ts index b5c5ce82f29..2badd833024 100644 --- a/config-ui/src/components/loading/styled.ts +++ b/config-ui/src/components/loading/styled.ts @@ -37,7 +37,7 @@ export const Wrapper = styled.div` export const Spin = styled.div<{ size: number }>` width: ${({ size }) => size}px; height: ${({ size }) => size}px; - border: 2px solid #7497f7; + border: 2px solid ${({ theme }) => theme.colors.primary}; border-radius: 50%; border-right-color: transparent; box-sizing: border-box; diff --git a/config-ui/src/components/message/index.tsx b/config-ui/src/components/message/index.tsx index 6b9f2fc2fed..ef7d633e158 100644 --- a/config-ui/src/components/message/index.tsx +++ b/config-ui/src/components/message/index.tsx @@ -35,7 +35,7 @@ export const Message = ({ style, size = 20, content }: Props) => { return ( - + {content} diff --git a/config-ui/src/components/page-header/styled.ts b/config-ui/src/components/page-header/styled.ts index 297e2dedfa5..2b031208273 100644 --- a/config-ui/src/components/page-header/styled.ts +++ b/config-ui/src/components/page-header/styled.ts @@ -47,7 +47,7 @@ export const Breadcrumb = styled.li` a { display: flex; align-items: center; - color: #292b3f; + color: ${({ theme }) => theme.colors.text}; } `; export const Extra = styled.div``; diff --git a/config-ui/src/components/tip-layout/styled.ts b/config-ui/src/components/tip-layout/styled.ts index 14dd42a74f4..b3f04dfb861 100644 --- a/config-ui/src/components/tip-layout/styled.ts +++ b/config-ui/src/components/tip-layout/styled.ts @@ -24,7 +24,7 @@ export const Wrapper = styled.div` align-items: center; padding-top: 100px; height: 100vh; - background-color: #f9f9fa; + background-color: ${({ theme }) => theme.colors.bgLayout}; box-sizing: border-box; `; @@ -42,7 +42,7 @@ export const Inner = styled.div` margin: 16px 0; &.warning { - color: #faad14; + color: ${({ theme }) => theme.colors.warningAlt}; } } `; diff --git a/config-ui/src/features/index.ts b/config-ui/src/features/index.ts index 4e6e8a4dece..4a64f6e0419 100644 --- a/config-ui/src/features/index.ts +++ b/config-ui/src/features/index.ts @@ -17,3 +17,4 @@ */ export * from './connections'; +export * from './theme'; diff --git a/config-ui/src/features/theme/index.ts b/config-ui/src/features/theme/index.ts new file mode 100644 index 00000000000..513ab48a7f8 --- /dev/null +++ b/config-ui/src/features/theme/index.ts @@ -0,0 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +export * from './slice'; diff --git a/config-ui/src/features/theme/slice.ts b/config-ui/src/features/theme/slice.ts new file mode 100644 index 00000000000..cd39897324e --- /dev/null +++ b/config-ui/src/features/theme/slice.ts @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +import { RootState } from '@/app/store'; +import type { ThemeMode, ResolvedTheme } from '@/theme/tokens'; + +export const THEME_STORAGE_KEY = 'devlake.theme'; + +const NEXT_MODE: Record = { + light: 'dark', + dark: 'system', + system: 'light', +}; + +const isThemeMode = (value: unknown): value is ThemeMode => value === 'light' || value === 'dark' || value === 'system'; + +const loadInitialMode = (): ThemeMode => { + if (typeof window === 'undefined') return 'system'; + try { + const stored = window.localStorage.getItem(THEME_STORAGE_KEY); + if (isThemeMode(stored)) return stored; + } catch { + // localStorage may be unavailable (e.g., SSR / privacy mode) + } + return 'system'; +}; + +const persistMode = (mode: ThemeMode): void => { + if (typeof window === 'undefined') return; + try { + window.localStorage.setItem(THEME_STORAGE_KEY, mode); + } catch { + // localStorage may be unavailable (e.g., SSR / privacy mode) + } +}; + +interface ThemeState { + mode: ThemeMode; +} + +const initialState: ThemeState = { + mode: loadInitialMode(), +}; + +export const themeSlice = createSlice({ + name: 'theme', + initialState, + reducers: { + setMode(state, action: PayloadAction) { + state.mode = action.payload; + persistMode(action.payload); + }, + cycleMode(state) { + state.mode = NEXT_MODE[state.mode]; + persistMode(state.mode); + }, + }, +}); + +export const { setMode, cycleMode } = themeSlice.actions; + +export default themeSlice.reducer; + +export const selectThemeMode = (state: RootState): ThemeMode => state.theme.mode; + +export const resolveThemeMode = (mode: ThemeMode): ResolvedTheme => { + if (mode !== 'system') return mode; + if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return 'light'; + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; +}; diff --git a/config-ui/src/index.css b/config-ui/src/index.css index c322bc785f7..7e523eb049b 100644 --- a/config-ui/src/index.css +++ b/config-ui/src/index.css @@ -16,8 +16,16 @@ * */ +/* Initial-paint fallbacks; ThemeProvider overrides these from theme tokens. */ +:root { + --devlake-color-text: #292b3f; + --devlake-color-text-muted: #94959f; + --devlake-color-bg: #ffffff; +} + body { - color: #292b3f; + color: var(--devlake-color-text); + background-color: var(--devlake-color-bg); margin: 0; } @@ -64,7 +72,7 @@ ul { p { margin: 8px 0; font-size: 12px; - color: #94959f; + color: var(--devlake-color-text-muted); } #root { diff --git a/config-ui/src/main.tsx b/config-ui/src/main.tsx index 9f33576fac9..eeff8947e49 100644 --- a/config-ui/src/main.tsx +++ b/config-ui/src/main.tsx @@ -19,25 +19,19 @@ import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { RouterProvider } from 'react-router-dom'; -import { ConfigProvider } from 'antd'; import { PageLoading } from '@/components'; +import { ThemeProvider } from '@/theme'; import { store } from './app/store'; import { router } from './app/router'; import './index.css'; ReactDOM.render( - - + + } /> - - , + + , document.getElementById('root'), ); diff --git a/config-ui/src/plugins/components/connection-form/fields/styled.ts b/config-ui/src/plugins/components/connection-form/fields/styled.ts index 08b4e92677f..1c69cdbe320 100644 --- a/config-ui/src/plugins/components/connection-form/fields/styled.ts +++ b/config-ui/src/plugins/components/connection-form/fields/styled.ts @@ -24,7 +24,7 @@ export const Label = styled.label` `; export const LabelInfo = styled.i` - color: #ff8b8b; + color: ${({ theme }) => theme.colors.secondary}; `; export const LabelDescription = styled.p` diff --git a/config-ui/src/plugins/components/data-scope-remote/search-local.tsx b/config-ui/src/plugins/components/data-scope-remote/search-local.tsx index 39e1282c28d..eba655a1f4f 100644 --- a/config-ui/src/plugins/components/data-scope-remote/search-local.tsx +++ b/config-ui/src/plugins/components/data-scope-remote/search-local.tsx @@ -219,7 +219,7 @@ export const SearchLocal = ({ mode, plugin, connectionId, config, disabledScope, {status === 'loaded' && ( - + {miller.items.length} scopes found )} diff --git a/config-ui/src/plugins/components/data-scope-remote/styled.ts b/config-ui/src/plugins/components/data-scope-remote/styled.ts index 27ecd0fe893..dc880b18c07 100644 --- a/config-ui/src/plugins/components/data-scope-remote/styled.ts +++ b/config-ui/src/plugins/components/data-scope-remote/styled.ts @@ -31,6 +31,6 @@ export const JobLoad = styled.div` & > span.count { margin: 0 8px; - color: #7497f7; + color: ${({ theme }) => theme.colors.primary}; } `; diff --git a/config-ui/src/plugins/register/argocd/transformation.tsx b/config-ui/src/plugins/register/argocd/transformation.tsx index be94536f346..34b880c4b96 100644 --- a/config-ui/src/plugins/register/argocd/transformation.tsx +++ b/config-ui/src/plugins/register/argocd/transformation.tsx @@ -99,7 +99,7 @@ const renderCollapseItems = ({ }) } /> - * + *
diff --git a/config-ui/src/plugins/register/azure/connection-fields/styled.ts b/config-ui/src/plugins/register/azure/connection-fields/styled.ts index 11e47a3d0a1..acdd9a72879 100644 --- a/config-ui/src/plugins/register/azure/connection-fields/styled.ts +++ b/config-ui/src/plugins/register/azure/connection-fields/styled.ts @@ -24,7 +24,7 @@ export const Label = styled.label` `; export const LabelInfo = styled.i` - color: #ff8b8b; + color: ${({ theme }) => theme.colors.secondary}; `; export const LabelDescription = styled.p` diff --git a/config-ui/src/plugins/register/azure/transformation.tsx b/config-ui/src/plugins/register/azure/transformation.tsx index df57ceb04fb..1a8ea080740 100644 --- a/config-ui/src/plugins/register/azure/transformation.tsx +++ b/config-ui/src/plugins/register/azure/transformation.tsx @@ -100,7 +100,7 @@ const renderCollapseItems = ({ }) } /> - * + *
diff --git a/config-ui/src/plugins/register/circleci/transformation.tsx b/config-ui/src/plugins/register/circleci/transformation.tsx index 90a00377d15..0fce8f6f701 100644 --- a/config-ui/src/plugins/register/circleci/transformation.tsx +++ b/config-ui/src/plugins/register/circleci/transformation.tsx @@ -100,7 +100,7 @@ const renderCollapseItems = ({ }) } /> - * + *
diff --git a/config-ui/src/plugins/register/gh-copilot/connection-fields/styled.ts b/config-ui/src/plugins/register/gh-copilot/connection-fields/styled.ts index 82ba9f717d0..b068ff9910b 100644 --- a/config-ui/src/plugins/register/gh-copilot/connection-fields/styled.ts +++ b/config-ui/src/plugins/register/gh-copilot/connection-fields/styled.ts @@ -20,6 +20,6 @@ import styled from 'styled-components'; export const ErrorText = styled.div` margin-top: 4px; - color: #f5222d; + color: ${({ theme }) => theme.colors.error}; font-size: 12px; `; diff --git a/config-ui/src/plugins/register/github/connection-fields/styled.ts b/config-ui/src/plugins/register/github/connection-fields/styled.ts index a9522799e0d..96ffc2eb0d7 100644 --- a/config-ui/src/plugins/register/github/connection-fields/styled.ts +++ b/config-ui/src/plugins/register/github/connection-fields/styled.ts @@ -24,7 +24,7 @@ export const Label = styled.label` `; export const LabelInfo = styled.i` - color: #ff8b8b; + color: ${({ theme }) => theme.colors.secondary}; `; export const LabelDescription = styled.p` @@ -48,11 +48,11 @@ export const Input = styled.div` margin-left: 4px; span.error { - color: #f5222d; + color: ${({ theme }) => theme.colors.error}; } span.success { - color: #4db764; + color: ${({ theme }) => theme.colors.success}; } } } @@ -65,7 +65,7 @@ export const Input = styled.div` export const Alert = styled.div` margin-top: 8px; padding: 12px 20px; - background: #f9f9fa; - border: 1px solid #dbdcdf; + background: ${({ theme }) => theme.colors.bgLayout}; + border: 1px solid ${({ theme }) => theme.colors.border}; border-radius: 4px; `; diff --git a/config-ui/src/plugins/register/github/connection-fields/token.tsx b/config-ui/src/plugins/register/github/connection-fields/token.tsx index 95a322c58b8..4094c672e9a 100644 --- a/config-ui/src/plugins/register/github/connection-fields/token.tsx +++ b/config-ui/src/plugins/register/github/connection-fields/token.tsx @@ -171,9 +171,9 @@ export const Token = ({ {status && (

- {status === 'success' && } - {status === 'warning' && } - {status === 'error' && } + {status === 'success' && } + {status === 'warning' && } + {status === 'error' && } Token Permissions

{status === 'success' &&

All required fields are checked.

} diff --git a/config-ui/src/plugins/register/github/transformation.tsx b/config-ui/src/plugins/register/github/transformation.tsx index 0581e931534..dcb61633ae9 100644 --- a/config-ui/src/plugins/register/github/transformation.tsx +++ b/config-ui/src/plugins/register/github/transformation.tsx @@ -277,7 +277,7 @@ const renderCollapseItems = ({ }) } /> - * + *
@@ -368,12 +368,12 @@ const renderCollapseItems = ({ content={ <>
- + Example 1: PR #321 body contains "Closes #1234" (PR #321 and issue #1234 will be mapped by the following RegEx)
- + Example 2: PR #321 body contains "Related to #1234" (PR #321 and issue #1234 will NOT be mapped by the following RegEx)
diff --git a/config-ui/src/plugins/register/gitlab/transformation.tsx b/config-ui/src/plugins/register/gitlab/transformation.tsx index 2405e661454..5e324ce7be6 100644 --- a/config-ui/src/plugins/register/gitlab/transformation.tsx +++ b/config-ui/src/plugins/register/gitlab/transformation.tsx @@ -156,7 +156,7 @@ const renderCollapseItems = ({ }) } /> - * + *
diff --git a/config-ui/src/plugins/register/jenkins/transformation.tsx b/config-ui/src/plugins/register/jenkins/transformation.tsx index 0a44f1dbd44..0cfbf98f43a 100644 --- a/config-ui/src/plugins/register/jenkins/transformation.tsx +++ b/config-ui/src/plugins/register/jenkins/transformation.tsx @@ -100,7 +100,7 @@ const renderCollapseItems = ({ }) } /> - * + *
diff --git a/config-ui/src/plugins/register/jira/connection-fields/styled.ts b/config-ui/src/plugins/register/jira/connection-fields/styled.ts index 11e47a3d0a1..acdd9a72879 100644 --- a/config-ui/src/plugins/register/jira/connection-fields/styled.ts +++ b/config-ui/src/plugins/register/jira/connection-fields/styled.ts @@ -24,7 +24,7 @@ export const Label = styled.label` `; export const LabelInfo = styled.i` - color: #ff8b8b; + color: ${({ theme }) => theme.colors.secondary}; `; export const LabelDescription = styled.p` diff --git a/config-ui/src/plugins/register/jira/transformation-fields/styled.ts b/config-ui/src/plugins/register/jira/transformation-fields/styled.ts index c4363e38386..6b6b2f44512 100644 --- a/config-ui/src/plugins/register/jira/transformation-fields/styled.ts +++ b/config-ui/src/plugins/register/jira/transformation-fields/styled.ts @@ -32,7 +32,7 @@ export const CrossDomain = styled.div` span { padding: 4px 8px; - background-color: #efefef; + background-color: ${({ theme }) => theme.colors.bgMuted}; } span + span { @@ -53,7 +53,7 @@ export const RemoteLinkWrapper = styled.div` .error { margin-top: 2px; - color: #cd4246; + color: ${({ theme }) => theme.colors.errorMuted}; } `; @@ -63,7 +63,7 @@ export const DialogBody = styled.div` padding: 8px 16px; max-height: 240px; overflow-y: auto; - background: #efefef; + background: ${({ theme }) => theme.colors.bgMuted}; } .search { diff --git a/config-ui/src/plugins/register/opsgenie/connection-fields/styled.ts b/config-ui/src/plugins/register/opsgenie/connection-fields/styled.ts index a9522799e0d..96ffc2eb0d7 100644 --- a/config-ui/src/plugins/register/opsgenie/connection-fields/styled.ts +++ b/config-ui/src/plugins/register/opsgenie/connection-fields/styled.ts @@ -24,7 +24,7 @@ export const Label = styled.label` `; export const LabelInfo = styled.i` - color: #ff8b8b; + color: ${({ theme }) => theme.colors.secondary}; `; export const LabelDescription = styled.p` @@ -48,11 +48,11 @@ export const Input = styled.div` margin-left: 4px; span.error { - color: #f5222d; + color: ${({ theme }) => theme.colors.error}; } span.success { - color: #4db764; + color: ${({ theme }) => theme.colors.success}; } } } @@ -65,7 +65,7 @@ export const Input = styled.div` export const Alert = styled.div` margin-top: 8px; padding: 12px 20px; - background: #f9f9fa; - border: 1px solid #dbdcdf; + background: ${({ theme }) => theme.colors.bgLayout}; + border: 1px solid ${({ theme }) => theme.colors.border}; border-radius: 4px; `; diff --git a/config-ui/src/plugins/register/q-dev/connection-fields/aws-credentials.tsx b/config-ui/src/plugins/register/q-dev/connection-fields/aws-credentials.tsx index 7b4c20d232f..405d33806fd 100644 --- a/config-ui/src/plugins/register/q-dev/connection-fields/aws-credentials.tsx +++ b/config-ui/src/plugins/register/q-dev/connection-fields/aws-credentials.tsx @@ -175,7 +175,7 @@ export const AwsCredentials = ({ type, initialValues, values, setValues, setErro onChange={handleAccessKeyChange} status={accessKeyError ? 'error' : ''} /> - {accessKeyError &&
{accessKeyError}
} + {accessKeyError &&
{accessKeyError}
} - {secretKeyError &&
{secretKeyError}
} + {secretKeyError &&
{secretKeyError}
}
)} @@ -200,7 +200,14 @@ export const AwsCredentials = ({ type, initialValues, values, setValues, setErro title="IAM Role Authentication" description="DevLake will use the IAM role attached to the EC2 instance, ECS task, or Lambda function" > -
+

Make sure the IAM role has the necessary S3 permissions to access your bucket. No additional credentials are required when using IAM role authentication. @@ -217,7 +224,7 @@ export const AwsCredentials = ({ type, initialValues, values, setValues, setErro onChange={handleRegionChange} status={regionError ? 'error' : ''} /> - {regionError &&

{regionError}
} + {regionError &&
{regionError}
} ); diff --git a/config-ui/src/plugins/register/q-dev/connection-fields/connection-test.tsx b/config-ui/src/plugins/register/q-dev/connection-fields/connection-test.tsx index 2b5d6033933..5157338fc45 100644 --- a/config-ui/src/plugins/register/q-dev/connection-fields/connection-test.tsx +++ b/config-ui/src/plugins/register/q-dev/connection-fields/connection-test.tsx @@ -174,7 +174,7 @@ export const QDevConnectionTest = ({ plugin, connectionId, values, initialValues
✓ S3 Access: Verified
{testResult.details.identityCenterAccess &&
✓ IAM Identity Center: Configured
} {!values.identityStoreId && ( -
+
⚠️ IAM Identity Center not configured - user display names will show as user IDs
)} diff --git a/config-ui/src/plugins/register/q-dev/connection-fields/identity-center-config.tsx b/config-ui/src/plugins/register/q-dev/connection-fields/identity-center-config.tsx index bd2333807cb..b467a32013d 100644 --- a/config-ui/src/plugins/register/q-dev/connection-fields/identity-center-config.tsx +++ b/config-ui/src/plugins/register/q-dev/connection-fields/identity-center-config.tsx @@ -105,7 +105,7 @@ export const IdentityCenterConfig = ({ initialValues, values, setValues, setErro onChange={handleStoreIdChange} status={storeIdError ? 'error' : ''} /> - {storeIdError &&
{storeIdError}
} + {storeIdError &&
{storeIdError}
} - {regionError &&
{regionError}
} + {regionError &&
{regionError}
}
); diff --git a/config-ui/src/plugins/register/q-dev/connection-fields/s3-config.tsx b/config-ui/src/plugins/register/q-dev/connection-fields/s3-config.tsx index d36843080df..734801fdbad 100644 --- a/config-ui/src/plugins/register/q-dev/connection-fields/s3-config.tsx +++ b/config-ui/src/plugins/register/q-dev/connection-fields/s3-config.tsx @@ -70,7 +70,7 @@ export const S3Config = ({ initialValues, values, setValues, setErrors }: Props) onChange={handleBucketChange} status={bucketError ? 'error' : ''} /> - {bucketError &&
{bucketError}
} + {bucketError &&
{bucketError}
} ); }; diff --git a/config-ui/src/plugins/register/webhook/styled.ts b/config-ui/src/plugins/register/webhook/styled.ts index ca59c72c150..c7186d44af6 100644 --- a/config-ui/src/plugins/register/webhook/styled.ts +++ b/config-ui/src/plugins/register/webhook/styled.ts @@ -27,7 +27,7 @@ export const Wrapper = styled.div` margin-bottom: 16px; padding: 0; font-weight: 600; - color: #4db764; + color: ${({ theme }) => theme.colors.success}; } h5 { @@ -35,12 +35,12 @@ export const Wrapper = styled.div` } p { - color: #292b3f; + color: ${({ theme }) => theme.colors.text}; } `; export const Action = styled.div` - color: #7497f7; + color: ${({ theme }) => theme.colors.primary}; span { cursor: pointer; diff --git a/config-ui/src/routes/blueprint/detail/components/sync-policy/styled.ts b/config-ui/src/routes/blueprint/detail/components/sync-policy/styled.ts index 079e5fc8a4a..e10081c31dc 100644 --- a/config-ui/src/routes/blueprint/detail/components/sync-policy/styled.ts +++ b/config-ui/src/routes/blueprint/detail/components/sync-policy/styled.ts @@ -47,5 +47,5 @@ export const Input = styled.div` `; export const Error = styled.div` - color: #e34040; + color: ${({ theme }) => theme.colors.errorAlt}; `; diff --git a/config-ui/src/routes/blueprint/detail/styled.ts b/config-ui/src/routes/blueprint/detail/styled.ts index 6a4ecafb5a4..88dd9a03b30 100644 --- a/config-ui/src/routes/blueprint/detail/styled.ts +++ b/config-ui/src/routes/blueprint/detail/styled.ts @@ -51,7 +51,7 @@ export const ConnectionItem = styled.li` margin-right: 24px; padding: 12px 16px; width: 280px; - background: #ffffff; + background: ${({ theme }) => theme.colors.bgContainer}; box-shadow: 0px 2.4px 4.8px -0.8px rgba(0, 0, 0, 0.1), 0px 1.6px 8px rgba(0, 0, 0, 0.07); border-radius: 4px; diff --git a/config-ui/src/routes/blueprint/home/index.tsx b/config-ui/src/routes/blueprint/home/index.tsx index daa57c1d5b4..0085a435da4 100644 --- a/config-ui/src/routes/blueprint/home/index.tsx +++ b/config-ui/src/routes/blueprint/home/index.tsx @@ -121,7 +121,11 @@ export const BlueprintHomePage = () => { title: 'Blueprint Name', key: 'name', render: (_, { id, name }) => ( - + {name} ), diff --git a/config-ui/src/routes/blueprint/home/styled.ts b/config-ui/src/routes/blueprint/home/styled.ts index 0f4b89498cd..df93ea1811d 100644 --- a/config-ui/src/routes/blueprint/home/styled.ts +++ b/config-ui/src/routes/blueprint/home/styled.ts @@ -35,7 +35,7 @@ export const Label = styled.label` `; export const LabelInfo = styled.i` - color: #ff8b8b; + color: ${({ theme }) => theme.colors.secondary}; `; export const LabelDescription = styled.p` diff --git a/config-ui/src/routes/connection/connections.tsx b/config-ui/src/routes/connection/connections.tsx index 31b463bc065..62427c45ccd 100644 --- a/config-ui/src/routes/connection/connections.tsx +++ b/config-ui/src/routes/connection/connections.tsx @@ -75,7 +75,7 @@ export const Connections = () => { }; return ( - +

Connections

Create and manage data connections from the following data sources or Webhooks to be used in syncing data in diff --git a/config-ui/src/routes/connection/styled.ts b/config-ui/src/routes/connection/styled.ts index 6aa45c94808..72e339e5519 100644 --- a/config-ui/src/routes/connection/styled.ts +++ b/config-ui/src/routes/connection/styled.ts @@ -18,7 +18,7 @@ import styled from 'styled-components'; -export const Wrapper = styled.div<{ theme: string }>` +export const Wrapper = styled.div` h2 { margin-top: 36px; } @@ -39,7 +39,7 @@ export const Wrapper = styled.div<{ theme: string }>` left: 0; width: 48px; height: 4px; - background-color: ${({ theme }) => theme}; + background-color: ${({ theme }) => theme.colors.primary}; } } ul { @@ -58,13 +58,15 @@ export const Wrapper = styled.div<{ theme: string }>` padding: 20px 0; width: 160px; border-radius: 8px; + border: 1px solid ${({ theme }) => (theme.mode === 'dark' ? theme.colors.border : 'transparent')}; + background-color: ${({ theme }) => theme.colors.bgContainer}; box-shadow: 0px 2.4px 4.8px -0.8px rgba(0, 0, 0, 0.1), 0px 1.6px 8px rgba(0, 0, 0, 0.07); box-sizing: border-box; cursor: pointer; transition: all 0.2s linear; &:hover { - background-color: #eeeeee; + background-color: ${({ theme }) => theme.colors.bgHover}; } & > .beta { @@ -73,8 +75,8 @@ export const Wrapper = styled.div<{ theme: string }>` right: 0; padding: 4px 8px; font-size: 12px; - color: #fff; - background-color: #f5a623; + color: ${({ theme }) => theme.colors.textInverse}; + background-color: ${({ theme }) => theme.colors.warning}; border-radius: 8px; } @@ -102,12 +104,12 @@ export const Wrapper = styled.div<{ theme: string }>` content: ''; width: 88px; height: 1px; - background-color: #dbdcdf; + background-color: ${({ theme }) => theme.colors.border}; } } & > .count { - color: #70727f; + color: ${({ theme }) => theme.colors.textMuted}; } } `; diff --git a/config-ui/src/routes/db-migrate/index.tsx b/config-ui/src/routes/db-migrate/index.tsx index 7a83b91b535..d32903eacbf 100644 --- a/config-ui/src/routes/db-migrate/index.tsx +++ b/config-ui/src/routes/db-migrate/index.tsx @@ -46,7 +46,7 @@ export const DBMigrate = () => {

- + New Migration Scripts Detected

diff --git a/config-ui/src/routes/error/index.tsx b/config-ui/src/routes/error/index.tsx index 3e1b1dd50c4..9e02bf64ce5 100644 --- a/config-ui/src/routes/error/index.tsx +++ b/config-ui/src/routes/error/index.tsx @@ -33,8 +33,8 @@ export const Error = () => { - -

{error.toString() || 'Unknown Error'}

+ +

{error.toString() || 'Unknown Error'}

Please try again, if the problem persists include the above error message when filing a bug report on{' '} diff --git a/config-ui/src/routes/layout/layout.tsx b/config-ui/src/routes/layout/layout.tsx index 5d7a02e661f..0d12eb5c3d0 100644 --- a/config-ui/src/routes/layout/layout.tsx +++ b/config-ui/src/routes/layout/layout.tsx @@ -19,17 +19,29 @@ import { useState, useEffect, useMemo } from 'react'; import { useLoaderData, Outlet, useNavigate, useLocation } from 'react-router-dom'; import { Helmet } from 'react-helmet'; -import { Layout as AntdLayout, Menu, Divider, Dropdown, Button } from 'antd'; -import { UserOutlined, LogoutOutlined } from '@ant-design/icons'; +import { Layout as AntdLayout, Menu, Divider, Dropdown, Button, Tooltip } from 'antd'; +import { UserOutlined, LogoutOutlined, SunOutlined, MoonOutlined, DesktopOutlined } from '@ant-design/icons'; import API from '@/api'; import { PageLoading, Logo, ExternalLink } from '@/components'; -import { init, selectError, selectStatus } from '@/features'; +import { init, selectError, selectStatus, cycleMode, selectThemeMode } from '@/features'; import { OnboardCard } from '@/routes/onboard/components'; import { useAppDispatch, useAppSelector } from '@/hooks'; import { menuItems, menuItemsMatch, headerItems } from './config'; +const themeIcon = { + light: , + dark: , + system: , +} as const; + +const themeLabel = { + light: 'Light theme', + dark: 'Dark theme', + system: 'Follow system', +} as const; + const { Sider, Header, Content, Footer } = AntdLayout; const brandName = import.meta.env.DEVLAKE_BRAND_NAME ?? 'DevLake'; @@ -63,6 +75,7 @@ export const Layout = () => { const dispatch = useAppDispatch(); const status = useAppSelector(selectStatus); const error = useAppSelector(selectError); + const themeMode = useAppSelector(selectThemeMode); useEffect(() => { dispatch(init(plugins)); @@ -153,6 +166,15 @@ export const Layout = () => { {i !== arr.length - 1 && } ))} + + +