Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
c1e3254
basic setup
mateumiralles Mar 3, 2026
1bb6a7a
FIX tailwind rebuild
mateumiralles Mar 3, 2026
a489d15
tw-script fix
mateumiralles Mar 16, 2026
a48930a
shadcn init
mateumiralles Mar 17, 2026
aa2fc40
boostrap legacy fix
mateumiralles Mar 17, 2026
5856e21
Home page
mateumiralles Mar 17, 2026
2194bef
button round fix
mateumiralles Mar 18, 2026
546f2a6
relocate primitives
mateumiralles Mar 18, 2026
3abab34
base AI layout
mateumiralles Mar 18, 2026
70d0931
sidebar and new background fix
mateumiralles Mar 18, 2026
b60463b
update admin-ui instructions
mateumiralles Mar 18, 2026
661590a
dark mode v1
mateumiralles Mar 19, 2026
2e611a4
format docs
mateumiralles Mar 19, 2026
ebb2d27
components merge
mateumiralles Mar 19, 2026
48f5f6e
clickable card variant
mateumiralles Mar 19, 2026
a70de4f
Login status pages
mateumiralles Mar 19, 2026
13722fe
ai store
mateumiralles Mar 24, 2026
489f87b
Installer screens
mateumiralles Mar 25, 2026
50fed69
dark mode fixes
mateumiralles Mar 25, 2026
287ca91
legacy UI fixes
mateumiralles Mar 25, 2026
613b93d
lint
mateumiralles Mar 25, 2026
c9bcf75
Update ai copy
mateumiralles Mar 25, 2026
4f7edf0
packages pages
mateumiralles Mar 26, 2026
7fc92aa
fix installer links
mateumiralles Mar 26, 2026
2723bba
Installer and Package navigation fix
mateumiralles Mar 27, 2026
05f419e
dark mode fixes
mateumiralles Mar 27, 2026
741eff2
nexus link page
mateumiralles Mar 27, 2026
c99545e
back pkg btn fix
mateumiralles Apr 8, 2026
05ce1bd
hide components overview
mateumiralles Apr 8, 2026
18bcc40
page primitive (#2418)
mateumiralles Apr 8, 2026
619ccb4
navbar component
mateumiralles Apr 10, 2026
2ce045c
home section layout
mateumiralles Apr 10, 2026
8438bee
settings tabs
mateumiralles Apr 12, 2026
59d954a
fix autodiagnose
mateumiralles Apr 13, 2026
0ef9675
rm unused settings page
mateumiralles Apr 13, 2026
b5ac127
navbar active fix
mateumiralles Apr 13, 2026
233c432
ecosystem tab
mateumiralles Apr 13, 2026
96c437f
formatter
mateumiralles Apr 13, 2026
eab95bc
rm template screen
mateumiralles Apr 13, 2026
e188089
Inbox tab
mateumiralles Apr 13, 2026
bf2355d
settings tab
mateumiralles Apr 15, 2026
e331dc4
Devices tab
mateumiralles Apr 16, 2026
e313e18
tooltip fix
mateumiralles Apr 16, 2026
dde99ab
sidebar collapse key
mateumiralles Apr 16, 2026
f1a8a09
breadcrumb fix and redirect
mateumiralles Apr 16, 2026
70c8443
system packages in AI section
mateumiralles Apr 17, 2026
11a8849
Add DocsSection and update EcosystemPage
mateumiralles Apr 17, 2026
1032b6a
hide breadcrumb from root paths
mateumiralles Apr 17, 2026
c0326d1
rm tooltips from sidebar if collapsed
mateumiralles Apr 17, 2026
0dec9b6
fix new async toast implementations
mateumiralles Apr 17, 2026
84e35b7
switch fix
mateumiralles Apr 21, 2026
136bb0b
system actions
mateumiralles Apr 21, 2026
bd4d904
split packageTabs
mateumiralles Apr 21, 2026
b6353d7
systgem pkgs chevron toggle
mateumiralles Apr 21, 2026
0a61101
input darkmode fix
mateumiralles Apr 21, 2026
47926bf
banner notifications
mateumiralles Apr 23, 2026
6b74885
reloacte installer
mateumiralles Apr 24, 2026
90cef03
delete old installer route
mateumiralles Apr 24, 2026
e6f7adc
path refactor
mateumiralles Apr 24, 2026
7f8f3c0
Staking dashboard
mateumiralles Apr 24, 2026
b47c696
stakers v1
mateumiralles Apr 27, 2026
0139bf9
re-use store / packages
mateumiralles Apr 28, 2026
b3786f4
nexus page wip
mateumiralles Apr 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
376 changes: 345 additions & 31 deletions .github/instructions/admin-ui.instructions.md

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ ENV COMPOSE_HTTP_TIMEOUT=300 \
WORKDIR /app

RUN apk update && apk add --no-cache docker curl docker-cli-compose xz zip unzip libltdl bash git bind bind-tools bind-dev \
miniupnpc dbus tmux avahi-tools
miniupnpc dbus tmux avahi-tools inotify-tools
RUN corepack enable

# Copy git data
COPY --from=git-data /usr/src/app/.git-data.json $GIT_DATA_PATH
COPY package.json yarn.lock .yarnrc.yml tsconfig.json ./
COPY packages packages

ENTRYPOINT ["/bin/sh", "-c", "mkdir -p /app/packages/dappmanager/dnp_repo /app/packages/dappmanager/DNCORE && \
ENTRYPOINT ["/bin/bash", "-c", "mkdir -p /app/packages/dappmanager/dnp_repo /app/packages/dappmanager/DNCORE && \
ln -sf /app/packages/admin-ui/build/ /app/packages/dappmanager/dist && \
yarn && yarn build && yarn run dev"]
yarn && yarn build && \
bash /app/packages/admin-ui/scripts/tw-watch.sh & \
cd /app && yarn run dev"]
21 changes: 21 additions & 0 deletions packages/admin-ui/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/styles/tailwind.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": "tw"
},
"aliases": {
"components": "@/components",
"ui": "@/components/primitives",
"lib": "@/lib",
"utils": "@/lib/utils",
"hooks": "@/hooks/components"
},
"iconLibrary": "lucide"
}
16 changes: 15 additions & 1 deletion packages/admin-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"server-mock:check-types": "tsc --noEmit --project tsconfig.server-mock.json",
"mock-standalone": "VITE_APP_MOCK=true yarn start",
"mock-standalone:build": "VITE_APP_MOCK=true yarn build",
"dev": "VITE_APP_API_TEST=true vite build --watch"
"dev": "VITE_APP_API_TEST=true vite build --watch",
"dev:tw-watch": "bash scripts/tw-watch.sh"
},
"dependencies": {
"@dappnode/common": "workspace:^0.1.0",
Expand All @@ -23,6 +24,7 @@
"@grafana/faro-web-sdk": "^2.3.1",
"@grafana/faro-web-tracing": "^2.3.1",
"@reduxjs/toolkit": "^1.3.5",
"@tailwindcss/vite": "^4.2.1",
"@types/clipboard": "^2.0.7",
"@types/qrcode.react": "^1.0.2",
"@types/react": "^18.2.14",
Expand All @@ -34,14 +36,20 @@
"@vitejs/plugin-react": "^4.3.1",
"ajv": "^6.10.2",
"bootstrap": "^5.3",
"class-variance-authority": "^0.7.1",
"clipboard": "^2.0.1",
"clsx": "^2.1.1",
"deepmerge": "^2.1.1",
"embla-carousel-react": "^8.6.0",
"ethereum-blockies-base64": "^1.0.2",
"is-ipfs": "^8.0.1",
"lodash-es": "^4.17.21",
"lucide-react": "^0.577.0",
"mitt": "^2.1.0",
"next-themes": "^0.4.6",
"pretty-bytes": "^5.3.0",
"qrcode.react": "^0.8.0",
"radix-ui": "^1.4.3",
"react": "^18.2.0",
"react-bootstrap": "^2.10",
"react-dom": "^18.3.1",
Expand All @@ -55,9 +63,15 @@
"redux-thunk": "^2.3.0",
"sass": "^1.49.7",
"semver": "^7.3.8",
"shadcn": "^4.0.8",
"socket.io-client": "^4.5.1",
"sonner": "^2.0.7",
"styled-components": "^4.2.0",
"swr": "^0.2.0",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.2.1",
"tw-animate-css": "^1.4.0",
"vaul": "^1.1.2",
"vite": "^5.4.19",
"vite-tsconfig-paths": "^4.3.2"
},
Expand Down
19 changes: 19 additions & 0 deletions packages/admin-ui/scripts/tw-watch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
# Watches for .tsx/.ts file changes under src/ and touches tailwind.css
# to trigger a Tailwind CSS rebuild via vite build --watch.
#
# This is needed because vite build --watch (Rollup) doesn't re-process
# tailwind.css when only .tsx files change — Tailwind needs to re-scan
# for new tw: classes.
#
# Excludes the styles/ directory to avoid infinite loops (touching
# tailwind.css would otherwise re-trigger the watcher).

