Skip to content

Commit d199486

Browse files
committed
refactor: update routing and terminology for metrics and apps
- Replaced the MetricsPage with a new structure under the metrics route, introducing MetricsAppsPage, MetricsAppDeploymentsPage, and MetricsDeploymentDetailPage. - Updated the Breadcrumbs and Sidebar components to reflect the change from "Apps" to "Releases" for better clarity. - Adjusted the AppsPage and TeamOverviewPage to use "Releases" instead of "Apps" in titles and labels. - Added new entries to .gitignore for local development files related to the full-stack environment.
1 parent 7b59ba0 commit d199486

15 files changed

Lines changed: 1027 additions & 302 deletions

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,12 @@ scripts/dev/dashboard-preview.sh
5252
web-dashboard/.env.local
5353
web-dashboard/src/styles/app.local.css
5454

55+
# Local full-stack dev overlay (mock GitHub + dashboard on :5173)
56+
docker-compose.dev.local.yml
57+
deploy/dev/
58+
scripts/dev/up.sh
59+
scripts/dev/mock-github.ts
60+
infra/pulumi/package.json
61+
5562
# CLI global-install packing dir
5663
.tmp-pack/

web-dashboard/src/components/shell/Breadcrumbs.tsx

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export function Breadcrumbs() {
7272

7373
/** Static labels for team-scoped leaf segments. */
7474
const TEAM_LEAF_LABELS = new Map<string, string>([
75-
["apps", "Apps"],
75+
["apps", "Releases"],
7676
["members", "Members"],
7777
["invitations", "Invitations"],
7878
["metrics", "Metrics"],
@@ -87,6 +87,42 @@ function buildCrumbs(
8787
const segments = pathname.split("/").filter((segment) => segment.length > 0);
8888

8989
if (teamId !== undefined) {
90+
const metricsIndex = segments.indexOf("metrics");
91+
if (metricsIndex !== -1) {
92+
const routeAppId = segments[metricsIndex + 2];
93+
const routeDepId = segments[metricsIndex + 4];
94+
if (segments[metricsIndex + 1] !== "apps" || routeAppId === undefined) {
95+
return [];
96+
}
97+
98+
const crumbs: Crumb[] = [
99+
{
100+
key: "metrics",
101+
to: `/teams/${teamId}/metrics`,
102+
node: "Metrics",
103+
},
104+
{
105+
key: "app",
106+
to: `/teams/${teamId}/metrics/apps/${routeAppId}`,
107+
node: <AppName appId={routeAppId} />,
108+
},
109+
];
110+
111+
if (
112+
segments[metricsIndex + 3] === "deployments" &&
113+
routeDepId !== undefined
114+
) {
115+
crumbs.push({
116+
key: "deployment",
117+
node: (
118+
<DeploymentName appId={routeAppId} deploymentId={routeDepId} />
119+
),
120+
});
121+
}
122+
123+
return crumbs;
124+
}
125+
90126
// Single-team OSS: the team root crumb is just the fixed `default-team`
91127
// slug, so omit it; show it only in multi-team mode where it disambiguates.
92128
const crumbs: Crumb[] = isMultiTeam
@@ -101,7 +137,7 @@ function buildCrumbs(
101137

102138
if (appId !== undefined) {
103139
crumbs.push(
104-
{ key: "apps", to: `/teams/${teamId}/apps`, node: "Apps" },
140+
{ key: "apps", to: `/teams/${teamId}/apps`, node: "Releases" },
105141
{
106142
key: "app",
107143
to: `/teams/${teamId}/apps/${appId}`,

web-dashboard/src/components/shell/Sidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Team-scoped sidebar: brand, main nav (Apps, Metrics) and the IAM "Access"
1+
// Team-scoped sidebar: brand, main nav (Releases, Metrics) and the IAM "Access"
22
// group (Members, Invitations), all as data-driven react-router NavLinks
33
// against the route map — the DOM/class structure is ported, its hardcoded
44
// `.html` hrefs and global `DB` are not. Per the RBAC matrix the Access group
@@ -127,7 +127,7 @@ interface TeamNavItem {
127127
const MAIN_NAV_ITEMS: readonly TeamNavItem[] = [
128128
{
129129
key: "apps",
130-
label: "Apps",
130+
label: "Releases",
131131
segment: "apps",
132132
icon: (
133133
<NavIcon>

web-dashboard/src/lib/appTile.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const APP_ICON_GRADIENTS = [
2+
"linear-gradient(135deg,#0051ff,#008bf7)",
3+
"linear-gradient(135deg,#00ceff,#008bf7)",
4+
"linear-gradient(135deg,#fe19ff,#b517d6)",
5+
"linear-gradient(135deg,#ff9100,#ff4d13)",
6+
"linear-gradient(135deg,#0031ea,#0051ff)",
7+
"linear-gradient(135deg,#008bf7,#00ceff)",
8+
"linear-gradient(135deg,#10b981,#059669)",
9+
"linear-gradient(135deg,#ff4d13,#ec0c43)",
10+
] as const;
11+
12+
/** Stable gradient pick so an app keeps its tile color across refetches. */
13+
export function gradientFor(appId: string): string {
14+
let hash = 0;
15+
for (let index = 0; index < appId.length; index += 1) {
16+
hash = (hash * 31 + appId.charCodeAt(index)) >>> 0;
17+
}
18+
return APP_ICON_GRADIENTS[hash % APP_ICON_GRADIENTS.length];
19+
}
20+
21+
/** Tile initials: "harbor-android" → "ha", single words → first two. */
22+
export function initialsFor(appName: string): string {
23+
const segments = appName
24+
.split(/[^\p{L}\p{N}]+/u)
25+
.filter((segment) => segment.length > 0);
26+
const first = segments[0] ?? appName;
27+
const second = segments[1];
28+
const initials =
29+
second !== undefined
30+
? `${first.charAt(0)}${second.charAt(0)}`
31+
: first.slice(0, 2);
32+
return initials === "" ? "?" : initials.toLowerCase();
33+
}

web-dashboard/src/pages/AppsPage.tsx

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { useTeamRole } from "../rbac/useTeamRole";
2828
import { buildReleaseReactSnippet } from "../lib/cliSnippet";
2929
import { formatDate } from "../model/format";
3030
import type { App } from "../model/app";
31+
import { gradientFor, initialsFor } from "../lib/appTile";
3132
import type { Deployment } from "../model/deployment";
3233
import type { FormEvent } from "react";
3334
import { buttonVariants } from "../components/ui/Button";
@@ -76,7 +77,7 @@ export function AppsPage() {
7677
<div className="mb-6 flex flex-wrap items-start gap-[18px]">
7778
<div className="min-w-0 flex-1">
7879
<h1 className={PAGE_TITLE}>
79-
Apps
80+
Releases
8081
</h1>
8182
<p className={PAGE_SUB}>
8283
React Native OTA targets in this team. Each app auto-creates a
@@ -551,41 +552,6 @@ function chipToneFor(deploymentName: string): ChipTone {
551552
return "blue";
552553
}
553554

554-
// Identicon gradients — decorative only; the initials are aria-hidden.
555-
const APP_ICON_GRADIENTS = [
556-
"linear-gradient(135deg,#0051ff,#008bf7)",
557-
"linear-gradient(135deg,#00ceff,#008bf7)",
558-
"linear-gradient(135deg,#fe19ff,#b517d6)",
559-
"linear-gradient(135deg,#ff9100,#ff4d13)",
560-
"linear-gradient(135deg,#0031ea,#0051ff)",
561-
"linear-gradient(135deg,#008bf7,#00ceff)",
562-
"linear-gradient(135deg,#10b981,#059669)",
563-
"linear-gradient(135deg,#ff4d13,#ec0c43)",
564-
] as const;
565-
566-
/** Stable gradient pick so an app keeps its tile color across refetches. */
567-
function gradientFor(appId: string): string {
568-
let hash = 0;
569-
for (let index = 0; index < appId.length; index += 1) {
570-
hash = (hash * 31 + appId.charCodeAt(index)) >>> 0;
571-
}
572-
return APP_ICON_GRADIENTS[hash % APP_ICON_GRADIENTS.length];
573-
}
574-
575-
/** Tile initials: "harbor-android" → "ha", single words → first two. */
576-
function initialsFor(appName: string): string {
577-
const segments = appName
578-
.split(/[^\p{L}\p{N}]+/u)
579-
.filter((segment) => segment.length > 0);
580-
const first = segments[0] ?? appName;
581-
const second = segments[1];
582-
const initials =
583-
second !== undefined
584-
? `${first.charAt(0)}${second.charAt(0)}`
585-
: first.slice(0, 2);
586-
return initials === "" ? "?" : initials.toLowerCase();
587-
}
588-
589555
// --- Icons (paths mirror the shared icon set) --------------------------------
590556

591557
function PlusIcon() {

web-dashboard/src/pages/TeamOverviewPage.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ function TeamOverview({ teamId }: { teamId: string }) {
117117
icon={<AppsIcon />}
118118
iconBackground="var(--color-blue-tint)"
119119
iconColor="var(--color-blue)"
120-
title="Apps"
120+
title="Releases"
121121
subtitle="Manage apps & deployments"
122122
/>
123123
{!membersHidden ? (
@@ -157,13 +157,13 @@ function AppsTile({ teamId }: { teamId: string }) {
157157
<CountTile
158158
accent="var(--color-blue)"
159159
icon={<AppsIcon />}
160-
label="Apps"
160+
label="Releases"
161161
count={appsQuery.data?.length}
162162
isPending={appsQuery.isPending}
163163
isError={appsQuery.isError}
164164
onRetry={() => void appsQuery.refetch()}
165165
to={`/teams/${teamId}/apps`}
166-
linkText="View apps"
166+
linkText="View releases"
167167
/>
168168
);
169169
}

0 commit comments

Comments
 (0)