create-authenik8-app 2.4.14 → 2.4.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -516
- package/dist/src/bin/cli.d.ts +3 -0
- package/dist/src/bin/cli.d.ts.map +1 -0
- package/dist/src/bin/cli.js +112 -0
- package/dist/src/bin/cli.js.map +1 -0
- package/dist/src/bin/index.js +108 -23
- package/dist/src/bin/index.js.map +1 -1
- package/dist/src/commands/add/apply.d.ts +6 -0
- package/dist/src/commands/add/apply.d.ts.map +1 -0
- package/dist/src/commands/add/apply.js +123 -0
- package/dist/src/commands/add/apply.js.map +1 -0
- package/dist/src/commands/add/ci.d.ts +6 -0
- package/dist/src/commands/add/ci.d.ts.map +1 -0
- package/dist/src/commands/add/ci.js +108 -0
- package/dist/src/commands/add/ci.js.map +1 -0
- package/dist/src/commands/add/context.d.ts +5 -0
- package/dist/src/commands/add/context.d.ts.map +1 -0
- package/dist/src/commands/add/context.js +19 -0
- package/dist/src/commands/add/context.js.map +1 -0
- package/dist/src/commands/add/diff.d.ts +4 -0
- package/dist/src/commands/add/diff.d.ts.map +1 -0
- package/dist/src/commands/add/diff.js +114 -0
- package/dist/src/commands/add/diff.js.map +1 -0
- package/dist/src/commands/add/index.d.ts +6 -0
- package/dist/src/commands/add/index.d.ts.map +1 -0
- package/dist/src/commands/add/index.js +61 -0
- package/dist/src/commands/add/index.js.map +1 -0
- package/dist/src/commands/add/output.d.ts +5 -0
- package/dist/src/commands/add/output.d.ts.map +1 -0
- package/dist/src/commands/add/output.js +58 -0
- package/dist/src/commands/add/output.js.map +1 -0
- package/dist/src/commands/add/plan.d.ts +6 -0
- package/dist/src/commands/add/plan.d.ts.map +1 -0
- package/dist/src/commands/add/plan.js +93 -0
- package/dist/src/commands/add/plan.js.map +1 -0
- package/dist/src/commands/add/registry.d.ts +4 -0
- package/dist/src/commands/add/registry.d.ts.map +1 -0
- package/dist/src/commands/add/registry.js +37 -0
- package/dist/src/commands/add/registry.js.map +1 -0
- package/dist/src/commands/add/types.d.ts +37 -0
- package/dist/src/commands/add/types.d.ts.map +1 -0
- package/dist/src/commands/add/types.js +2 -0
- package/dist/src/commands/add/types.js.map +1 -0
- package/dist/src/commands/doctor/checks.d.ts +3 -0
- package/dist/src/commands/doctor/checks.d.ts.map +1 -0
- package/dist/src/commands/doctor/checks.js +353 -0
- package/dist/src/commands/doctor/checks.js.map +1 -0
- package/dist/src/commands/doctor/context.d.ts +5 -0
- package/dist/src/commands/doctor/context.d.ts.map +1 -0
- package/dist/src/commands/doctor/context.js +134 -0
- package/dist/src/commands/doctor/context.js.map +1 -0
- package/dist/src/commands/doctor/index.d.ts +9 -0
- package/dist/src/commands/doctor/index.d.ts.map +1 -0
- package/dist/src/commands/doctor/index.js +80 -0
- package/dist/src/commands/doctor/index.js.map +1 -0
- package/dist/src/commands/doctor/output.d.ts +4 -0
- package/dist/src/commands/doctor/output.d.ts.map +1 -0
- package/dist/src/commands/doctor/output.js +53 -0
- package/dist/src/commands/doctor/output.js.map +1 -0
- package/dist/src/commands/doctor/postGeneration.d.ts +7 -0
- package/dist/src/commands/doctor/postGeneration.d.ts.map +1 -0
- package/dist/src/commands/doctor/postGeneration.js +22 -0
- package/dist/src/commands/doctor/postGeneration.js.map +1 -0
- package/dist/src/commands/doctor/services.d.ts +5 -0
- package/dist/src/commands/doctor/services.d.ts.map +1 -0
- package/dist/src/commands/doctor/services.js +80 -0
- package/dist/src/commands/doctor/services.js.map +1 -0
- package/dist/src/commands/doctor/types.d.ts +69 -0
- package/dist/src/commands/doctor/types.d.ts.map +1 -0
- package/dist/src/commands/doctor/types.js +2 -0
- package/dist/src/commands/doctor/types.js.map +1 -0
- package/dist/src/commands/upgrade/context.d.ts +5 -0
- package/dist/src/commands/upgrade/context.d.ts.map +1 -0
- package/dist/src/commands/upgrade/context.js +57 -0
- package/dist/src/commands/upgrade/context.js.map +1 -0
- package/dist/src/commands/upgrade/index.d.ts +7 -0
- package/dist/src/commands/upgrade/index.d.ts.map +1 -0
- package/dist/src/commands/upgrade/index.js +35 -0
- package/dist/src/commands/upgrade/index.js.map +1 -0
- package/dist/src/commands/upgrade/migrations.d.ts +4 -0
- package/dist/src/commands/upgrade/migrations.d.ts.map +1 -0
- package/dist/src/commands/upgrade/migrations.js +22 -0
- package/dist/src/commands/upgrade/migrations.js.map +1 -0
- package/dist/src/commands/upgrade/output.d.ts +4 -0
- package/dist/src/commands/upgrade/output.d.ts.map +1 -0
- package/dist/src/commands/upgrade/output.js +52 -0
- package/dist/src/commands/upgrade/output.js.map +1 -0
- package/dist/src/commands/upgrade/plan.d.ts +3 -0
- package/dist/src/commands/upgrade/plan.d.ts.map +1 -0
- package/dist/src/commands/upgrade/plan.js +119 -0
- package/dist/src/commands/upgrade/plan.js.map +1 -0
- package/dist/src/commands/upgrade/types.d.ts +48 -0
- package/dist/src/commands/upgrade/types.d.ts.map +1 -0
- package/dist/src/commands/upgrade/types.js +2 -0
- package/dist/src/commands/upgrade/types.js.map +1 -0
- package/dist/src/lib/args.d.ts +21 -0
- package/dist/src/lib/args.d.ts.map +1 -1
- package/dist/src/lib/args.js +119 -1
- package/dist/src/lib/args.js.map +1 -1
- package/dist/src/lib/constants.d.ts +1 -1
- package/dist/src/lib/constants.d.ts.map +1 -1
- package/dist/src/lib/constants.js +3 -1
- package/dist/src/lib/constants.js.map +1 -1
- package/dist/src/lib/expressOAuth.d.ts +10 -0
- package/dist/src/lib/expressOAuth.d.ts.map +1 -0
- package/dist/src/lib/expressOAuth.js +164 -0
- package/dist/src/lib/expressOAuth.js.map +1 -0
- package/dist/src/lib/interruption.d.ts +14 -0
- package/dist/src/lib/interruption.d.ts.map +1 -0
- package/dist/src/lib/interruption.js +16 -0
- package/dist/src/lib/interruption.js.map +1 -0
- package/dist/src/lib/nonInteractive.d.ts +8 -0
- package/dist/src/lib/nonInteractive.d.ts.map +1 -0
- package/dist/src/lib/nonInteractive.js +111 -0
- package/dist/src/lib/nonInteractive.js.map +1 -0
- package/dist/src/lib/oauth.d.ts +9 -0
- package/dist/src/lib/oauth.d.ts.map +1 -0
- package/dist/src/lib/oauth.js +62 -0
- package/dist/src/lib/oauth.js.map +1 -0
- package/dist/src/lib/onboarding.d.ts +8 -0
- package/dist/src/lib/onboarding.d.ts.map +1 -0
- package/dist/src/lib/onboarding.js +35 -0
- package/dist/src/lib/onboarding.js.map +1 -0
- package/dist/src/lib/packageManagerCommands.d.ts +11 -0
- package/dist/src/lib/packageManagerCommands.d.ts.map +1 -0
- package/dist/src/lib/packageManagerCommands.js +34 -0
- package/dist/src/lib/packageManagerCommands.js.map +1 -0
- package/dist/src/lib/process.d.ts.map +1 -1
- package/dist/src/lib/process.js +9 -0
- package/dist/src/lib/process.js.map +1 -1
- package/dist/src/lib/projectManifest.d.ts +67 -0
- package/dist/src/lib/projectManifest.d.ts.map +1 -0
- package/dist/src/lib/projectManifest.js +134 -0
- package/dist/src/lib/projectManifest.js.map +1 -0
- package/dist/src/lib/release.d.ts +9 -0
- package/dist/src/lib/release.d.ts.map +1 -0
- package/dist/src/lib/release.js +48 -0
- package/dist/src/lib/release.js.map +1 -0
- package/dist/src/lib/rootCommand.d.ts +15 -0
- package/dist/src/lib/rootCommand.d.ts.map +1 -0
- package/dist/src/lib/rootCommand.js +17 -0
- package/dist/src/lib/rootCommand.js.map +1 -0
- package/dist/src/lib/schemas.d.ts +15 -6
- package/dist/src/lib/schemas.d.ts.map +1 -1
- package/dist/src/lib/schemas.js +8 -1
- package/dist/src/lib/schemas.js.map +1 -1
- package/dist/src/lib/semver.d.ts +10 -0
- package/dist/src/lib/semver.d.ts.map +1 -0
- package/dist/src/lib/semver.js +51 -0
- package/dist/src/lib/semver.js.map +1 -0
- package/dist/src/lib/types.d.ts +5 -1
- package/dist/src/lib/types.d.ts.map +1 -1
- package/dist/src/steps/configurePrisma.d.ts.map +1 -1
- package/dist/src/steps/configurePrisma.js +18 -23
- package/dist/src/steps/configurePrisma.js.map +1 -1
- package/dist/src/steps/configureReadme.d.ts +4 -0
- package/dist/src/steps/configureReadme.d.ts.map +1 -0
- package/dist/src/steps/configureReadme.js +22 -0
- package/dist/src/steps/configureReadme.js.map +1 -0
- package/dist/src/steps/createProject.d.ts.map +1 -1
- package/dist/src/steps/createProject.js +16 -170
- package/dist/src/steps/createProject.js.map +1 -1
- package/dist/src/steps/finalSetup.d.ts +3 -2
- package/dist/src/steps/finalSetup.d.ts.map +1 -1
- package/dist/src/steps/finalSetup.js +14 -9
- package/dist/src/steps/finalSetup.js.map +1 -1
- package/dist/src/steps/installDeps.d.ts +1 -0
- package/dist/src/steps/installDeps.d.ts.map +1 -1
- package/dist/src/steps/installDeps.js +11 -0
- package/dist/src/steps/installDeps.js.map +1 -1
- package/dist/src/steps/prompts.d.ts.map +1 -1
- package/dist/src/steps/prompts.js +32 -13
- package/dist/src/steps/prompts.js.map +1 -1
- package/dist/src/utils/output.d.ts.map +1 -1
- package/dist/src/utils/output.js +23 -11
- package/dist/src/utils/output.js.map +1 -1
- package/package.json +5 -4
- package/templates/express-auth/README.md +21 -12
- package/templates/express-auth/package.json +11 -7
- package/templates/express-auth/src/config/redis.ts +109 -0
- package/templates/express-auth/src/server.ts +2 -0
- package/templates/express-auth+/README.md +25 -18
- package/templates/express-auth+/package.json +11 -7
- package/templates/express-auth+/src/auth/auth.ts +2 -0
- package/templates/express-auth+/src/auth/controllers/password.controller.ts +12 -1
- package/templates/express-auth+/src/auth/routes/password.route.ts +1 -0
- package/templates/express-auth+/src/config/redis.ts +109 -0
- package/templates/express-auth+/src/utils/security.ts +12 -0
- package/templates/express-base/README.md +21 -12
- package/templates/express-base/package.json +11 -8
- package/templates/express-base/src/.env.example +1 -1
- package/templates/express-base/src/config/redis.ts +109 -0
- package/templates/express-base/src/server.ts +2 -0
- package/templates/fullstack/.env.example +3 -2
- package/templates/fullstack/PRESET_CONTRACT.md +7 -3
- package/templates/fullstack/README.md +45 -7
- package/templates/fullstack/apps/api/package.json +6 -4
- package/templates/fullstack/apps/api/prisma/migrations/20260721000000_init/migration.sql +163 -0
- package/templates/fullstack/apps/api/prisma/migrations/migration_lock.toml +3 -0
- package/templates/fullstack/apps/api/src/auth/authenik8.ts +13 -4
- package/templates/fullstack/apps/api/src/config/env.ts +9 -1
- package/templates/fullstack/apps/api/src/config/logger.ts +21 -11
- package/templates/fullstack/apps/api/tests/logger.test.ts +43 -0
- package/templates/fullstack/apps/web/package.json +3 -1
- package/templates/fullstack/apps/web/src/App.tsx +1 -1
- package/templates/fullstack/apps/web/src/auth/RouteGuards.tsx +1 -1
- package/templates/fullstack/apps/web/src/components/AppShell.tsx +90 -45
- package/templates/fullstack/apps/web/src/components/AuthShell.tsx +10 -7
- package/templates/fullstack/apps/web/src/components/Page.tsx +6 -3
- package/templates/fullstack/apps/web/src/features/projects/ProjectPages.tsx +1 -1
- package/templates/fullstack/apps/web/src/main.tsx +13 -6
- package/templates/fullstack/apps/web/src/pages/DashboardPage.tsx +163 -23
- package/templates/fullstack/apps/web/src/pages/StatusPages.tsx +1 -1
- package/templates/fullstack/apps/web/src/pages/auth/LoginPage.tsx +93 -17
- package/templates/fullstack/apps/web/src/pages/auth/OAuthCallbackPage.tsx +1 -1
- package/templates/fullstack/apps/web/src/pages/auth/RecoveryPages.tsx +1 -1
- package/templates/fullstack/apps/web/src/pages/auth/RegisterPage.tsx +1 -1
- package/templates/fullstack/apps/web/src/pages/auth/VerifyEmailPage.tsx +1 -1
- package/templates/fullstack/apps/web/src/styles.css +119 -43
- package/templates/fullstack/apps/web/vite.config.ts +12 -0
- package/templates/fullstack/docker-compose.yml +2 -2
- package/templates/fullstack/gitignore.template +1 -0
- package/templates/fullstack/package.json +17 -8
- package/templates/fullstack/scripts/run-local.mjs +157 -0
- package/templates/prisma/postgresql/.env.example +1 -2
- package/templates/prisma/sqlite/.env.example +1 -2
- package/templates/prisma/sqlite/client.ts +2 -2
|
@@ -1,48 +1,188 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
1
2
|
import { useQuery } from "@tanstack/react-query";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
Activity,
|
|
5
|
+
ArrowRight,
|
|
6
|
+
CheckCircle2,
|
|
7
|
+
FolderKanban,
|
|
8
|
+
MonitorSmartphone,
|
|
9
|
+
Plus,
|
|
10
|
+
ShieldCheck,
|
|
11
|
+
} from "lucide-react";
|
|
12
|
+
import { Badge } from "@astryxdesign/core/Badge";
|
|
13
|
+
import { Button } from "@astryxdesign/core/Button";
|
|
14
|
+
import { Card } from "@astryxdesign/core/Card";
|
|
15
|
+
import { StatusDot } from "@astryxdesign/core/StatusDot";
|
|
16
|
+
import { Link, useNavigate } from "react-router";
|
|
17
|
+
import { EmptyState, Spinner } from "@authenik8/ui";
|
|
5
18
|
import { accountApi, healthApi, projectApi } from "@authenik8/api-client";
|
|
6
|
-
import {
|
|
19
|
+
import { ErrorNotice } from "../components/Page";
|
|
7
20
|
import { useAuth } from "../auth/AuthProvider";
|
|
8
21
|
|
|
9
22
|
export function DashboardPage() {
|
|
10
23
|
const { user } = useAuth();
|
|
24
|
+
const navigate = useNavigate();
|
|
11
25
|
const projects = useQuery({ queryKey: ["projects"], queryFn: projectApi.list });
|
|
12
26
|
const sessions = useQuery({ queryKey: ["sessions"], queryFn: accountApi.sessions });
|
|
13
27
|
const health = useQuery({ queryKey: ["health"], queryFn: healthApi.status, refetchInterval: 60_000 });
|
|
28
|
+
const firstName = user?.name.trim().split(/\s+/)[0] || "there";
|
|
29
|
+
const projectCount = projects.data?.projects.length;
|
|
30
|
+
const sessionCount = sessions.data?.sessions.length;
|
|
31
|
+
const healthLabel = health.isPending
|
|
32
|
+
? "Checking"
|
|
33
|
+
: health.isError
|
|
34
|
+
? "Unavailable"
|
|
35
|
+
: health.data?.status ?? "Unknown";
|
|
14
36
|
|
|
15
37
|
return (
|
|
16
|
-
|
|
17
|
-
<
|
|
38
|
+
<div className="dashboard-page">
|
|
39
|
+
<header className="dashboard-hero">
|
|
40
|
+
<div className="hero-copy">
|
|
41
|
+
<div className="eyebrow">
|
|
42
|
+
<StatusDot
|
|
43
|
+
variant={health.isError ? "error" : health.isPending ? "warning" : "success"}
|
|
44
|
+
label={`API status: ${healthLabel}`}
|
|
45
|
+
isPulsing={health.isPending}
|
|
46
|
+
/>
|
|
47
|
+
<span>Protected workspace</span>
|
|
48
|
+
</div>
|
|
49
|
+
<h1>Good to see you, {firstName}</h1>
|
|
50
|
+
<p>Your projects, active identity sessions, and account security in one place.</p>
|
|
51
|
+
</div>
|
|
52
|
+
<div className="hero-actions">
|
|
53
|
+
<Button
|
|
54
|
+
label="Security settings"
|
|
55
|
+
variant="secondary"
|
|
56
|
+
onClick={() => navigate("/settings/security")}
|
|
57
|
+
/>
|
|
58
|
+
<Button
|
|
59
|
+
label="New project"
|
|
60
|
+
variant="primary"
|
|
61
|
+
icon={<Plus size={17} />}
|
|
62
|
+
onClick={() => navigate("/projects/new")}
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
</header>
|
|
66
|
+
|
|
18
67
|
<div className="stat-grid">
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
68
|
+
<MetricCard
|
|
69
|
+
icon={<FolderKanban size={20} />}
|
|
70
|
+
tone="cyan"
|
|
71
|
+
label="Projects"
|
|
72
|
+
value={projectCount ?? "-"}
|
|
73
|
+
detail={projectCount === 1 ? "Owned resource" : "Owned resources"}
|
|
74
|
+
/>
|
|
75
|
+
<MetricCard
|
|
76
|
+
icon={<MonitorSmartphone size={20} />}
|
|
77
|
+
tone="violet"
|
|
78
|
+
label="Active sessions"
|
|
79
|
+
value={sessionCount ?? "-"}
|
|
80
|
+
detail="Across your devices"
|
|
81
|
+
/>
|
|
82
|
+
<MetricCard
|
|
83
|
+
icon={<ShieldCheck size={20} />}
|
|
84
|
+
tone="green"
|
|
85
|
+
label="Email security"
|
|
86
|
+
value={user?.verified ? "Verified" : "Pending"}
|
|
87
|
+
detail={user?.verified ? "Identity confirmed" : "Action required"}
|
|
88
|
+
/>
|
|
89
|
+
<MetricCard
|
|
90
|
+
icon={<Activity size={20} />}
|
|
91
|
+
tone="amber"
|
|
92
|
+
label="API status"
|
|
93
|
+
value={healthLabel}
|
|
94
|
+
detail="Checked every minute"
|
|
95
|
+
/>
|
|
23
96
|
</div>
|
|
24
97
|
|
|
25
98
|
<div className="dashboard-grid">
|
|
26
|
-
<
|
|
27
|
-
<div className="panel-heading"
|
|
28
|
-
|
|
29
|
-
<
|
|
99
|
+
<Card padding={0} className="dashboard-panel">
|
|
100
|
+
<div className="panel-heading">
|
|
101
|
+
<div><h2>Recent projects</h2><p>Your latest workspace activity</p></div>
|
|
102
|
+
<Link className="text-link" to="/projects">View all <ArrowRight size={15} /></Link>
|
|
103
|
+
</div>
|
|
104
|
+
{projects.isPending ? (
|
|
105
|
+
<div className="panel-loading"><Spinner /></div>
|
|
106
|
+
) : projects.error ? (
|
|
107
|
+
<div className="panel-error"><ErrorNotice error={projects.error} /></div>
|
|
108
|
+
) : projects.data!.projects.length === 0 ? (
|
|
109
|
+
<EmptyState
|
|
110
|
+
icon={<FolderKanban />}
|
|
111
|
+
title="No projects yet"
|
|
112
|
+
action={<Link className="button button-primary" to="/projects/new">Create project</Link>}
|
|
113
|
+
>
|
|
114
|
+
Create the first owned resource in this workspace.
|
|
115
|
+
</EmptyState>
|
|
30
116
|
) : (
|
|
31
|
-
<div className="table-wrap"
|
|
32
|
-
|
|
33
|
-
|
|
117
|
+
<div className="table-wrap">
|
|
118
|
+
<table>
|
|
119
|
+
<caption className="sr-only">Recently updated projects</caption>
|
|
120
|
+
<thead><tr><th>Name</th><th>Status</th><th>Updated</th></tr></thead>
|
|
121
|
+
<tbody>
|
|
122
|
+
{projects.data!.projects.slice(0, 5).map((project) => (
|
|
123
|
+
<tr key={project.id}>
|
|
124
|
+
<td><Link className="table-link" to={`/projects/${project.id}`}>{project.name}</Link></td>
|
|
125
|
+
<td><Badge variant={projectStatusVariant(project.status)} label={formatLabel(project.status)} /></td>
|
|
126
|
+
<td>{new Date(project.updatedAt).toLocaleDateString()}</td>
|
|
127
|
+
</tr>
|
|
128
|
+
))}
|
|
129
|
+
</tbody>
|
|
130
|
+
</table>
|
|
131
|
+
</div>
|
|
34
132
|
)}
|
|
35
|
-
</
|
|
36
|
-
|
|
37
|
-
|
|
133
|
+
</Card>
|
|
134
|
+
|
|
135
|
+
<Card padding={0} className="dashboard-panel security-panel">
|
|
136
|
+
<div className="panel-heading">
|
|
137
|
+
<div><h2>Security posture</h2><p>Live account checks</p></div>
|
|
138
|
+
<Badge variant={user?.verified ? "success" : "warning"} label={user?.verified ? "Protected" : "Review"} />
|
|
139
|
+
</div>
|
|
38
140
|
<ul className="check-list">
|
|
39
141
|
<li><CheckCircle2 /><span><strong>Rotating session</strong><small>Refresh cookie protected</small></span></li>
|
|
40
142
|
<li className={user?.verified ? "" : "pending"}><CheckCircle2 /><span><strong>Email verification</strong><small>{user?.verified ? "Identity confirmed" : "Action required"}</small></span></li>
|
|
41
143
|
<li><CheckCircle2 /><span><strong>API authorization</strong><small>Role and owner policies active</small></span></li>
|
|
42
144
|
</ul>
|
|
43
|
-
<
|
|
44
|
-
|
|
145
|
+
<div className="security-action">
|
|
146
|
+
<Button
|
|
147
|
+
label="Review account security"
|
|
148
|
+
variant="secondary"
|
|
149
|
+
width="100%"
|
|
150
|
+
onClick={() => navigate("/settings/security")}
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
</Card>
|
|
45
154
|
</div>
|
|
46
|
-
|
|
155
|
+
</div>
|
|
47
156
|
);
|
|
48
157
|
}
|
|
158
|
+
|
|
159
|
+
interface MetricCardProps {
|
|
160
|
+
icon: ReactNode;
|
|
161
|
+
tone: "cyan" | "violet" | "green" | "amber";
|
|
162
|
+
label: string;
|
|
163
|
+
value: string | number;
|
|
164
|
+
detail: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function MetricCard({ icon, tone, label, value, detail }: MetricCardProps) {
|
|
168
|
+
return (
|
|
169
|
+
<Card padding={0} className="stat-card">
|
|
170
|
+
<span className={`stat-icon ${tone}`}>{icon}</span>
|
|
171
|
+
<div className="stat-copy">
|
|
172
|
+
<span>{label}</span>
|
|
173
|
+
<strong>{value}</strong>
|
|
174
|
+
<small>{detail}</small>
|
|
175
|
+
</div>
|
|
176
|
+
</Card>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function projectStatusVariant(status: string): "success" | "neutral" | "warning" {
|
|
181
|
+
if (status === "ACTIVE") return "success";
|
|
182
|
+
if (status === "ARCHIVED") return "neutral";
|
|
183
|
+
return "warning";
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function formatLabel(value: string) {
|
|
187
|
+
return value.toLowerCase().replaceAll("_", " ");
|
|
188
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ArrowLeft, LockKeyhole, SearchX } from "lucide-react";
|
|
2
|
-
import { Link } from "react-router
|
|
2
|
+
import { Link } from "react-router";
|
|
3
3
|
|
|
4
4
|
function StatusPage({ icon, title, message }: { icon: React.ReactNode; title: string; message: string }) {
|
|
5
5
|
return <main className="status-page"><div className="status-icon">{icon}</div><h1>{title}</h1><p>{message}</p><Link className="button button-primary" to="/"><ArrowLeft size={17} /> Return to dashboard</Link></main>;
|
|
@@ -1,12 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
type ComponentProps,
|
|
4
|
+
type ComponentType,
|
|
5
|
+
type FormEvent,
|
|
6
|
+
type InputHTMLAttributes,
|
|
7
|
+
} from "react";
|
|
8
|
+
import { Github, KeyRound, LockKeyhole, Mail } from "lucide-react";
|
|
9
|
+
import { Banner } from "@astryxdesign/core/Banner";
|
|
10
|
+
import { Button } from "@astryxdesign/core/Button";
|
|
11
|
+
import { TextInput } from "@astryxdesign/core/TextInput";
|
|
12
|
+
import { Link, useLocation, useNavigate, useSearchParams } from "react-router";
|
|
5
13
|
import { AuthShell } from "../../components/AuthShell";
|
|
6
|
-
import {
|
|
14
|
+
import { getErrorMessage } from "../../components/Page";
|
|
7
15
|
import { useAuth } from "../../auth/AuthProvider";
|
|
8
16
|
import { enabledOAuthProviders } from "../../auth/providers";
|
|
9
17
|
|
|
18
|
+
type CredentialInputProps = ComponentProps<typeof TextInput> &
|
|
19
|
+
Pick<InputHTMLAttributes<HTMLInputElement>, "autoComplete">;
|
|
20
|
+
|
|
21
|
+
// Astryx 0.1.7 forwards native input attributes at runtime but does not yet
|
|
22
|
+
// declare autoComplete. Keep that browser and password-manager contract here.
|
|
23
|
+
const CredentialInput = TextInput as ComponentType<CredentialInputProps>;
|
|
24
|
+
|
|
10
25
|
export function LoginPage() {
|
|
11
26
|
const { login } = useAuth();
|
|
12
27
|
const navigate = useNavigate();
|
|
@@ -33,19 +48,80 @@ export function LoginPage() {
|
|
|
33
48
|
}
|
|
34
49
|
|
|
35
50
|
return (
|
|
36
|
-
<AuthShell
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
|
|
51
|
+
<AuthShell
|
|
52
|
+
title="Welcome back"
|
|
53
|
+
subtitle="Sign in to your workspace"
|
|
54
|
+
footer={<>New to Authenik8? <Link to="/register">Create an account</Link></>}
|
|
55
|
+
>
|
|
56
|
+
{params.has("oauthError") && (
|
|
57
|
+
<Banner
|
|
58
|
+
className="login-alert"
|
|
59
|
+
status="error"
|
|
60
|
+
title="OAuth sign-in failed"
|
|
61
|
+
description="The provider could not complete sign-in. Please try again."
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
64
|
+
{error !== undefined && (
|
|
65
|
+
<Banner
|
|
66
|
+
className="login-alert"
|
|
67
|
+
status="error"
|
|
68
|
+
title="Unable to sign in"
|
|
69
|
+
description={getErrorMessage(error)}
|
|
70
|
+
/>
|
|
71
|
+
)}
|
|
72
|
+
<form className="form-stack login-form" onSubmit={submit}>
|
|
73
|
+
<CredentialInput
|
|
74
|
+
label="Email address"
|
|
75
|
+
type="email"
|
|
76
|
+
htmlName="email"
|
|
77
|
+
autoComplete="email"
|
|
78
|
+
value={email}
|
|
79
|
+
onChange={setEmail}
|
|
80
|
+
startIcon={<Mail size={17} />}
|
|
81
|
+
size="lg"
|
|
82
|
+
width="100%"
|
|
83
|
+
isRequired
|
|
84
|
+
/>
|
|
85
|
+
<CredentialInput
|
|
86
|
+
label="Password"
|
|
87
|
+
type="password"
|
|
88
|
+
htmlName="password"
|
|
89
|
+
autoComplete="current-password"
|
|
90
|
+
value={password}
|
|
91
|
+
onChange={setPassword}
|
|
92
|
+
startIcon={<LockKeyhole size={17} />}
|
|
93
|
+
size="lg"
|
|
94
|
+
width="100%"
|
|
95
|
+
isRequired
|
|
96
|
+
/>
|
|
97
|
+
<div className="login-recovery"><Link to="/forgot-password">Forgot password?</Link></div>
|
|
98
|
+
<Button
|
|
99
|
+
type="submit"
|
|
100
|
+
label="Sign in"
|
|
101
|
+
variant="primary"
|
|
102
|
+
icon={<KeyRound size={17} />}
|
|
103
|
+
width="100%"
|
|
104
|
+
isLoading={pending}
|
|
105
|
+
isDisabled={pending}
|
|
106
|
+
/>
|
|
44
107
|
</form>
|
|
45
|
-
{enabledOAuthProviders.length > 0 &&
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
108
|
+
{enabledOAuthProviders.length > 0 && (
|
|
109
|
+
<>
|
|
110
|
+
<div className="divider"><span>or continue with</span></div>
|
|
111
|
+
<div className="oauth-grid">
|
|
112
|
+
{enabledOAuthProviders.map((provider) => (
|
|
113
|
+
<Button
|
|
114
|
+
key={provider}
|
|
115
|
+
label={provider === "google" ? "Google" : "GitHub"}
|
|
116
|
+
variant="secondary"
|
|
117
|
+
width="100%"
|
|
118
|
+
icon={provider === "google" ? <span className="provider-g">G</span> : <Github size={18} />}
|
|
119
|
+
onClick={() => window.location.assign(`/api/auth/oauth/${provider}`)}
|
|
120
|
+
/>
|
|
121
|
+
))}
|
|
122
|
+
</div>
|
|
123
|
+
</>
|
|
124
|
+
)}
|
|
49
125
|
</AuthShell>
|
|
50
126
|
);
|
|
51
127
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
import { useNavigate, useSearchParams } from "react-router
|
|
2
|
+
import { useNavigate, useSearchParams } from "react-router";
|
|
3
3
|
import { Spinner } from "@authenik8/ui";
|
|
4
4
|
import { useAuth } from "../../auth/AuthProvider";
|
|
5
5
|
import { AuthShell } from "../../components/AuthShell";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, type FormEvent } from "react";
|
|
2
2
|
import { ArrowLeft, Mail, RotateCcw } from "lucide-react";
|
|
3
|
-
import { Link, useSearchParams } from "react-router
|
|
3
|
+
import { Link, useSearchParams } from "react-router";
|
|
4
4
|
import { Button, Input } from "@authenik8/ui";
|
|
5
5
|
import { authApi } from "@authenik8/api-client";
|
|
6
6
|
import { AuthShell } from "../../components/AuthShell";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, type FormEvent } from "react";
|
|
2
2
|
import { UserPlus } from "lucide-react";
|
|
3
|
-
import { Link } from "react-router
|
|
3
|
+
import { Link } from "react-router";
|
|
4
4
|
import { registerSchema, type RegisterInput } from "@authenik8/contracts";
|
|
5
5
|
import { ApiError, authApi } from "@authenik8/api-client";
|
|
6
6
|
import { Button, Input } from "@authenik8/ui";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
|
-
import { Link, useSearchParams } from "react-router
|
|
2
|
+
import { Link, useSearchParams } from "react-router";
|
|
3
3
|
import { Spinner } from "@authenik8/ui";
|
|
4
4
|
import { authApi } from "@authenik8/api-client";
|
|
5
5
|
import { AuthShell } from "../../components/AuthShell";
|
|
@@ -20,6 +20,37 @@
|
|
|
20
20
|
--amber-light: #fff1ce;
|
|
21
21
|
--red: #bd384c;
|
|
22
22
|
--red-light: #fde8ec;
|
|
23
|
+
--color-background-surface: #ffffff;
|
|
24
|
+
--color-background-body: #f3f5f2;
|
|
25
|
+
--color-background-card: #ffffff;
|
|
26
|
+
--color-background-popover: #ffffff;
|
|
27
|
+
--color-background-muted: #eef2ee;
|
|
28
|
+
--color-accent: #218653;
|
|
29
|
+
--color-accent-muted: #e4f4e9;
|
|
30
|
+
--color-neutral: #edf1ee;
|
|
31
|
+
--color-overlay: rgba(11, 35, 23, .58);
|
|
32
|
+
--color-overlay-hover: rgba(18, 53, 36, .06);
|
|
33
|
+
--color-overlay-pressed: rgba(18, 53, 36, .11);
|
|
34
|
+
--color-text-primary: #27302b;
|
|
35
|
+
--color-text-secondary: #69736c;
|
|
36
|
+
--color-text-disabled: #98a099;
|
|
37
|
+
--color-text-accent: #176a46;
|
|
38
|
+
--color-on-accent: #ffffff;
|
|
39
|
+
--color-icon-primary: #27302b;
|
|
40
|
+
--color-icon-secondary: #69736c;
|
|
41
|
+
--color-icon-accent: #218653;
|
|
42
|
+
--color-success: #246a43;
|
|
43
|
+
--color-success-muted: #e2f3e7;
|
|
44
|
+
--color-warning: #9a6512;
|
|
45
|
+
--color-warning-muted: #fff1ce;
|
|
46
|
+
--color-error: #bd384c;
|
|
47
|
+
--color-error-muted: #fde8ec;
|
|
48
|
+
--color-border: #dce3dd;
|
|
49
|
+
--color-border-emphasized: #bdc9bf;
|
|
50
|
+
--radius-element: 6px;
|
|
51
|
+
--radius-container: 10px;
|
|
52
|
+
--radius-page: 14px;
|
|
53
|
+
--shadow-low: 0 1px 2px rgba(18, 53, 36, .05), 0 6px 18px rgba(18, 53, 36, .04);
|
|
23
54
|
}
|
|
24
55
|
|
|
25
56
|
* { box-sizing: border-box; letter-spacing: 0; }
|
|
@@ -98,56 +129,90 @@ code { overflow-wrap: anywhere; color: #246a43; font-family: "SFMono-Regular", C
|
|
|
98
129
|
.screen-loader { min-height: 50vh; display: grid; place-items: center; }
|
|
99
130
|
.panel-loading, .center-status { min-height: 180px; display: grid; place-items: center; }
|
|
100
131
|
|
|
101
|
-
.
|
|
102
|
-
.
|
|
103
|
-
.
|
|
104
|
-
.
|
|
105
|
-
.
|
|
106
|
-
.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
132
|
+
.workspace-shell { background: var(--navy); }
|
|
133
|
+
.workspace-topbar { min-height: 68px; border-bottom: 1px solid var(--line); background: rgba(255, 255, 255, .96); backdrop-filter: blur(14px); }
|
|
134
|
+
.brand-logo { width: 43px; height: 43px; margin: -6px; display: block; }
|
|
135
|
+
.current-section { min-width: 0; display: flex; align-items: center; gap: 8px; color: #626d65; font-size: 13px; font-weight: 700; }
|
|
136
|
+
.current-section svg { color: var(--green); }
|
|
137
|
+
.workspace-sidebar {
|
|
138
|
+
--color-background-surface: var(--navy);
|
|
139
|
+
--color-background-muted: var(--navy-soft);
|
|
140
|
+
--color-text-primary: #dcebe1;
|
|
141
|
+
--color-text-secondary: #8fad9a;
|
|
142
|
+
--color-text-disabled: #64806e;
|
|
143
|
+
--color-icon-primary: #dcebe1;
|
|
144
|
+
--color-icon-secondary: #9eb9a8;
|
|
145
|
+
--color-icon-accent: #7ce8aa;
|
|
146
|
+
--color-neutral: #20563a;
|
|
147
|
+
--color-overlay-hover: #19472f;
|
|
148
|
+
--color-overlay-pressed: #20563a;
|
|
149
|
+
--color-border: #28533a;
|
|
150
|
+
background: var(--navy);
|
|
151
|
+
border-right: 1px solid #28533a;
|
|
152
|
+
}
|
|
153
|
+
.workspace-sidebar button:focus-visible { outline: 2px solid #7ce8aa; outline-offset: -2px; }
|
|
154
|
+
.sidebar-account { min-width: 0; display: flex; align-items: center; gap: 10px; }
|
|
116
155
|
.sidebar-account > div:last-child { min-width: 0; display: grid; gap: 2px; }
|
|
117
156
|
.sidebar-account strong, .sidebar-account span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
118
157
|
.sidebar-account strong { color: #f2faf5; font-size: 12px; }
|
|
119
158
|
.sidebar-account span { color: #8fad9a; font-size: 10px; }
|
|
120
|
-
.mobile-sidebar, .mobile-scrim { display: none; }
|
|
121
|
-
|
|
122
|
-
.app-main { min-width: 0; }
|
|
123
|
-
.topbar { height: 64px; padding: 0 28px; border-bottom: 1px solid var(--line); display: flex; align-items: center; gap: 14px; background: rgba(255,255,255,.95); position: sticky; top: 0; z-index: 20; }
|
|
124
|
-
.breadcrumb { min-width: 0; display: flex; align-items: center; gap: 8px; color: #626d65; font-size: 13px; font-weight: 700; }
|
|
125
|
-
.breadcrumb svg { color: var(--green); }
|
|
126
|
-
.topbar > .icon-button:last-child { margin-left: auto; }
|
|
127
|
-
.menu-button { display: none; }
|
|
128
159
|
.page { width: 100%; max-width: 1480px; margin: 0 auto; padding: 32px; }
|
|
129
160
|
.page-header { min-height: 62px; margin-bottom: 24px; display: flex; justify-content: space-between; align-items: flex-start; gap: 20px; }
|
|
130
161
|
.page-header p { margin-bottom: 0; font-size: 14px; }
|
|
131
162
|
.page-actions, .action-row { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 9px; }
|
|
132
163
|
|
|
133
|
-
.
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
164
|
+
.dashboard-page { display: grid; gap: 22px; }
|
|
165
|
+
.dashboard-hero {
|
|
166
|
+
min-height: 210px;
|
|
167
|
+
padding: 34px 38px;
|
|
168
|
+
position: relative;
|
|
169
|
+
overflow: hidden;
|
|
170
|
+
border: 1px solid #285f41;
|
|
171
|
+
border-radius: 12px;
|
|
172
|
+
display: flex;
|
|
173
|
+
align-items: flex-end;
|
|
174
|
+
justify-content: space-between;
|
|
175
|
+
gap: 28px;
|
|
176
|
+
color: #ffffff;
|
|
177
|
+
background:
|
|
178
|
+
radial-gradient(circle at 86% 8%, rgba(124, 232, 170, .2), transparent 34%),
|
|
179
|
+
linear-gradient(135deg, #123524 0%, #16452d 68%, #1a5134 100%);
|
|
180
|
+
box-shadow: 0 18px 45px rgba(18, 53, 36, .14);
|
|
181
|
+
--color-accent: #a7dfbf;
|
|
182
|
+
--color-on-accent: #123524;
|
|
183
|
+
--color-text-primary: #123524;
|
|
184
|
+
--color-border: rgba(255, 255, 255, .42);
|
|
185
|
+
}
|
|
186
|
+
.dashboard-hero::after { content: ""; width: 190px; height: 190px; position: absolute; top: -112px; right: 14%; border: 1px solid rgba(255,255,255,.14); border-radius: 50%; box-shadow: 0 0 0 38px rgba(255,255,255,.025), 0 0 0 78px rgba(255,255,255,.02); pointer-events: none; }
|
|
187
|
+
.hero-copy, .hero-actions { position: relative; z-index: 1; }
|
|
188
|
+
.hero-copy { max-width: 700px; }
|
|
189
|
+
.hero-copy h1 { margin-bottom: 10px; color: #ffffff; font-size: clamp(28px, 3vw, 38px); letter-spacing: -.025em; }
|
|
190
|
+
.hero-copy p { max-width: 610px; margin-bottom: 0; color: #b9cebf; font-size: 15px; }
|
|
191
|
+
.eyebrow { margin-bottom: 17px; display: flex; align-items: center; gap: 8px; color: #9fc5ac; font-size: 11px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
|
|
192
|
+
.hero-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 9px; }
|
|
193
|
+
.hero-actions button:first-child { color: #eef8f1; border-color: rgba(255,255,255,.28); background: rgba(255,255,255,.08); }
|
|
194
|
+
.hero-actions button:first-child:hover { background: rgba(255,255,255,.14); }
|
|
195
|
+
|
|
196
|
+
.stat-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
|
|
197
|
+
.stat-card { min-height: 116px; padding: 18px; display: flex; align-items: center; gap: 14px; box-shadow: var(--shadow-low); }
|
|
198
|
+
.stat-copy { min-width: 0; display: grid; gap: 4px; }
|
|
199
|
+
.stat-copy > span { color: #717c74; font-size: 12px; font-weight: 650; }
|
|
200
|
+
.stat-copy strong { overflow-wrap: anywhere; color: #27302b; font-size: 23px; line-height: 1.1; letter-spacing: -.02em; }
|
|
201
|
+
.stat-copy small { margin-top: 2px; overflow: hidden; color: #8a938c; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
|
|
202
|
+
.stat-icon { width: 42px; height: 42px; flex: 0 0 auto; border-radius: 8px; display: grid; place-items: center; }
|
|
139
203
|
.stat-icon.cyan { color: #1d7549; background: var(--cyan-light); }
|
|
140
204
|
.stat-icon.violet { color: #46647b; background: var(--violet-light); }
|
|
141
205
|
.stat-icon.green { color: #246a43; background: var(--green-light); }
|
|
142
206
|
.stat-icon.amber { color: #8a5a0f; background: var(--amber-light); }
|
|
143
207
|
.dashboard-grid { display: grid; grid-template-columns: minmax(0, 1.65fr) minmax(280px, .7fr); gap: 18px; align-items: start; }
|
|
208
|
+
.dashboard-panel { min-width: 0; overflow: hidden; box-shadow: var(--shadow-low); }
|
|
144
209
|
.panel { min-width: 0; overflow: hidden; }
|
|
145
210
|
.panel-heading { min-height: 72px; padding: 18px 20px; border-bottom: 1px solid #e8eaf0; display: flex; justify-content: space-between; align-items: center; gap: 16px; }
|
|
146
211
|
.panel-heading h2, .panel-heading p { margin-bottom: 0; }
|
|
147
212
|
.panel-heading p { margin-top: 2px; font-size: 12px; }
|
|
148
213
|
.text-link, .back-link { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 750; }
|
|
149
214
|
.back-link { margin-bottom: 13px; }
|
|
150
|
-
.
|
|
215
|
+
.panel-error { padding: 18px 20px; }
|
|
151
216
|
.check-list { margin: 0; padding: 8px 20px 18px; list-style: none; }
|
|
152
217
|
.check-list li { padding: 14px 0; border-bottom: 1px solid #eceef3; display: flex; align-items: center; gap: 11px; }
|
|
153
218
|
.check-list svg { color: var(--green); }
|
|
@@ -155,7 +220,7 @@ code { overflow-wrap: anywhere; color: #246a43; font-family: "SFMono-Regular", C
|
|
|
155
220
|
.check-list span { display: grid; gap: 3px; }
|
|
156
221
|
.check-list strong { font-size: 13px; }
|
|
157
222
|
.check-list small { color: var(--muted); font-size: 11px; }
|
|
158
|
-
.security-
|
|
223
|
+
.security-action { padding: 0 20px 20px; }
|
|
159
224
|
|
|
160
225
|
.table-wrap { max-width: 100%; overflow-x: auto; }
|
|
161
226
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
@@ -210,6 +275,11 @@ tbody tr:hover { background: #fbfcfd; }
|
|
|
210
275
|
|
|
211
276
|
.auth-layout { min-height: 100vh; display: grid; grid-template-columns: minmax(360px, .8fr) minmax(500px, 1.2fr); background: #fff; }
|
|
212
277
|
.auth-brand-panel { min-height: 100vh; padding: 34px 42px; position: relative; overflow: hidden; display: flex; flex-direction: column; color: #fff; background: var(--navy); border-right: 1px solid #286342; }
|
|
278
|
+
.auth-top-brand { min-width: 0; display: flex; align-items: center; gap: 10px; color: #ffffff; font-size: 15px; font-weight: 800; }
|
|
279
|
+
.auth-top-brand:hover { color: #ffffff; }
|
|
280
|
+
.auth-top-brand img { width: 44px; height: 44px; margin: -5px; flex: 0 0 auto; }
|
|
281
|
+
.auth-top-brand span { min-width: 0; display: grid; }
|
|
282
|
+
.auth-top-brand small { color: #93b5a1; font-size: 10px; font-weight: 600; }
|
|
213
283
|
.auth-brand-copy { max-width: 470px; margin: auto 0; position: relative; z-index: 1; }
|
|
214
284
|
.auth-logo { width: 220px; height: 220px; margin: 0 0 2px -34px; display: block; }
|
|
215
285
|
.security-mark { margin-bottom: 18px; display: block; color: #7ce8aa; font-family: "SFMono-Regular", Consolas, monospace; font-size: 11px; font-weight: 800; }
|
|
@@ -217,14 +287,18 @@ tbody tr:hover { background: #fbfcfd; }
|
|
|
217
287
|
.auth-brand-copy p { max-width: 390px; margin: 17px 0 0; color: #b4cbbb; font-size: 15px; }
|
|
218
288
|
.auth-signal { display: flex; align-items: center; gap: 8px; color: #93b29f; font-size: 11px; }
|
|
219
289
|
.signal-dot { width: 8px; height: 8px; border-radius: 50%; background: #7ce8aa; box-shadow: 0 0 0 4px rgba(124,232,170,.12); }
|
|
220
|
-
.auth-form-panel { min-width: 0; padding: 50px 7vw; display: grid; place-items: center; }
|
|
221
|
-
.auth-form-
|
|
290
|
+
.auth-form-panel { min-width: 0; padding: 50px 7vw; display: grid; place-items: center; background: radial-gradient(circle at 100% 0%, rgba(33, 134, 83, .08), transparent 32%), #f8faf8; }
|
|
291
|
+
.auth-form-card { width: min(100%, 480px); box-shadow: 0 20px 55px rgba(18, 53, 36, .1); }
|
|
292
|
+
.auth-form-wrap { width: 100%; padding: 34px; }
|
|
222
293
|
.auth-form-wrap > header { margin-bottom: 27px; }
|
|
223
294
|
.auth-form-wrap > header h2 { margin-bottom: 6px; font-size: 26px; }
|
|
224
295
|
.auth-form-wrap > header p { margin: 0; font-size: 14px; }
|
|
225
296
|
.auth-form-wrap > footer { margin-top: 24px; color: #71788a; font-size: 13px; text-align: center; }
|
|
226
297
|
.auth-form-wrap > footer a { display: inline-flex; align-items: center; gap: 5px; font-weight: 700; }
|
|
227
298
|
.auth-form-wrap .notice { margin-bottom: 17px; }
|
|
299
|
+
.login-alert { margin-bottom: 17px; }
|
|
300
|
+
.login-form { gap: 18px; }
|
|
301
|
+
.login-recovery { margin-top: -7px; display: flex; justify-content: flex-end; font-size: 13px; font-weight: 700; }
|
|
228
302
|
.divider { margin: 22px 0; display: flex; align-items: center; gap: 12px; color: #9197a5; font-size: 11px; }
|
|
229
303
|
.divider::before, .divider::after { content: ""; height: 1px; flex: 1; background: #e2e5eb; }
|
|
230
304
|
.oauth-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
|
@@ -240,21 +314,19 @@ tbody tr:hover { background: #fbfcfd; }
|
|
|
240
314
|
.auth-brand-copy h1 { font-size: 32px; }
|
|
241
315
|
}
|
|
242
316
|
|
|
243
|
-
@media (max-width:
|
|
244
|
-
.
|
|
245
|
-
.
|
|
246
|
-
.mobile-scrim { display: block; position: fixed; inset: 0; z-index: 39; background: rgba(11,35,23,.58); }
|
|
247
|
-
.mobile-sidebar { width: min(84vw, 280px); padding: 20px 14px; position: fixed; inset: 0 auto 0 0; z-index: 40; display: flex; flex-direction: column; transform: translateX(-105%); transition: transform 180ms ease; }
|
|
248
|
-
.mobile-sidebar.is-open { transform: translateX(0); }
|
|
249
|
-
.topbar { height: 58px; padding: 0 16px; }
|
|
250
|
-
.menu-button { display: inline-flex; }
|
|
317
|
+
@media (max-width: 768px) {
|
|
318
|
+
.workspace-topbar { min-height: 58px; }
|
|
319
|
+
.current-section { display: none; }
|
|
251
320
|
.page { padding: 22px 16px 36px; }
|
|
252
321
|
.page-header { min-height: 0; margin-bottom: 18px; display: grid; }
|
|
253
322
|
.page-header h1 { font-size: 24px; }
|
|
254
323
|
.page-actions { justify-content: flex-start; }
|
|
324
|
+
.dashboard-page { gap: 16px; }
|
|
325
|
+
.dashboard-hero { min-height: 230px; padding: 26px 24px; display: grid; align-content: end; }
|
|
326
|
+
.hero-actions { justify-content: flex-start; }
|
|
255
327
|
.stat-grid { grid-template-columns: 1fr 1fr; gap: 10px; }
|
|
256
|
-
.stat-card { min-height:
|
|
257
|
-
.stat-
|
|
328
|
+
.stat-card { min-height: 104px; padding: 14px; }
|
|
329
|
+
.stat-copy strong { font-size: 19px; }
|
|
258
330
|
.settings-layout { grid-template-columns: 1fr; gap: 16px; }
|
|
259
331
|
.settings-nav { grid-template-columns: 1fr 1fr; }
|
|
260
332
|
.settings-nav a { border-left: 0; border-bottom: 3px solid transparent; text-align: center; }
|
|
@@ -270,11 +342,15 @@ tbody tr:hover { background: #fbfcfd; }
|
|
|
270
342
|
.auth-signal { display: none; }
|
|
271
343
|
.security-mark { margin-bottom: 10px; }
|
|
272
344
|
.auth-form-panel { min-height: calc(100vh - 220px); padding: 34px 20px; align-items: start; }
|
|
345
|
+
.auth-form-wrap { padding: 28px 24px; }
|
|
273
346
|
th, td { padding: 12px 14px; }
|
|
274
347
|
}
|
|
275
348
|
|
|
276
349
|
@media (max-width: 480px) {
|
|
277
350
|
.stat-grid { grid-template-columns: 1fr; }
|
|
351
|
+
.dashboard-hero { padding: 24px 20px; }
|
|
352
|
+
.hero-copy h1 { font-size: 27px; }
|
|
353
|
+
.hero-actions { display: grid; }
|
|
278
354
|
.oauth-grid { grid-template-columns: 1fr; }
|
|
279
355
|
.form-panel, .settings-panel { padding: 18px; }
|
|
280
356
|
.form-actions, .action-row { justify-content: stretch; }
|