cd "$(dirname "$0")/.." || exit 1

echo "[tw-watch] Watching src/ for .tsx/.ts changes..."

while inotifywait -r -e modify --exclude 'styles/' src/; do
echo "[tw-watch] Change detected, touching tailwind.css"
touch src/styles/tailwind.css
done
171 changes: 87 additions & 84 deletions packages/admin-ui/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import React, { useCallback, useEffect, useState } from "react";
import { Route, useLocation, useNavigate } from "react-router-dom";
import { Route, useLocation, Navigate } from "react-router-dom";
import { startApi, apiAuth, LoginStatus } from "api";
// Components
import { ToastContainer } from "react-toastify";
import NotificationsMain from "./components/NotificationsMain";
import ErrorBoundary from "./components/ErrorBoundary";
import Loading from "components/Loading";
import Welcome from "components/welcome/Welcome";
import SideBar from "components/sidebar/SideBar";
import { TopBar } from "components/topbar/TopBar";
// Pages
// Theme
import { ThemeProvider, useTheme } from "components/ThemeProvider";
// Legacy pages
import { pages } from "./pages";
import { Login } from "./start-pages/Login";
import { Register } from "./start-pages/Register";
import { NoConnection } from "start-pages/NoConnection";
// New pages
// import { NewHomePage } from "./pages-new/home/HomePage";

