Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath-dev"
version = "0.0.58"
version = "0.0.59"
description = "UiPath Developer Console"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
Expand Down
49 changes: 28 additions & 21 deletions src/uipath/dev/server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse, Response
from fastapi.responses import HTMLResponse

from uipath.dev.server import UiPathDeveloperServer

Expand Down Expand Up @@ -108,25 +108,6 @@ def create_app(server: UiPathDeveloperServer) -> FastAPI:
allow_headers=["*"],
)

# Favicon — UiPath orange branded icon
_favicon_svg = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">'
'<rect width="32" height="32" rx="6" fill="#FA4616"/>'
'<text x="16" y="24" font-size="22" text-anchor="middle" fill="#FFFFFF" '
'font-family="system-ui, -apple-system, sans-serif" font-weight="700">U</text>'
"</svg>"
)
_favicon_bytes = _favicon_svg.encode("utf-8")

@app.get("/favicon.ico", include_in_schema=False)
async def _favicon_ico():
return Response(content=_favicon_bytes, media_type="image/svg+xml")

@app.get("/favicon.svg", include_in_schema=False)
async def _favicon_svg_route():
return Response(content=_favicon_bytes, media_type="image/svg+xml")

# Store server reference on app state for route access
app.state.server = server

Expand All @@ -136,10 +117,36 @@ async def _favicon_svg_route():
"no",
)

# Read user's pyproject.toml from CWD (once at startup)
_user_project: dict[str, str | None] = {
"project_name": None,
"project_version": None,
"project_authors": None,
}
_pyproject_path = Path.cwd() / "pyproject.toml"
if _pyproject_path.is_file():
try:
import tomllib

with open(_pyproject_path, "rb") as f:
_pydata = tomllib.load(f)
_proj = _pydata.get("project", {})
_user_project["project_name"] = _proj.get("name")
_user_project["project_version"] = _proj.get("version")
_authors = _proj.get("authors")
if _authors and isinstance(_authors, list) and len(_authors) > 0:
_user_project["project_authors"] = (
_authors[0].get("name")
if isinstance(_authors[0], dict)
else str(_authors[0])
)
except Exception:
pass

# Config endpoint — tells the frontend which features are available
@app.get("/api/config", include_in_schema=False)
async def _config():
return {"auth_enabled": auth_enabled}
return {"auth_enabled": auth_enabled, **_user_project}

# Register routes
from uipath.dev.server.routes.entrypoints import router as entrypoints_router
Expand Down
2 changes: 1 addition & 1 deletion src/uipath/dev/server/frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>UiPath Developer Console</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
</head>
<body>
<div id="root"></div>
Expand Down
Binary file added src/uipath/dev/server/frontend/public/favicon.ico
Binary file not shown.
95 changes: 51 additions & 44 deletions src/uipath/dev/server/frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useRunStore } from "./store/useRunStore";
import { useAuthStore } from "./store/useAuthStore";
import { useConfigStore } from "./store/useConfigStore";
import { useWebSocket } from "./store/useWebSocket";
import { listRuns, listEntrypoints, getRun } from "./api/client";
import type { RunDetail } from "./types/run";
import { useHashRoute } from "./hooks/useHashRoute";
import { useIsMobile } from "./hooks/useIsMobile";
import Sidebar from "./components/layout/Sidebar";
import StatusBar from "./components/layout/StatusBar";
import NewRunPanel from "./components/runs/NewRunPanel";
import SetupView from "./components/runs/SetupView";
import RunDetailsPanel from "./components/runs/RunDetailsPanel";
Expand Down Expand Up @@ -40,15 +42,17 @@ export default function App() {
}
}, [view, routeRunId, selectedRunId, selectRun]);

// Load existing runs, entrypoints, and auth status on mount
// Load existing runs, entrypoints, auth status, and config on mount
const initAuth = useAuthStore((s) => s.init);
const initConfig = useConfigStore((s) => s.init);
useEffect(() => {
listRuns().then(setRuns).catch(console.error);
listEntrypoints()
.then((eps) => setEntrypoints(eps.map((e) => e.name)))
.catch(console.error);
initAuth();
}, [setRuns, setEntrypoints, initAuth]);
initConfig();
}, [setRuns, setEntrypoints, initAuth, initConfig]);

const selectedRun = selectedRunId ? runs[selectedRunId] : null;

Expand Down Expand Up @@ -182,49 +186,52 @@ export default function App() {
};

return (
<div className="flex h-screen w-screen relative">
{/* Mobile hamburger button */}
{isMobile && !sidebarOpen && (
<button
onClick={() => setSidebarOpen(true)}
className="fixed top-2 left-2 z-40 w-9 h-9 flex items-center justify-center rounded-lg cursor-pointer"
style={{ background: "var(--bg-secondary)", border: "1px solid var(--border)" }}
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
<line x1="3" y1="6" x2="21" y2="6" />
<line x1="3" y1="12" x2="21" y2="12" />
<line x1="3" y1="18" x2="21" y2="18" />
</svg>
</button>
)}
<Sidebar
runs={Object.values(runs)}
selectedRunId={selectedRunId}
onSelectRun={handleSelectRun}
onNewRun={handleNewRun}
isMobile={isMobile}
isOpen={sidebarOpen}
onClose={() => setSidebarOpen(false)}
/>
<main className="flex-1 overflow-hidden bg-[var(--bg-primary)]">
{view === "new" ? (
<NewRunPanel />
) : view === "setup" && setupEntrypoint && setupMode ? (
<SetupView
entrypoint={setupEntrypoint}
mode={setupMode}
ws={ws}
onRunCreated={handleRunCreated}
isMobile={isMobile}
/>
) : selectedRun ? (
<RunDetailsPanel run={selectedRun} ws={ws} isMobile={isMobile} />
) : (
<div className="flex items-center justify-center h-full text-[var(--text-muted)]">
Select a run or create a new one
</div>
<div className="flex flex-col h-screen w-screen">
<div className="flex flex-1 overflow-hidden relative">
{/* Mobile hamburger button */}
{isMobile && !sidebarOpen && (
<button
onClick={() => setSidebarOpen(true)}
className="fixed top-2 left-2 z-40 w-9 h-9 flex items-center justify-center rounded-lg cursor-pointer"
style={{ background: "var(--bg-secondary)", border: "1px solid var(--border)" }}
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
<line x1="3" y1="6" x2="21" y2="6" />
<line x1="3" y1="12" x2="21" y2="12" />
<line x1="3" y1="18" x2="21" y2="18" />
</svg>
</button>
)}
</main>
<Sidebar
runs={Object.values(runs)}
selectedRunId={selectedRunId}
onSelectRun={handleSelectRun}
onNewRun={handleNewRun}
isMobile={isMobile}
isOpen={sidebarOpen}
onClose={() => setSidebarOpen(false)}
/>
<main className="flex-1 overflow-hidden bg-[var(--bg-primary)]">
{view === "new" ? (
<NewRunPanel />
) : view === "setup" && setupEntrypoint && setupMode ? (
<SetupView
entrypoint={setupEntrypoint}
mode={setupMode}
ws={ws}
onRunCreated={handleRunCreated}
isMobile={isMobile}
/>
) : selectedRun ? (
<RunDetailsPanel run={selectedRun} ws={ws} isMobile={isMobile} />
) : (
<div className="flex items-center justify-center h-full text-[var(--text-muted)]">
Select a run or create a new one
</div>
)}
</main>
</div>
<StatusBar />
<ReloadToast />
</div>
);
Expand Down
Loading