137 lines
5.2 KiB
TypeScript
137 lines
5.2 KiB
TypeScript
import { AuthenticatedLayout } from "./AuthenticatedLayout.tsx";
|
|
|
|
export interface AppCard {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
domain: string;
|
|
role: string;
|
|
}
|
|
|
|
export const AppLaunchpadPage = ({
|
|
apps,
|
|
isAdmin,
|
|
}: {
|
|
apps: AppCard[];
|
|
isAdmin: boolean;
|
|
}) => {
|
|
return (
|
|
<AuthenticatedLayout
|
|
title="Launchpad"
|
|
currentPath="/dashboard"
|
|
isAdmin={isAdmin}
|
|
>
|
|
<div style="margin-bottom: 2rem;">
|
|
<h1 style="font-size: 1.75rem; font-weight: 700; margin: 0 0 0.5rem 0; color: var(--text-primary);">
|
|
Application Launchpad
|
|
</h1>
|
|
<p style="color: var(--text-secondary); margin: 0; font-size: 0.95rem;">
|
|
Single sign-on authorized workloads and microservices.
|
|
</p>
|
|
</div>
|
|
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1.25rem;">
|
|
{apps.length === 0
|
|
? (
|
|
<div
|
|
class="card"
|
|
style="grid-column: 1 / -1; text-align: center; padding: 3rem 1.5rem;"
|
|
>
|
|
<div style="display: inline-flex; width: 48px; height: 48px; background: var(--surface-muted); border-radius: var(--radius-full); align-items: center; justify-content: center; margin-bottom: 1rem; color: var(--text-muted);">
|
|
<svg
|
|
width="24"
|
|
height="24"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<circle cx="12" cy="12" r="10"></circle>
|
|
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
</svg>
|
|
</div>
|
|
<h3 style="margin: 0 0 0.5rem 0; color: var(--text-primary);">
|
|
No Authorized Applications
|
|
</h3>
|
|
<p style="margin: 0; color: var(--text-muted); font-size: 0.9rem;">
|
|
You do not have active role grants for any workloads.
|
|
</p>
|
|
</div>
|
|
)
|
|
: (
|
|
apps.map((app) => {
|
|
const isAdminRole = app.role === "Admin" ||
|
|
app.role === "Global Admin";
|
|
const targetUrl = app.domain.startsWith("http")
|
|
? app.domain
|
|
: `https://${app.domain}`;
|
|
|
|
return (
|
|
<div
|
|
class="card"
|
|
key={app.id}
|
|
style="display: flex; flex-direction: column; justify-content: space-between; margin-bottom: 0; transition: transform 0.15s ease, box-shadow 0.15s ease;"
|
|
>
|
|
<div>
|
|
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.75rem; gap: 0.5rem;">
|
|
<div style="display: flex; align-items: center; gap: 0.65rem;">
|
|
<div style="display: flex; align-items: center; justify-content: center; width: 38px; height: 38px; background: var(--primary-light); color: var(--primary); border-radius: var(--radius-md); font-weight: 700; font-size: 1rem;">
|
|
{app.name.charAt(0).toUpperCase()}
|
|
</div>
|
|
<div>
|
|
<h3 style="margin: 0; font-size: 1.1rem; color: var(--text-primary);">
|
|
{app.name}
|
|
</h3>
|
|
<span style="font-size: 0.8rem; color: var(--text-muted); font-family: monospace;">
|
|
{app.domain}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<span
|
|
class={`badge ${
|
|
isAdminRole ? "badge-success" : "badge-info"
|
|
}`}
|
|
>
|
|
{app.role}
|
|
</span>
|
|
</div>
|
|
|
|
<p style="color: var(--text-secondary); font-size: 0.875rem; line-height: 1.5; margin: 0 0 1.25rem 0; flex: 1;">
|
|
{app.description ||
|
|
"Zero-trust secured internal application."}
|
|
</p>
|
|
</div>
|
|
|
|
<a
|
|
href={targetUrl}
|
|
class="btn-primary"
|
|
style="width: 100%; min-height: 46px; text-decoration: none; justify-content: center;"
|
|
>
|
|
<span>Launch App</span>
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2.5"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<line x1="7" y1="17" x2="17" y2="7"></line>
|
|
<polyline points="7 7 17 7 17 17"></polyline>
|
|
</svg>
|
|
</a>
|
|
</div>
|
|
);
|
|
})
|
|
)}
|
|
</div>
|
|
</AuthenticatedLayout>
|
|
);
|
|
};
|