// Old start pages (keep until deleted)
// import { Login } from "./start-pages/Login";
// import { Register } from "./start-pages/Register";
// import { NoConnection } from "start-pages/NoConnection";
import { LoginPage } from "./pages-new/home/LoginPage";
import { RegisterPage } from "./pages-new/home/RegisterPage";
import { NoConnectionPage } from "./pages-new/home/NoConnectionPage";
import { AiLayout } from "./pages-new/ai/AiLayout";
import { HomeLayout } from "./pages-new/home/HomeLayout";
import { StakingLayout } from "./pages-new/staking/StakingLayout";
// Layouts
import { LegacyStakingLayout } from "./layouts/LegacyStakingLayout";
// Types
import { AppContextIface, Theme } from "types";
import Smooth from "components/Smooth";
import { PwaPermissionsAlert, PwaPermissionsModal } from "components/PwaPermissions";
import { LocalProxyBanner } from "pages/wifi/components/localProxying/LocalProxyBanner";
import { AppContextIface } from "types";
// Grafana Faro for frontend monitoring and tracing
import { FaroRoutes } from "@grafana/faro-react";
import { useUiTelemetryConsent } from "hooks/useUiTelemetryConsent";
Expand All @@ -28,28 +34,6 @@ export const AppContext = React.createContext<AppContextIface>({
toggleTheme: () => {}
});

const useLocalStorage = <T extends string>(
key: string,
initialValue: T
): [T, React.Dispatch<React.SetStateAction<T>>] => {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
// Assert that either the item or initialValue is of type T
return (item as T) || initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});

useEffect(() => {
window.localStorage.setItem(key, storedValue);
}, [key, storedValue]);

return [storedValue, setStoredValue];
};

function MainApp({ username }: { username: string }) {
// App is the parent container of any other component.
// If this re-renders, the whole app will. So DON'T RERENDER APP!
Expand All @@ -59,7 +43,11 @@ function MainApp({ username }: { username: string }) {
useUiTelemetryConsent();

const [screenWidth, setScreenWidth] = useState(window.innerWidth);
const [theme, setTheme] = useLocalStorage<Theme>("theme", "light");
const { theme, setTheme } = useTheme();

// Resolve "system" to actual light/dark for legacy code that expects a binary value
const resolvedTheme =
theme === "system" ? (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light") : theme;

useEffect(() => {
const handleResize = () => setScreenWidth(window.innerWidth);
Expand All @@ -73,35 +61,38 @@ function MainApp({ username }: { username: string }) {
window.scrollTo(0, 0);
}, [screenLocation.pathname]);

// Legacy AppContext — derives from the ThemeProvider
const appContext: AppContextIface = {
theme,
toggleTheme: () => setTheme((curr: Theme) => (curr === "light" ? "dark" : "light"))
theme: resolvedTheme,
toggleTheme: () => setTheme(resolvedTheme === "light" ? "dark" : "light")
};

// Keep <body> class in sync for legacy CSS that targets body.dark / body.light
useEffect(() => {
const html = document.documentElement;
const body = document.body;

html.classList.remove("light", "dark");
body.classList.remove("light", "dark");

html.classList.add(theme);
body.classList.add(theme);
}, [theme]);
body.classList.add(resolvedTheme);
}, [resolvedTheme]);

return (
<AppContext.Provider value={appContext}>
<div className="body" id={theme}>
<SideBar screenWidth={screenWidth} />
<TopBar username={username} appContext={appContext} />
<div id="main">
<ErrorBoundary>
<LocalProxyBanner />
<NotificationsMain />
</ErrorBoundary>
<PwaPermissionsAlert />
<FaroRoutes>
{/** Provide the app context only to the dashboard (where the modules switch is handled) */}
<div className="body" id={resolvedTheme}>
<FaroRoutes>
{/* New UI routes — Tailwind + shadcn, no legacy chrome */}
<Route
path="/ai/*"
element={
<ErrorBoundary>
<AiLayout />
</ErrorBoundary>
}
/>

{/* Legacy routes — Bootstrap + SCSS, with sidebar/topbar/legacy chrome */}
<Route
path="/legacy"
element={<LegacyStakingLayout screenWidth={screenWidth} username={username} appContext={appContext} />}
>
{Object.values(pages).map(({ RootComponent, rootPath }) => (
<Route
key={rootPath}
Expand All @@ -113,36 +104,44 @@ function MainApp({ username }: { username: string }) {
}
/>
))}
{/* Redirection for routes with hashes */}
{/* 404 routes redirect to dashboard or default page */}
<Route path="*" element={<DefaultRedirect />} />
</FaroRoutes>
</div>

{/* Place here non-page components */}
<Welcome />
<Smooth />
<PwaPermissionsModal />
<ToastContainer />
{/* Default: redirect /legacy to /legacy/dashboard */}
<Route index element={<Navigate to="dashboard" replace />} />
</Route>

{/* Staking section — new Tailwind + shadcn pages */}
<Route
path="/staking/*"
element={
<ErrorBoundary>
<StakingLayout />
</ErrorBoundary>
}
/>

{/* Home section — catch-all for / and /info, /settings etc. */}
<Route
path="/*"
element={
<ErrorBoundary>
<HomeLayout />
</ErrorBoundary>
}
/>
</FaroRoutes>
</div>
</AppContext.Provider>
);
}

function DefaultRedirect() {
const navigate = useNavigate();
const location = useLocation();

useEffect(() => {
if (location.pathname === "/") {
navigate("/dashboard", { replace: true });
}
}, [location, navigate]);

return null;
export default function App() {
return (
<ThemeProvider defaultTheme="system" storageKey="dappnode-ui-theme">
<AppInner />
</ThemeProvider>
);
}

export default function App() {
function AppInner() {
const [loginStatus, setLoginStatus] = useState<LoginStatus>();
// Handles the login, register and connecting logic. Nothing else will render
// Until the app has been logged in
Expand Down Expand Up @@ -188,12 +187,16 @@ export default function App() {
case "logged-in":
return <MainApp username={loginStatus.username} />;
case "not-logged-in":
return <Login refetchStatus={onFetchLoginStatus} />;
// return <Login refetchStatus={onFetchLoginStatus} />;
return <LoginPage refetchStatus={onFetchLoginStatus} />;
case "not-registered":
return <Register refetchStatus={onFetchLoginStatus} />;
// return <Register refetchStatus={onFetchLoginStatus} />;
return <RegisterPage refetchStatus={onFetchLoginStatus} />;
case "error":
return <NoConnection error={loginStatus.error} />;
// return <NoConnection error={loginStatus.error} />;
return <NoConnectionPage error={loginStatus.error} />;
default:
return <NoConnection />;
// return <NoConnection />;
return <NoConnectionPage />;
}
}
2 changes: 1 addition & 1 deletion packages/admin-ui/src/components/ConfirmDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function Modal({

return (
<div
className="confirm-dialog-root"
className="confirm-dialog-root legacy-bootstrap"
ref={modalEl}
onClick={(e) => {
if (modalEl.current === e.target) onClose();
Expand Down
Loading
Loading