mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-16 16:01:05 +00:00
Finalize Foundry sync flow
This commit is contained in:
parent
5c70cbcd23
commit
1c852cc5f8
14 changed files with 768 additions and 187 deletions
|
|
@ -51,6 +51,17 @@ function labelStyle(color: string) {
|
|||
};
|
||||
}
|
||||
|
||||
function mergedRouteParams(matches: Array<{ params: Record<string, unknown> }>): Record<string, string> {
|
||||
return matches.reduce<Record<string, string>>((acc, match) => {
|
||||
for (const [key, value] of Object.entries(match.params)) {
|
||||
if (typeof value === "string" && value.length > 0) {
|
||||
acc[key] = value;
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export function DevPanel() {
|
||||
if (!import.meta.env.DEV) {
|
||||
return null;
|
||||
|
|
@ -62,7 +73,12 @@ export function DevPanel() {
|
|||
const user = activeMockUser(snapshot);
|
||||
const organizations = eligibleOrganizations(snapshot);
|
||||
const t = useFoundryTokens();
|
||||
const location = useRouterState({ select: (state) => state.location });
|
||||
const routeContext = useRouterState({
|
||||
select: (state) => ({
|
||||
location: state.location,
|
||||
params: mergedRouteParams(state.matches as Array<{ params: Record<string, unknown> }>),
|
||||
}),
|
||||
});
|
||||
const [visible, setVisible] = useState<boolean>(() => readStoredVisibility());
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -84,8 +100,19 @@ export function DevPanel() {
|
|||
}, []);
|
||||
|
||||
const modeLabel = isMockFrontendClient ? "Mock" : "Live";
|
||||
const github = organization?.github ?? null;
|
||||
const runtime = organization?.runtime ?? null;
|
||||
const selectedWorkspaceId = routeContext.params.workspaceId ?? null;
|
||||
const selectedTaskId = routeContext.params.taskId ?? null;
|
||||
const selectedRepoId = routeContext.params.repoId ?? null;
|
||||
const selectedSessionId =
|
||||
routeContext.location.search && typeof routeContext.location.search === "object" && "sessionId" in routeContext.location.search
|
||||
? (((routeContext.location.search as Record<string, unknown>).sessionId as string | undefined) ?? null)
|
||||
: null;
|
||||
const contextOrganization =
|
||||
(routeContext.params.organizationId ? (snapshot.organizations.find((candidate) => candidate.id === routeContext.params.organizationId) ?? null) : null) ??
|
||||
(selectedWorkspaceId ? (snapshot.organizations.find((candidate) => candidate.workspaceId === selectedWorkspaceId) ?? null) : null) ??
|
||||
organization;
|
||||
const github = contextOrganization?.github ?? null;
|
||||
const runtime = contextOrganization?.runtime ?? null;
|
||||
const runtimeSummary = useMemo(() => {
|
||||
if (!runtime || runtime.errorCount === 0) {
|
||||
return "No actor errors";
|
||||
|
|
@ -122,16 +149,31 @@ export function DevPanel() {
|
|||
alignItems: "center",
|
||||
gap: "8px",
|
||||
border: `1px solid ${t.borderDefault}`,
|
||||
background: t.surfacePrimary,
|
||||
background: "rgba(9, 9, 11, 0.78)",
|
||||
color: t.textPrimary,
|
||||
borderRadius: "999px",
|
||||
padding: "10px 14px",
|
||||
boxShadow: "0 18px 40px rgba(0, 0, 0, 0.28)",
|
||||
padding: "9px 12px",
|
||||
boxShadow: "0 18px 40px rgba(0, 0, 0, 0.22)",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<Bug size={14} />
|
||||
Dev
|
||||
<span style={{ display: "inline-flex", alignItems: "center", gap: "8px", fontSize: "12px", lineHeight: 1 }}>
|
||||
<span style={{ color: t.textSecondary }}>Show Dev Panel</span>
|
||||
<span
|
||||
style={{
|
||||
padding: "4px 7px",
|
||||
borderRadius: "999px",
|
||||
border: `1px solid ${t.borderDefault}`,
|
||||
background: "rgba(255, 255, 255, 0.04)",
|
||||
fontSize: "11px",
|
||||
fontWeight: 700,
|
||||
letterSpacing: "0.03em",
|
||||
}}
|
||||
>
|
||||
Shift+D
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
@ -181,7 +223,7 @@ export function DevPanel() {
|
|||
{modeLabel}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ fontSize: "11px", color: t.textMuted }}>{location.pathname}</div>
|
||||
<div style={{ fontSize: "11px", color: t.textMuted }}>{routeContext.location.pathname}</div>
|
||||
</div>
|
||||
<button type="button" onClick={() => setVisible(false)} style={pillButtonStyle()}>
|
||||
Hide
|
||||
|
|
@ -189,12 +231,23 @@ export function DevPanel() {
|
|||
</div>
|
||||
|
||||
<div style={{ display: "grid", gap: "12px", padding: "14px" }}>
|
||||
<div style={sectionStyle(t.borderSubtle, t.surfaceSecondary)}>
|
||||
<div style={labelStyle(t.textMuted)}>Context</div>
|
||||
<div style={{ display: "grid", gap: "4px", fontSize: "12px" }}>
|
||||
<div>Organization: {contextOrganization?.settings.displayName ?? "None selected"}</div>
|
||||
<div>Workspace: {selectedWorkspaceId ?? "None selected"}</div>
|
||||
<div>Task: {selectedTaskId ?? "None selected"}</div>
|
||||
<div>Repo: {selectedRepoId ?? "None selected"}</div>
|
||||
<div>Session: {selectedSessionId ?? "None selected"}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={sectionStyle(t.borderSubtle, t.surfaceSecondary)}>
|
||||
<div style={labelStyle(t.textMuted)}>Session</div>
|
||||
<div style={{ display: "grid", gap: "4px", fontSize: "12px" }}>
|
||||
<div>Auth: {snapshot.auth.status}</div>
|
||||
<div>User: {user ? `${user.name} (@${user.githubLogin})` : "None"}</div>
|
||||
<div>Organization: {organization?.settings.displayName ?? "None selected"}</div>
|
||||
<div>Active org: {organization?.settings.displayName ?? "None selected"}</div>
|
||||
</div>
|
||||
{isMockFrontendClient ? (
|
||||
<div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
|
||||
|
|
@ -221,26 +274,26 @@ export function DevPanel() {
|
|||
<div>Repos: {github?.importedRepoCount ?? 0}</div>
|
||||
<div>Last sync: {github?.lastSyncLabel ?? "n/a"}</div>
|
||||
</div>
|
||||
{organization ? (
|
||||
{contextOrganization ? (
|
||||
<div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
|
||||
<button type="button" onClick={() => void client.triggerGithubSync(organization.id)} style={pillButtonStyle()}>
|
||||
<button type="button" onClick={() => void client.triggerGithubSync(contextOrganization.id)} style={pillButtonStyle()}>
|
||||
<RefreshCw size={12} style={{ marginRight: "6px", verticalAlign: "text-bottom" }} />
|
||||
Sync
|
||||
</button>
|
||||
<button type="button" onClick={() => void client.reconnectGithub(organization.id)} style={pillButtonStyle()}>
|
||||
<button type="button" onClick={() => void client.reconnectGithub(contextOrganization.id)} style={pillButtonStyle()}>
|
||||
<Wifi size={12} style={{ marginRight: "6px", verticalAlign: "text-bottom" }} />
|
||||
Reconnect
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
{isMockFrontendClient && organization && client.setMockDebugOrganizationState ? (
|
||||
{isMockFrontendClient && contextOrganization && client.setMockDebugOrganizationState ? (
|
||||
<div style={{ display: "grid", gap: "8px" }}>
|
||||
<div style={{ display: "flex", gap: "6px", flexWrap: "wrap" }}>
|
||||
{(["pending", "syncing", "synced", "error"] as const).map((status) => (
|
||||
<button
|
||||
key={status}
|
||||
type="button"
|
||||
onClick={() => void client.setMockDebugOrganizationState?.({ organizationId: organization.id, githubSyncStatus: status })}
|
||||
onClick={() => void client.setMockDebugOrganizationState?.({ organizationId: contextOrganization.id, githubSyncStatus: status })}
|
||||
style={pillButtonStyle(github?.syncStatus === status)}
|
||||
>
|
||||
{status}
|
||||
|
|
@ -252,7 +305,7 @@ export function DevPanel() {
|
|||
<button
|
||||
key={status}
|
||||
type="button"
|
||||
onClick={() => void client.setMockDebugOrganizationState?.({ organizationId: organization.id, githubInstallationStatus: status })}
|
||||
onClick={() => void client.setMockDebugOrganizationState?.({ organizationId: contextOrganization.id, githubInstallationStatus: status })}
|
||||
style={pillButtonStyle(github?.installationStatus === status)}
|
||||
>
|
||||
{status}
|
||||
|
|
@ -270,13 +323,13 @@ export function DevPanel() {
|
|||
<div>{runtimeSummary}</div>
|
||||
{runtime?.issues[0] ? <div>Latest: {runtime.issues[0].message}</div> : null}
|
||||
</div>
|
||||
{organization ? (
|
||||
{contextOrganization ? (
|
||||
<div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
|
||||
{isMockFrontendClient && client.setMockDebugOrganizationState ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void client.setMockDebugOrganizationState?.({ organizationId: organization.id, runtimeStatus: "error" })}
|
||||
onClick={() => void client.setMockDebugOrganizationState?.({ organizationId: contextOrganization.id, runtimeStatus: "error" })}
|
||||
style={pillButtonStyle(runtime?.status === "error")}
|
||||
>
|
||||
<ShieldAlert size={12} style={{ marginRight: "6px", verticalAlign: "text-bottom" }} />
|
||||
|
|
@ -284,7 +337,7 @@ export function DevPanel() {
|
|||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void client.setMockDebugOrganizationState?.({ organizationId: organization.id, runtimeStatus: "healthy" })}
|
||||
onClick={() => void client.setMockDebugOrganizationState?.({ organizationId: contextOrganization.id, runtimeStatus: "healthy" })}
|
||||
style={pillButtonStyle(runtime?.status === "healthy")}
|
||||
>
|
||||
Healthy
|
||||
|
|
@ -292,7 +345,7 @@ export function DevPanel() {
|
|||
</>
|
||||
) : null}
|
||||
{runtime?.errorCount ? (
|
||||
<button type="button" onClick={() => void client.clearOrganizationRuntimeIssues(organization.id)} style={pillButtonStyle()}>
|
||||
<button type="button" onClick={() => void client.clearOrganizationRuntimeIssues(contextOrganization.id)} style={pillButtonStyle()}>
|
||||
Clear actor errors
|
||||
</button>
|
||||
) : null}
|
||||
|
|
@ -309,7 +362,7 @@ export function DevPanel() {
|
|||
key={candidate.id}
|
||||
type="button"
|
||||
onClick={() => void client.selectOrganization(candidate.id)}
|
||||
style={pillButtonStyle(organization?.id === candidate.id)}
|
||||
style={pillButtonStyle(contextOrganization?.id === candidate.id)}
|
||||
>
|
||||
{candidate.settings.displayName}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useNavigate } from "@tanstack/react-router";
|
|||
import { useStyletron } from "baseui";
|
||||
import { LabelSmall, LabelXSmall } from "baseui/typography";
|
||||
import {
|
||||
AlertTriangle,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
ChevronUp,
|
||||
|
|
@ -14,6 +15,7 @@ import {
|
|||
LogOut,
|
||||
PanelLeft,
|
||||
Plus,
|
||||
RefreshCw,
|
||||
Settings,
|
||||
User,
|
||||
} from "lucide-react";
|
||||
|
|
@ -21,6 +23,7 @@ import {
|
|||
import { formatRelativeAge, type Task, type ProjectSection } from "./view-model";
|
||||
import { ContextMenuOverlay, TaskIndicator, PanelHeaderBar, SPanel, ScrollBody, useContextMenu } from "./ui";
|
||||
import { activeMockOrganization, eligibleOrganizations, useMockAppClient, useMockAppSnapshot } from "../../lib/mock-app";
|
||||
import { getMockOrganizationStatus } from "../../lib/mock-organization-status";
|
||||
import { useFoundryTokens } from "../../app/theme";
|
||||
import type { FoundryTokens } from "../../styles/tokens";
|
||||
|
||||
|
|
@ -40,6 +43,28 @@ function projectIconColor(label: string): string {
|
|||
return PROJECT_COLORS[Math.abs(hash) % PROJECT_COLORS.length]!;
|
||||
}
|
||||
|
||||
function organizationStatusToneStyles(tokens: FoundryTokens, tone: "info" | "warning" | "error") {
|
||||
if (tone === "error") {
|
||||
return {
|
||||
backgroundColor: "rgba(255, 79, 0, 0.14)",
|
||||
borderColor: "rgba(255, 79, 0, 0.3)",
|
||||
color: "#ffd6c7",
|
||||
};
|
||||
}
|
||||
if (tone === "warning") {
|
||||
return {
|
||||
backgroundColor: "rgba(255, 193, 7, 0.16)",
|
||||
borderColor: "rgba(255, 193, 7, 0.24)",
|
||||
color: "#ffe6a6",
|
||||
};
|
||||
}
|
||||
return {
|
||||
backgroundColor: "rgba(24, 140, 255, 0.16)",
|
||||
borderColor: "rgba(24, 140, 255, 0.24)",
|
||||
color: "#b9d8ff",
|
||||
};
|
||||
}
|
||||
|
||||
export const Sidebar = memo(function Sidebar({
|
||||
projects,
|
||||
newTaskRepos,
|
||||
|
|
@ -694,6 +719,7 @@ function SidebarFooter() {
|
|||
const client = useMockAppClient();
|
||||
const snapshot = useMockAppSnapshot();
|
||||
const organization = activeMockOrganization(snapshot);
|
||||
const organizationStatus = organization ? getMockOrganizationStatus(organization) : null;
|
||||
const [open, setOpen] = useState(false);
|
||||
const [workspaceFlyoutOpen, setWorkspaceFlyoutOpen] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
|
@ -802,6 +828,41 @@ function SidebarFooter() {
|
|||
gap: "2px",
|
||||
});
|
||||
|
||||
const statusChipClass =
|
||||
organizationStatus != null
|
||||
? css({
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: "6px",
|
||||
flexShrink: 0,
|
||||
padding: "4px 7px",
|
||||
borderRadius: "999px",
|
||||
border: `1px solid ${organizationStatusToneStyles(t, organizationStatus.tone).borderColor}`,
|
||||
backgroundColor: organizationStatusToneStyles(t, organizationStatus.tone).backgroundColor,
|
||||
color: organizationStatusToneStyles(t, organizationStatus.tone).color,
|
||||
fontSize: "10px",
|
||||
fontWeight: 600,
|
||||
lineHeight: 1,
|
||||
})
|
||||
: "";
|
||||
|
||||
const footerStatusClass =
|
||||
organizationStatus != null
|
||||
? css({
|
||||
margin: "0 8px 4px",
|
||||
padding: "8px 10px",
|
||||
borderRadius: "10px",
|
||||
border: `1px solid ${organizationStatusToneStyles(t, organizationStatus.tone).borderColor}`,
|
||||
backgroundColor: organizationStatusToneStyles(t, organizationStatus.tone).backgroundColor,
|
||||
color: organizationStatusToneStyles(t, organizationStatus.tone).color,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
fontSize: "11px",
|
||||
lineHeight: 1.3,
|
||||
})
|
||||
: "";
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className={css({ position: "relative", flexShrink: 0 })}>
|
||||
{open ? (
|
||||
|
|
@ -851,6 +912,7 @@ function SidebarFooter() {
|
|||
<span className={css({ flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" })}>
|
||||
{organization.settings.displayName}
|
||||
</span>
|
||||
{organizationStatus ? <span className={statusChipClass}>{organizationStatus.label}</span> : null}
|
||||
<ChevronRight size={12} className={css({ flexShrink: 0, color: t.textMuted })} />
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -919,6 +981,30 @@ function SidebarFooter() {
|
|||
<span className={css({ flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" })}>
|
||||
{org.settings.displayName}
|
||||
</span>
|
||||
{(() => {
|
||||
const orgStatus = getMockOrganizationStatus(org);
|
||||
if (!orgStatus) return null;
|
||||
const tone = organizationStatusToneStyles(t, orgStatus.tone);
|
||||
return (
|
||||
<span
|
||||
className={css({
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
padding: "4px 7px",
|
||||
borderRadius: "999px",
|
||||
border: `1px solid ${tone.borderColor}`,
|
||||
backgroundColor: tone.backgroundColor,
|
||||
color: tone.color,
|
||||
fontSize: "10px",
|
||||
fontWeight: 600,
|
||||
lineHeight: 1,
|
||||
flexShrink: 0,
|
||||
})}
|
||||
>
|
||||
{orgStatus.label}
|
||||
</span>
|
||||
);
|
||||
})()}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
|
@ -949,6 +1035,15 @@ function SidebarFooter() {
|
|||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{organizationStatus ? (
|
||||
<div className={footerStatusClass}>
|
||||
{organizationStatus.key === "syncing" ? <RefreshCw size={12} /> : <AlertTriangle size={12} />}
|
||||
<div className={css({ display: "grid", gap: "2px", minWidth: 0 })}>
|
||||
<div className={css({ fontWeight: 600 })}>{organizationStatus.label}</div>
|
||||
<div className={css({ color: t.textSecondary, fontSize: "10px" })}>{organizationStatus.detail}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={css({ padding: "8px" })}>
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useNavigate } from "@tanstack/react-router";
|
|||
import { ArrowLeft, Clock, CreditCard, FileText, Github, LogOut, Moon, Settings, Sun, Users } from "lucide-react";
|
||||
import { activeMockUser, eligibleOrganizations, useMockAppClient, useMockAppSnapshot } from "../lib/mock-app";
|
||||
import { isMockFrontendClient } from "../lib/env";
|
||||
import { getMockOrganizationStatus } from "../lib/mock-organization-status";
|
||||
import { useColorMode, useFoundryTokens } from "../app/theme";
|
||||
import type { FoundryTokens } from "../styles/tokens";
|
||||
import { appSurfaceStyle, primaryButtonStyle, secondaryButtonStyle, subtleButtonStyle, cardStyle, badgeStyle, inputStyle } from "../styles/shared-styles";
|
||||
|
|
@ -134,6 +135,40 @@ function githubBadge(t: FoundryTokens, organization: FoundryOrganization) {
|
|||
return <span style={badgeStyle(t, t.borderSubtle)}>Install GitHub App</span>;
|
||||
}
|
||||
|
||||
function organizationStatusBadge(t: FoundryTokens, organization: FoundryOrganization) {
|
||||
const status = getMockOrganizationStatus(organization);
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const toneStyles =
|
||||
status.tone === "error"
|
||||
? { background: "rgba(255, 79, 0, 0.18)", color: "#ffd6c7", borderColor: "rgba(255, 79, 0, 0.35)" }
|
||||
: status.tone === "warning"
|
||||
? { background: "rgba(255, 193, 7, 0.18)", color: "#ffe6a6", borderColor: "rgba(255, 193, 7, 0.28)" }
|
||||
: { background: "rgba(24, 140, 255, 0.18)", color: "#b9d8ff", borderColor: "rgba(24, 140, 255, 0.28)" };
|
||||
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: "6px",
|
||||
padding: "4px 8px",
|
||||
borderRadius: "999px",
|
||||
border: `1px solid ${toneStyles.borderColor}`,
|
||||
background: toneStyles.background,
|
||||
color: toneStyles.color,
|
||||
fontSize: "11px",
|
||||
fontWeight: 600,
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
{status.label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function StatCard({ label, value, caption }: { label: string; value: string; caption: string }) {
|
||||
const t = useFoundryTokens();
|
||||
return (
|
||||
|
|
@ -410,7 +445,10 @@ export function MockOrganizationSelectorPage() {
|
|||
|
||||
{/* Info */}
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontSize: "14px", fontWeight: 500, lineHeight: 1.3 }}>{organization.settings.displayName}</div>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" }}>
|
||||
<div style={{ fontSize: "14px", fontWeight: 500, lineHeight: 1.3 }}>{organization.settings.displayName}</div>
|
||||
{organizationStatusBadge(t, organization)}
|
||||
</div>
|
||||
<div style={{ fontSize: "12px", color: t.textTertiary, lineHeight: 1.3, marginTop: "1px" }}>
|
||||
{organization.kind === "personal" ? "Personal" : "Organization"} · {planCatalog[organization.billing.planId]!.label} ·{" "}
|
||||
{organization.members.length} member{organization.members.length !== 1 ? "s" : ""}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
import type { FoundryOrganization } from "@sandbox-agent/foundry-shared";
|
||||
|
||||
export type MockOrganizationStatusTone = "info" | "warning" | "error";
|
||||
|
||||
export interface MockOrganizationStatus {
|
||||
key: "syncing" | "pending" | "sync_error" | "reconnect_required" | "install_required";
|
||||
label: string;
|
||||
detail: string;
|
||||
tone: MockOrganizationStatusTone;
|
||||
}
|
||||
|
||||
export function getMockOrganizationStatus(organization: FoundryOrganization): MockOrganizationStatus | null {
|
||||
if (organization.kind === "personal") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (organization.github.installationStatus === "reconnect_required") {
|
||||
return {
|
||||
key: "reconnect_required",
|
||||
label: "Connection issue",
|
||||
detail: "Reconnect GitHub",
|
||||
tone: "error",
|
||||
};
|
||||
}
|
||||
|
||||
if (organization.github.installationStatus === "install_required") {
|
||||
return {
|
||||
key: "install_required",
|
||||
label: "Link GitHub",
|
||||
detail: "Install GitHub App",
|
||||
tone: "warning",
|
||||
};
|
||||
}
|
||||
|
||||
if (organization.github.syncStatus === "syncing") {
|
||||
return {
|
||||
key: "syncing",
|
||||
label: "Syncing",
|
||||
detail: "Syncing repositories",
|
||||
tone: "info",
|
||||
};
|
||||
}
|
||||
|
||||
if (organization.github.syncStatus === "pending") {
|
||||
return {
|
||||
key: "pending",
|
||||
label: "Needs sync",
|
||||
detail: "Waiting for first sync",
|
||||
tone: "warning",
|
||||
};
|
||||
}
|
||||
|
||||
if (organization.github.syncStatus === "error") {
|
||||
return {
|
||||
key: "sync_error",
|
||||
label: "Sync failed",
|
||||
detail: "Last GitHub sync failed",
|
||||
tone: "error",
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue