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
5 changes: 5 additions & 0 deletions .changeset/dirty-plants-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"trackio": patch
---

feat:Add a settings/CLI page to Trackio
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ dist/
build/
tf_test_run/
test.py
screenshots/
40 changes: 40 additions & 0 deletions tests/ui/test_settings_theme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from playwright.sync_api import expect, sync_playwright

import trackio


def test_settings_theme_switching_and_persistence(temp_dir):
trackio.init(project="test_theme", name="theme_run")
trackio.log(metrics={"loss": 0.5})
trackio.finish()

app, url, _, _ = trackio.show(block_thread=False, open_browser=False)

try:
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.set_default_timeout(5000)
base_url = url if url.endswith("/") else url + "/"
page.goto(base_url)
page.wait_for_load_state("networkidle")

page.get_by_role("button", name="Settings", exact=True).click()
page.wait_for_load_state("networkidle")
expect(page.locator(".settings-page")).to_be_visible()

page.get_by_role("button", name="Dark", exact=True).click()
assert page.locator("html").get_attribute("data-theme") == "dark"

page.get_by_role("button", name="Light", exact=True).click()
assert page.locator("html").get_attribute("data-theme") is None

page.get_by_role("button", name="Dark", exact=True).click()
page.goto(base_url)
page.wait_for_load_state("networkidle")
assert page.locator("html").get_attribute("data-theme") == "dark"

browser.close()
finally:
trackio.delete_project("test_theme", force=True)
app.close()
6 changes: 3 additions & 3 deletions tests/ui/test_ui_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def test_runs_plots_images_are_displayed(temp_dir):
page.goto(url if url.endswith("/") else url + "/")
page.wait_for_load_state("networkidle")
nav_links = page.locator(".nav-link")
expect(nav_links).to_have_count(6)
expect(nav_links).to_have_count(7)

run_label = page.locator(".run-name", has_text="test_run")
expect(run_label).to_be_visible()
Expand Down Expand Up @@ -106,7 +106,7 @@ def test_navbar_page_navigation(temp_dir):
page.goto(url if url.endswith("/") else url + "/")
page.wait_for_load_state("networkidle")
nav_links = page.locator(".nav-link")
expect(nav_links).to_have_count(6)
expect(nav_links).to_have_count(7)

expect(page.locator(".metrics-page")).to_be_visible()

Expand Down Expand Up @@ -145,7 +145,7 @@ def test_runs_table_shows_run_data(temp_dir):
page.wait_for_load_state("networkidle")

nav_links = page.locator(".nav-link")
expect(nav_links).to_have_count(6)
expect(nav_links).to_have_count(7)
page.get_by_role("button", name="Runs", exact=True).click()
page.wait_for_load_state("networkidle")

Expand Down
13 changes: 11 additions & 2 deletions trackio/frontend/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@
} from "./lib/api.js";
import { setColorPalette } from "./lib/stores.js";
import { getPageFromPath, navigateTo, getQueryParam } from "./lib/router.js";
import { applyTheme, detectSystemTheme } from "./lib/theme.js";
import Settings from "./pages/Settings.svelte";
import { initTheme, isDark, onThemeChange } from "./lib/theme.js";

applyTheme(getQueryParam("__theme") || detectSystemTheme());
initTheme();

let darkMode = $state(isDark());
onThemeChange((dark) => { darkMode = dark; });

let currentPage = $state("metrics");
let projects = $state([]);
Expand Down Expand Up @@ -56,6 +60,7 @@
let plotOrder = $state([]);
let tableTruncateLength = $state(250);
let readOnlySource = $state(null);
let spaceId = $state(null);

function handleNavigate(page) {
currentPage = page;
Expand Down Expand Up @@ -256,6 +261,7 @@
if (settings.plot_order) plotOrder = settings.plot_order;
if (settings.table_truncate_length) tableTruncateLength = settings.table_truncate_length;
if (settings.media_dir) setMediaDir(settings.media_dir);
if (settings.space_id) spaceId = settings.space_id;
}
} catch {
// settings endpoint may not be available
Expand Down Expand Up @@ -333,6 +339,7 @@
bind:filterText
{metricColumns}
{logoUrls}
{darkMode}
/>
{/if}

Expand Down Expand Up @@ -377,6 +384,8 @@
<RunDetail project={selectedProject} />
{:else if currentPage === "files"}
<Files project={selectedProject} />
{:else if currentPage === "settings"}
<Settings {spaceId} selectedProject={selectedProject} {projects} />
{/if}
</div>
</div>
Expand Down
26 changes: 24 additions & 2 deletions trackio/frontend/src/components/Navbar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@
{link.label}
</button>
{/each}
<button
class="nav-link settings-btn"
class:active={currentPage === "settings"}
onclick={() => handleClick("settings")}
title="CLI & Settings"
>
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
<circle cx="12" cy="12" r="3"/>
</svg>
<span>Settings</span>
</button>
</div>
</nav>

Expand All @@ -41,12 +53,14 @@
min-height: 44px;
}
.nav-spacer {
flex: 1;
flex: 1 1 0;
min-width: 0;
}
.nav-tabs {
display: flex;
gap: 0;
padding-right: 16px;
flex-shrink: 0;
padding-right: 8px;
}
.nav-link {
padding: 10px 16px;
Expand All @@ -68,4 +82,12 @@
border-bottom-color: var(--body-text-color, #1f2937);
font-weight: 500;
}
.settings-btn {
display: flex;
align-items: center;
gap: 6px;
}
.settings-btn svg {
flex-shrink: 0;
}
</style>
4 changes: 2 additions & 2 deletions trackio/frontend/src/components/Sidebar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import GradioSlider from "./GradioSlider.svelte";
import GradioTextbox from "./GradioTextbox.svelte";
import { buildColorMap, getColorForIndex } from "../lib/stores.js";
import { isDark } from "../lib/theme.js";

let {
open = $bindable(true),
Expand All @@ -31,6 +30,7 @@
readOnlySource = null,
projectLocked = false,
logoUrls = { light: "/static/trackio/trackio_logo_type_light_transparent.png", dark: "/static/trackio/trackio_logo_type_dark_transparent.png" },
darkMode = false,
} = $props();

let navTick = $state(0);
Expand Down Expand Up @@ -105,7 +105,7 @@
<div class="sidebar-scroll">
<div class="logo-section">
<img
src={isDark() ? logoUrls.dark : logoUrls.light}
src={darkMode ? logoUrls.dark : logoUrls.light}
alt="Trackio"
class="logo"
/>
Expand Down
3 changes: 3 additions & 0 deletions trackio/frontend/src/lib/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export function getPageFromPath() {
return "run-detail";
case "files":
return "files";
case "settings":
return "settings";
default:
return "metrics";
}
Expand All @@ -33,6 +35,7 @@ export function navigateTo(page) {
runs: "/runs",
"run-detail": "/run",
files: "/files",
settings: "/settings",
};
const path = pathMap[page] || "/";
const search = params.toString();
Expand Down
67 changes: 67 additions & 0 deletions trackio/frontend/src/lib/theme.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
const THEME_KEY = "trackio_theme_preference";

let _listeners = [];

export function onThemeChange(fn) {
_listeners.push(fn);
return () => {
_listeners = _listeners.filter((f) => f !== fn);
};
}

function _notify() {
const dark = isDark();
_listeners.forEach((fn) => fn(dark));
}

const darkOverrides = {
"--neutral-50": "#fafafa",
"--neutral-100": "#f4f4f5",
Expand Down Expand Up @@ -65,6 +81,7 @@ export function applyTheme(themeName) {
root.style.removeProperty(key);
});
}
_notify();
}

export function isDark() {
Expand All @@ -80,3 +97,53 @@ export function detectSystemTheme() {
}
return "default";
}

export function getThemePreference() {
return localStorage.getItem(THEME_KEY) || "system";
}

export function setThemePreference(pref) {
localStorage.setItem(THEME_KEY, pref);
applyThemeFromPreference(pref);
_ensureSystemListener(pref === "system");
}

export function applyThemeFromPreference(pref) {
if (pref === "system") {
applyTheme(detectSystemTheme());
} else if (pref === "dark") {
applyTheme("dark");
} else {
applyTheme("default");
}
}

let _systemListenerAttached = false;

function _onSystemThemeChange() {
if (getThemePreference() === "system") {
applyTheme(detectSystemTheme());
}
}

function _ensureSystemListener(needed) {
const mq = window.matchMedia("(prefers-color-scheme: dark)");
if (needed && !_systemListenerAttached) {
mq.addEventListener("change", _onSystemThemeChange);
_systemListenerAttached = true;
} else if (!needed && _systemListenerAttached) {
mq.removeEventListener("change", _onSystemThemeChange);
_systemListenerAttached = false;
}
}

export function initTheme() {
const urlTheme = new URLSearchParams(window.location.search).get("__theme");
if (urlTheme) {
applyTheme(urlTheme);
return;
}
const pref = getThemePreference();
applyThemeFromPreference(pref);
_ensureSystemListener(pref === "system");
}
Loading
Loading