diff --git a/next.config.ts b/next.config.ts
index eac6f327..17705dc2 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -3,15 +3,16 @@ import pkg from './package.json' assert { type: 'json' };
const TRACKER_SCRIPT = '/script.js';
-const basePath = process.env.BASE_PATH;
-const collectApiEndpoint = process.env.COLLECT_API_ENDPOINT;
-const cloudMode = !!process.env.CLOUD_MODE;
-const corsMaxAge = process.env.CORS_MAX_AGE;
-const defaultLocale = process.env.DEFAULT_LOCALE;
-const forceSSL = process.env.FORCE_SSL;
-const frameAncestors = process.env.ALLOWED_FRAME_URLS ?? '';
-const trackerScriptName = process.env.TRACKER_SCRIPT_NAME;
-const trackerScriptURL = process.env.TRACKER_SCRIPT_URL;
+const basePath = process.env.BASE_PATH || '';
+const cloudMode = process.env.CLOUD_MODE || '';
+const cloudUrl = process.env.CLOUD_URL || '';
+const collectApiEndpoint = process.env.COLLECT_API_ENDPOINT || '';
+const corsMaxAge = process.env.CORS_MAX_AGE || '';
+const defaultLocale = process.env.DEFAULT_LOCALE || '';
+const forceSSL = process.env.FORCE_SSL || '';
+const frameAncestors = process.env.ALLOWED_FRAME_URLS || '';
+const trackerScriptName = process.env.TRACKER_SCRIPT_NAME || '';
+const trackerScriptURL = process.env.TRACKER_SCRIPT_URL || '';
const contentSecurityPolicy = [
`default-src 'self'`,
@@ -163,6 +164,7 @@ export default {
env: {
basePath,
cloudMode,
+ cloudUrl,
currentVersion: pkg.version,
defaultLocale,
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 61707399..94992862 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -367,7 +367,44 @@ importers:
specifier: ^5.9.2
version: 5.9.2
- dist: {}
+ dist:
+ dependencies:
+ chart.js:
+ specifier: ^4.5.0
+ version: 4.5.0
+ chartjs-adapter-date-fns:
+ specifier: ^3.0.0
+ version: 3.0.0(chart.js@4.5.0)(date-fns@2.30.0)
+ colord:
+ specifier: ^2.9.2
+ version: 2.9.3
+ jsonwebtoken:
+ specifier: ^9.0.2
+ version: 9.0.2
+ lucide-react:
+ specifier: ^0.542.0
+ version: 0.542.0(react@19.1.1)
+ pure-rand:
+ specifier: ^7.0.1
+ version: 7.0.1
+ react-simple-maps:
+ specifier: ^2.3.0
+ version: 2.3.0(prop-types@15.8.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ react-use-measure:
+ specifier: ^2.0.4
+ version: 2.1.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ react-window:
+ specifier: ^1.8.6
+ version: 1.8.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ serialize-error:
+ specifier: ^12.0.0
+ version: 12.0.0
+ thenby:
+ specifier: ^1.3.4
+ version: 1.3.4
+ uuid:
+ specifier: ^11.1.0
+ version: 11.1.0
packages:
@@ -5246,6 +5283,11 @@ packages:
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ lucide-react@0.542.0:
+ resolution: {integrity: sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
lucide-react@0.543.0:
resolution: {integrity: sha512-fpVfuOQO0V3HBaOA1stIiP/A2fPCXHIleRZL16Mx3HmjTYwNSbimhnFBygs2CAfU1geexMX5ItUcWBGUaqw5CA==}
peerDependencies:
@@ -13376,6 +13418,10 @@ snapshots:
dependencies:
react: 19.1.1
+ lucide-react@0.542.0(react@19.1.1):
+ dependencies:
+ react: 19.1.1
+
lucide-react@0.543.0(react@19.1.1):
dependencies:
react: 19.1.1
diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx
index 6c14a484..c3f88c5b 100644
--- a/src/app/(main)/App.tsx
+++ b/src/app/(main)/App.tsx
@@ -1,22 +1,25 @@
'use client';
import { Grid, Loading, Column } from '@umami/react-zen';
import Script from 'next/script';
-import { usePathname } from 'next/navigation';
import { UpdateNotice } from './UpdateNotice';
import { SideNav } from '@/app/(main)/SideNav';
-import { useLoginQuery, useConfig } from '@/components/hooks';
+import { useLoginQuery, useConfig, useNavigation } from '@/components/hooks';
export function App({ children }) {
const { user, isLoading, error } = useLoginQuery();
const config = useConfig();
- const pathname = usePathname();
+ const { pathname, router } = useNavigation();
if (isLoading || !config) {
return ;
}
if (error) {
- window.location.href = `${process.env.basePath || ''}/login`;
+ if (process.env.cloudMode) {
+ window.location.href = '/login';
+ } else {
+ router.push('/login');
+ }
return null;
}
diff --git a/src/app/(main)/SideNav.tsx b/src/app/(main)/SideNav.tsx
index a50f0c1f..4398009e 100644
--- a/src/app/(main)/SideNav.tsx
+++ b/src/app/(main)/SideNav.tsx
@@ -1,3 +1,4 @@
+import { Key } from 'react';
import Link from 'next/link';
import {
Sidebar,
@@ -12,8 +13,7 @@ import { Globe, LinkIcon, LogoSvg, Grid2x2, PanelLeft } from '@/components/icons
import { useMessages, useNavigation, useGlobalState } from '@/components/hooks';
import { NavButton } from '@/components/input/NavButton';
import { PanelButton } from '@/components/input/PanelButton';
-import { Key } from 'react';
-import { SettingsButton } from '@/components/input/SettingsButton';
+import { LanguageButton } from '@/components/input/LanguageButton';
export function SideNav(props: SidebarProps) {
const { formatMessage, labels } = useMessages();
@@ -77,9 +77,9 @@ export function SideNav(props: SidebarProps) {
})}
-
-
- {!isCollapsed && !hasNav && }
+
+
+
diff --git a/src/assets/switch.svg b/src/assets/switch.svg
new file mode 100644
index 00000000..86166cc5
--- /dev/null
+++ b/src/assets/switch.svg
@@ -0,0 +1 @@
+
diff --git a/src/components/icons.ts b/src/components/icons.ts
index ddcda3b6..e0e7c678 100644
--- a/src/components/icons.ts
+++ b/src/components/icons.ts
@@ -1,14 +1,14 @@
export * from 'lucide-react';
export {
- Logo as LogoSvg,
Compare as CompareSvg,
Funnel as FunnelSvg,
Lightning as LightningSvg,
Location as LocationSvg,
+ Logo as LogoSvg,
Magnet as MagnetSvg,
Money as MoneySvg,
Network as NetworkSvg,
Path as PathSvg,
+ Switch as SwitchSvg,
Target as TargetSvg,
- AddUser as AddUserSvg,
} from '@/components/svg';
diff --git a/src/components/input/NavButton.tsx b/src/components/input/NavButton.tsx
index 39bff58d..240c6735 100644
--- a/src/components/input/NavButton.tsx
+++ b/src/components/input/NavButton.tsx
@@ -24,9 +24,10 @@ import {
Settings,
User,
Users,
+ SwitchSvg,
} from '@/components/icons';
import { DOCS_URL } from '@/lib/constants';
-import * as url from 'node:url';
+import { ArrowRight } from 'lucide-react';
export interface TeamsButtonProps {
showText?: boolean;
@@ -43,7 +44,7 @@ export function NavButton({ showText = true }: TeamsButtonProps) {
const label = teamId ? team?.name : user.username;
const getUrl = (url: string) => {
- return cloudMode ? `${process.env.cloudUrl}/${url}` : url;
+ return cloudMode ? `${process.env.cloudUrl}${url}` : url;
};
const handleAction = async () => {};
@@ -75,42 +76,52 @@ export function NavButton({ showText = true }: TeamsButtonProps) {
-
diff --git a/src/components/messages.ts b/src/components/messages.ts
index c40a3b05..0438c06e 100644
--- a/src/components/messages.ts
+++ b/src/components/messages.ts
@@ -362,6 +362,7 @@ export const labels = defineMessages({
share: { id: 'label.share', defaultMessage: 'Share' },
support: { id: 'label.support', defaultMessage: 'Support' },
documentation: { id: 'label.documentation', defaultMessage: 'Documentation' },
+ switchAccount: { id: 'label.switch-account', defaultMessage: 'Switch account' },
});
export const messages = defineMessages({
diff --git a/src/components/svg/Download.tsx b/src/components/svg/Download.tsx
new file mode 100644
index 00000000..56d4d683
--- /dev/null
+++ b/src/components/svg/Download.tsx
@@ -0,0 +1,9 @@
+import * as React from 'react';
+import type { SVGProps } from 'react';
+const SvgDownload = (props: SVGProps) => (
+
+);
+export default SvgDownload;
diff --git a/src/components/svg/Export.tsx b/src/components/svg/Export.tsx
new file mode 100644
index 00000000..355321cf
--- /dev/null
+++ b/src/components/svg/Export.tsx
@@ -0,0 +1,12 @@
+import * as React from 'react';
+import type { SVGProps } from 'react';
+const SvgExport = (props: SVGProps) => (
+
+);
+export default SvgExport;
diff --git a/src/components/svg/Switch.tsx b/src/components/svg/Switch.tsx
new file mode 100644
index 00000000..2a12f393
--- /dev/null
+++ b/src/components/svg/Switch.tsx
@@ -0,0 +1,19 @@
+import * as React from 'react';
+import type { SVGProps } from 'react';
+const SvgSwitch = (props: SVGProps) => (
+
+);
+export default SvgSwitch;
diff --git a/src/components/svg/index.ts b/src/components/svg/index.ts
index 86c3ea94..1bfc728a 100644
--- a/src/components/svg/index.ts
+++ b/src/components/svg/index.ts
@@ -6,7 +6,9 @@ export { default as Bookmark } from './Bookmark';
export { default as Change } from './Change';
export { default as Compare } from './Compare';
export { default as Dashboard } from './Dashboard';
+export { default as Download } from './Download';
export { default as Expand } from './Expand';
+export { default as Export } from './Export';
export { default as Flag } from './Flag';
export { default as Funnel } from './Funnel';
export { default as Gear } from './Gear';
@@ -28,6 +30,7 @@ export { default as Redo } from './Redo';
export { default as Reports } from './Reports';
export { default as Security } from './Security';
export { default as Speaker } from './Speaker';
+export { default as Switch } from './Switch';
export { default as Tag } from './Tag';
export { default as Target } from './Target';
export { default as Visitor } from './Visitor';