mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-21 22:52:19 +03:00
feat(home): Add Home page customization options for experienced users (#2531)
Integrated into release/v3.8.2
This commit is contained in:
committed by
GitHub
parent
363ecc4024
commit
c2d4d6755c
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { useState, useEffect, useMemo, useCallback, useRef } from "react";
|
||||
import { useState, useEffect, useMemo, useCallback, useRef, Suspense } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
@@ -13,6 +13,7 @@ import { useNotificationStore } from "@/store/notificationStore";
|
||||
import { copyToClipboard } from "@/shared/utils/clipboard";
|
||||
|
||||
const ProviderTopology = dynamic(() => import("../home/ProviderTopology"), { ssr: false });
|
||||
const ProviderLimits = dynamic(() => import("./usage/components/ProviderLimits"), { ssr: false });
|
||||
import type { NewsAnnouncement } from "@/shared/utils/releaseNotes";
|
||||
|
||||
type UpdateStep = {
|
||||
@@ -95,6 +96,33 @@ export default function HomePageClient({ machineId }: HomePageClientProps) {
|
||||
const [updateSteps, setUpdateSteps] = useState<UpdateStep[]>([]);
|
||||
const [updatePhase, setUpdatePhase] = useState<"idle" | "running" | "done" | "failed">("idle");
|
||||
|
||||
// Appearance settings for home page pinning
|
||||
const [pinProviderQuotaToHome, setPinProviderQuotaToHome] = useState(false);
|
||||
const [showQuickStartOnHome, setShowQuickStartOnHome] = useState(true); // default on
|
||||
const [showProviderTopologyOnHome, setShowProviderTopologyOnHome] = useState(true); // default on
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch the pin settings (lightweight)
|
||||
fetch("/api/settings")
|
||||
.then((r) => (r.ok ? r.json() : {}))
|
||||
.then((data) => {
|
||||
if (data) {
|
||||
if (typeof data.pinProviderQuotaToHome === "boolean") {
|
||||
setPinProviderQuotaToHome(data.pinProviderQuotaToHome);
|
||||
}
|
||||
if (typeof data.showQuickStartOnHome === "boolean") {
|
||||
setShowQuickStartOnHome(data.showQuickStartOnHome);
|
||||
}
|
||||
if (typeof data.showProviderTopologyOnHome === "boolean") {
|
||||
setShowProviderTopologyOnHome(data.showProviderTopologyOnHome);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
/* ignore — defaults stay */
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
setBaseUrl(`${window.location.origin}/v1`);
|
||||
@@ -721,22 +749,30 @@ export default function HomePageClient({ machineId }: HomePageClientProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Quick Start */}
|
||||
<Card>
|
||||
<div className="flex flex-col gap-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">{t("quickStart")}</h2>
|
||||
<p className="text-sm text-text-muted">{t("quickStartDesc")}</p>
|
||||
{/* Pinned Provider Quota Limits (compact, no filters) */}
|
||||
{pinProviderQuotaToHome && (
|
||||
<Suspense fallback={<CardSkeleton />}>
|
||||
<ProviderLimits showFilters={false} />
|
||||
</Suspense>
|
||||
)}
|
||||
|
||||
{/* Quick Start (controlled by Appearance setting, default on) */}
|
||||
{showQuickStartOnHome && (
|
||||
<Card>
|
||||
<div className="flex flex-col gap-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">{t("quickStart")}</h2>
|
||||
<p className="text-sm text-text-muted">{t("quickStartDesc")}</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/docs"
|
||||
className="hidden sm:inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border border-border text-text-muted hover:text-text-main hover:bg-bg-subtle transition-colors"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]">menu_book</span>
|
||||
{t("fullDocs")}
|
||||
</Link>
|
||||
</div>
|
||||
<Link
|
||||
href="/docs"
|
||||
className="hidden sm:inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border border-border text-text-muted hover:text-text-main hover:bg-bg-subtle transition-colors"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]">menu_book</span>
|
||||
{t("fullDocs")}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<ol className="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
|
||||
<li className="rounded-lg border border-border bg-bg-subtle p-4 flex gap-3">
|
||||
@@ -807,34 +843,37 @@ export default function HomePageClient({ machineId }: HomePageClientProps) {
|
||||
</ol>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Provider Topology */}
|
||||
<Card>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold">{t("providerTopology")}</h2>
|
||||
<p className="text-xs text-text-muted">
|
||||
Connected providers routing through OmniRoute in real time
|
||||
</p>
|
||||
{/* Provider Topology (controlled by Appearance setting, default on) */}
|
||||
{showProviderTopologyOnHome && (
|
||||
<Card>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold">{t("providerTopology")}</h2>
|
||||
<p className="text-xs text-text-muted">
|
||||
Connected providers routing through OmniRoute in real time
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-[11px] text-text-muted">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="size-2 rounded-full bg-green-500" /> Active
|
||||
</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="size-2 rounded-full bg-amber-500" /> Recent
|
||||
</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="size-2 rounded-full bg-red-500" /> Error
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-[11px] text-text-muted">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="size-2 rounded-full bg-green-500" /> Active
|
||||
</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="size-2 rounded-full bg-amber-500" /> Recent
|
||||
</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="size-2 rounded-full bg-red-500" /> Error
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ProviderTopology
|
||||
providers={providerStats
|
||||
.filter((p) => p.total > 0)
|
||||
.map((p) => ({ id: p.id, provider: p.id, name: p.provider.name }))}
|
||||
/>
|
||||
</Card>
|
||||
<ProviderTopology
|
||||
providers={providerStats
|
||||
.filter((p) => p.total > 0)
|
||||
.map((p) => ({ id: p.id, provider: p.id, name: p.provider.name }))}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Provider Models Modal */}
|
||||
{selectedProvider && (
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
normalizeHiddenSidebarItems,
|
||||
type HideableSidebarItemId,
|
||||
} from "@/shared/constants/sidebarVisibility";
|
||||
import { PIN_PROVIDER_QUOTA_TO_HOME_KEY } from "@/shared/constants/homeWidgets";
|
||||
|
||||
export default function AppearanceTab() {
|
||||
const { theme, setTheme, isDark } = useTheme();
|
||||
@@ -40,6 +41,9 @@ export default function AppearanceTab() {
|
||||
const showCloudflaredTunnel = settings.hideEndpointCloudflaredTunnel !== true;
|
||||
const showTailscaleFunnel = settings.hideEndpointTailscaleFunnel !== true;
|
||||
const showNgrokTunnel = settings.hideEndpointNgrokTunnel !== true;
|
||||
const pinProviderQuotaToHome = settings[PIN_PROVIDER_QUOTA_TO_HOME_KEY] === true;
|
||||
const showQuickStartOnHome = settings.showQuickStartOnHome !== false; // default on
|
||||
const showProviderTopologyOnHome = settings.showProviderTopologyOnHome !== false; // default on
|
||||
|
||||
const getSettingsLabel = (key: string, fallback: string) =>
|
||||
typeof t.has === "function" && t.has(key) ? t(key) : fallback;
|
||||
@@ -385,6 +389,83 @@ export default function AppearanceTab() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pin Information to Home Page section */}
|
||||
<div className="pt-4 border-t border-border">
|
||||
<div className="mb-3">
|
||||
<p className="font-medium">
|
||||
{getSettingsLabel("pinProviderQuotaToHome", "Pin Information to Home Page")}
|
||||
</p>
|
||||
<p className="text-sm text-text-muted">
|
||||
Choose which sections to pin to the top of the Home page.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Pinned options as a rounded table/list */}
|
||||
<div className="rounded-lg border border-border bg-surface/40 overflow-hidden">
|
||||
<div className="divide-y divide-border/70">
|
||||
{/* Provider Quota Limits */}
|
||||
<div className="flex items-start justify-between px-4 py-3">
|
||||
<div>
|
||||
<p className="font-medium">
|
||||
{getSettingsLabel("providerQuotaLimits", "Provider Quota Limits")}
|
||||
</p>
|
||||
<p className="text-sm text-text-muted">
|
||||
{getSettingsLabel(
|
||||
"providerQuotaLimitsDesc",
|
||||
"Pin the Provider Quota status container (with Refresh All button) to the top of the Home page."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle
|
||||
checked={pinProviderQuotaToHome}
|
||||
onChange={() => updateSetting(PIN_PROVIDER_QUOTA_TO_HOME_KEY, !pinProviderQuotaToHome)}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Quick Start */}
|
||||
<div className="flex items-start justify-between px-4 py-3">
|
||||
<div>
|
||||
<p className="font-medium">
|
||||
{getSettingsLabel("quickStart", "Quick Start")}
|
||||
</p>
|
||||
<p className="text-sm text-text-muted">
|
||||
{getSettingsLabel(
|
||||
"quickStartDesc",
|
||||
"Show the Quick Start panel on the Home page."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle
|
||||
checked={showQuickStartOnHome}
|
||||
onChange={() => updateSetting("showQuickStartOnHome", !showQuickStartOnHome)}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Provider Topology */}
|
||||
<div className="flex items-start justify-between px-4 py-3">
|
||||
<div>
|
||||
<p className="font-medium">
|
||||
{getSettingsLabel("providerTopology", "Provider Topology")}
|
||||
</p>
|
||||
<p className="text-sm text-text-muted">
|
||||
{getSettingsLabel(
|
||||
"providerTopologyDesc",
|
||||
"Show the Provider Topology on the Home page."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle
|
||||
checked={showProviderTopologyOnHome}
|
||||
onChange={() => updateSetting("showProviderTopologyOnHome", !showProviderTopologyOnHome)}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t border-border">
|
||||
<div className="mb-3">
|
||||
<p className="font-medium">{t("sidebarVisibilityToggle")}</p>
|
||||
|
||||
@@ -3886,6 +3886,13 @@
|
||||
"systemTheme": "System Theme",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"pinProviderQuotaToHome": "Pin Information to Home Page",
|
||||
"providerQuotaLimits": "Provider Quota Limits",
|
||||
"providerQuotaLimitsDesc": "Pin the Provider Quota status container (with Refresh All button) to the top of the Home page.",
|
||||
"quickStart": "Quick Start",
|
||||
"quickStartDesc": "Show the Quick Start panel on the Home page.",
|
||||
"providerTopology": "Provider Topology",
|
||||
"providerTopologyDesc": "Show the Provider Topology on the Home page.",
|
||||
"enableCache": "Enable Cache",
|
||||
"cacheTTL": "Cache TTL",
|
||||
"maxCacheSize": "Max Cache Size",
|
||||
|
||||
@@ -595,6 +595,9 @@ export const updateSettingsSchema = z.object({
|
||||
hideEndpointCloudflaredTunnel: z.boolean().optional(),
|
||||
hideEndpointTailscaleFunnel: z.boolean().optional(),
|
||||
hideEndpointNgrokTunnel: z.boolean().optional(),
|
||||
pinProviderQuotaToHome: z.boolean().optional(),
|
||||
showQuickStartOnHome: z.boolean().optional(),
|
||||
showProviderTopologyOnHome: z.boolean().optional(),
|
||||
bruteForceProtection: z.boolean().optional(),
|
||||
hiddenSidebarItems: z.array(z.enum(HIDEABLE_SIDEBAR_ITEM_IDS)).optional(),
|
||||
comboConfigMode: z.enum(COMBO_CONFIG_MODES).optional(),
|
||||
|
||||
@@ -24,6 +24,9 @@ export interface Settings {
|
||||
hideEndpointCloudflaredTunnel?: boolean;
|
||||
hideEndpointTailscaleFunnel?: boolean;
|
||||
hideEndpointNgrokTunnel?: boolean;
|
||||
pinProviderQuotaToHome?: boolean;
|
||||
showQuickStartOnHome?: boolean;
|
||||
showProviderTopologyOnHome?: boolean;
|
||||
hiddenSidebarItems?: HideableSidebarItemId[];
|
||||
resilienceSettings?: ResilienceSettings;
|
||||
// LOCAL_ONLY manage-scope bypass policy (DB-stored, hot-reloaded by
|
||||
|
||||
Reference in New Issue
Block a user