mirror of
https://github.com/Nezumi-2711/uptime-monitoring.git
synced 2026-09-22 13:48:31 +00:00
fix: improve the dashboard ui
This commit is contained in:
@@ -36,8 +36,8 @@ const formatUptimeTooltip: NonNullable<ComponentProps<typeof ChartTooltipContent
|
||||
const point = item.payload as UptimeDatum;
|
||||
return (
|
||||
<div className="flex w-full items-center justify-between gap-5">
|
||||
<span className="text-[#777]">Successful</span>
|
||||
<span className="font-mono font-medium tabular-nums text-[#171717]">
|
||||
<span className="text-(--muted)">Successful</span>
|
||||
<span className="font-mono font-medium tabular-nums text-(--ink)">
|
||||
{point.successfulChecks}/{point.totalChecks}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -31,14 +31,14 @@ export function AiComposeField({
|
||||
<div className="ai-compose-pane">
|
||||
<div className="ai-compose-pane-header">
|
||||
<label htmlFor="incident-note">Internal note</label>
|
||||
<small>Admin only</small>
|
||||
<small>Admin only · {note.length}/1000</small>
|
||||
</div>
|
||||
<textarea
|
||||
id="incident-note"
|
||||
value={note}
|
||||
onChange={(event) => onNoteChange(event.target.value)}
|
||||
maxLength={1000}
|
||||
placeholder="redis full memory, scaling capacity"
|
||||
placeholder="e.g. Redis is at capacity; scaling is in progress"
|
||||
/>
|
||||
{enabled ? (
|
||||
<Button
|
||||
@@ -64,12 +64,13 @@ export function AiComposeField({
|
||||
<div className="ai-compose-pane">
|
||||
<div className="ai-compose-pane-header">
|
||||
<label htmlFor="incident-public-body">Public update</label>
|
||||
<small>Customer-facing</small>
|
||||
<small>Customer-facing · {body.length}/2000</small>
|
||||
</div>
|
||||
<textarea
|
||||
id="incident-public-body"
|
||||
value={body}
|
||||
onChange={(event) => onBodyChange(event.target.value)}
|
||||
placeholder="Explain the impact and what your team is doing"
|
||||
maxLength={2000}
|
||||
required
|
||||
aria-busy={isPending}
|
||||
|
||||
@@ -29,6 +29,7 @@ export function IncidentDialog({ onClose }: { onClose(): void }) {
|
||||
monitorIds: [],
|
||||
});
|
||||
const [generated, setGenerated] = useState(false);
|
||||
const availableMonitors = monitors.data?.monitors ?? [];
|
||||
function generate() {
|
||||
draft.mutate(
|
||||
{ note: form.note, status: form.status, monitorIds: form.monitorIds },
|
||||
@@ -46,94 +47,106 @@ export function IncidentDialog({ onClose }: { onClose(): void }) {
|
||||
}
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => !open && !create.isPending && onClose()}>
|
||||
<DialogContent className="incident-dialog max-h-[90vh] w-[calc(100%-2rem)] overflow-y-auto sm:max-w-2xl">
|
||||
<DialogContent className="incident-dialog">
|
||||
<DialogHeader>
|
||||
<p className="overline">Incident management</p>
|
||||
<DialogTitle>Declare incident</DialogTitle>
|
||||
<DialogDescription>Turn a short internal note into a clear customer-facing update.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form className="incident-form" onSubmit={submit}>
|
||||
<div className="incident-form-grid">
|
||||
<div className="field">
|
||||
<span id="incident-status-label">Status</span>
|
||||
<Select value={form.status} onValueChange={(status) => setForm({ ...form, status: status as IncidentStatus })}>
|
||||
<SelectTrigger aria-labelledby="incident-status-label">
|
||||
<SelectValue>
|
||||
<IncidentStatusOption value={form.status} />
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{INCIDENT_STATUSES.map((status) => (
|
||||
<SelectItem key={status} value={status}>
|
||||
<IncidentStatusOption value={status} />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="incident-form-body">
|
||||
<div className="incident-form-grid">
|
||||
<div className="field">
|
||||
<span id="incident-status-label">Status</span>
|
||||
<Select value={form.status} onValueChange={(status) => setForm({ ...form, status: status as IncidentStatus })}>
|
||||
<SelectTrigger aria-labelledby="incident-status-label">
|
||||
<SelectValue>
|
||||
<IncidentStatusOption value={form.status} />
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{INCIDENT_STATUSES.map((status) => (
|
||||
<SelectItem key={status} value={status}>
|
||||
<IncidentStatusOption value={status} />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="field">
|
||||
<span id="incident-impact-label">Impact</span>
|
||||
<Select value={form.impact} onValueChange={(impact) => setForm({ ...form, impact: impact as IncidentImpact })}>
|
||||
<SelectTrigger aria-labelledby="incident-impact-label">
|
||||
<SelectValue>
|
||||
<IncidentImpactOption value={form.impact} />
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{INCIDENT_IMPACTS.map((impact) => (
|
||||
<SelectItem key={impact} value={impact}>
|
||||
<IncidentImpactOption value={impact} />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<label className="field incident-title-field" htmlFor="incident-title">
|
||||
<span>Public title</span>
|
||||
<Input
|
||||
id="incident-title"
|
||||
value={form.title}
|
||||
onChange={(event) => setForm({ ...form, title: event.target.value })}
|
||||
placeholder="Briefly describe the disruption"
|
||||
maxLength={120}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<span id="incident-impact-label">Impact</span>
|
||||
<Select value={form.impact} onValueChange={(impact) => setForm({ ...form, impact: impact as IncidentImpact })}>
|
||||
<SelectTrigger aria-labelledby="incident-impact-label">
|
||||
<SelectValue>
|
||||
<IncidentImpactOption value={form.impact} />
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{INCIDENT_IMPACTS.map((impact) => (
|
||||
<SelectItem key={impact} value={impact}>
|
||||
<IncidentImpactOption value={impact} />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<label className="field incident-title-field" htmlFor="incident-title">
|
||||
<span>Public title</span>
|
||||
<Input
|
||||
id="incident-title"
|
||||
value={form.title}
|
||||
onChange={(event) => setForm({ ...form, title: event.target.value })}
|
||||
maxLength={120}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<fieldset className="incident-service-picker">
|
||||
<legend>
|
||||
Affected services <small>Optional</small>
|
||||
</legend>
|
||||
<div className="incident-service-options">
|
||||
{availableMonitors.length ? (
|
||||
availableMonitors.map((monitor) => (
|
||||
<label key={monitor.id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={form.monitorIds.includes(monitor.id)}
|
||||
onChange={(event) =>
|
||||
setForm({
|
||||
...form,
|
||||
monitorIds: event.target.checked
|
||||
? [...form.monitorIds, monitor.id]
|
||||
: form.monitorIds.filter((id) => id !== monitor.id),
|
||||
})
|
||||
}
|
||||
/>
|
||||
<span>{monitor.name}</span>
|
||||
</label>
|
||||
))
|
||||
) : (
|
||||
<p className="incident-service-empty">{monitors.isPending ? 'Loading services…' : 'No monitors available'}</p>
|
||||
)}
|
||||
</div>
|
||||
</fieldset>
|
||||
<AiComposeField
|
||||
note={form.note}
|
||||
body={form.body}
|
||||
onNoteChange={(note) => setForm({ ...form, note })}
|
||||
onBodyChange={(body) => setForm({ ...form, body })}
|
||||
onGenerate={generate}
|
||||
isPending={draft.isPending}
|
||||
error={draft.error}
|
||||
generated={generated}
|
||||
/>
|
||||
{create.isError && (
|
||||
<p className="form-error" role="alert">
|
||||
{create.error.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<fieldset className="incident-service-picker">
|
||||
<legend>
|
||||
Affected services <small>Optional</small>
|
||||
</legend>
|
||||
<div className="incident-service-options">
|
||||
{monitors.data?.monitors.map((monitor) => (
|
||||
<label key={monitor.id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={form.monitorIds.includes(monitor.id)}
|
||||
onChange={(event) =>
|
||||
setForm({
|
||||
...form,
|
||||
monitorIds: event.target.checked
|
||||
? [...form.monitorIds, monitor.id]
|
||||
: form.monitorIds.filter((id) => id !== monitor.id),
|
||||
})
|
||||
}
|
||||
/>
|
||||
<span>{monitor.name}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
<AiComposeField
|
||||
note={form.note}
|
||||
body={form.body}
|
||||
onNoteChange={(note) => setForm({ ...form, note })}
|
||||
onBodyChange={(body) => setForm({ ...form, body })}
|
||||
onGenerate={generate}
|
||||
isPending={draft.isPending}
|
||||
error={draft.error}
|
||||
generated={generated}
|
||||
/>
|
||||
<div className="form-actions compact-actions">
|
||||
<div className="incident-dialog-actions">
|
||||
<Button variant="unstyled" className="secondary-button" type="button" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
@@ -141,11 +154,6 @@ export function IncidentDialog({ onClose }: { onClose(): void }) {
|
||||
{create.isPending ? 'Publishing…' : 'Declare incident'}
|
||||
</Button>
|
||||
</div>
|
||||
{create.isError && (
|
||||
<p className="form-error" role="alert">
|
||||
{create.error.message}
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -20,51 +20,58 @@ export function IncidentUpdateDialog({ incident, onClose }: { incident: Incident
|
||||
}
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => !open && !post.isPending && onClose()}>
|
||||
<DialogContent className="incident-dialog max-h-[90vh] w-[calc(100%-2rem)] overflow-y-auto sm:max-w-2xl">
|
||||
<DialogContent className="incident-dialog">
|
||||
<DialogHeader>
|
||||
<p className="overline">Incident update</p>
|
||||
<DialogTitle>{incident.title ?? 'Service disruption'}</DialogTitle>
|
||||
<DialogDescription>Publish the next update and advance the incident lifecycle.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form className="incident-form" onSubmit={submit}>
|
||||
<div className="field">
|
||||
<span id="update-status-label">Status</span>
|
||||
<Select value={status} onValueChange={(value) => setStatus(value as IncidentStatus)}>
|
||||
<SelectTrigger aria-labelledby="update-status-label">
|
||||
<SelectValue>
|
||||
<IncidentStatusOption value={status} />
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{INCIDENT_STATUSES.map((value) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
<IncidentStatusOption value={value} />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<AiComposeField
|
||||
note={note}
|
||||
body={body}
|
||||
onNoteChange={setNote}
|
||||
onBodyChange={setBody}
|
||||
onGenerate={() =>
|
||||
draft.mutate(
|
||||
{ note, status },
|
||||
{
|
||||
onSuccess: (result) => {
|
||||
setBody(result.body);
|
||||
setGenerated(true);
|
||||
<div className="incident-form-body">
|
||||
<div className="field incident-update-status">
|
||||
<span id="update-status-label">Status</span>
|
||||
<Select value={status} onValueChange={(value) => setStatus(value as IncidentStatus)}>
|
||||
<SelectTrigger aria-labelledby="update-status-label">
|
||||
<SelectValue>
|
||||
<IncidentStatusOption value={status} />
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{INCIDENT_STATUSES.map((value) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
<IncidentStatusOption value={value} />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<AiComposeField
|
||||
note={note}
|
||||
body={body}
|
||||
onNoteChange={setNote}
|
||||
onBodyChange={setBody}
|
||||
onGenerate={() =>
|
||||
draft.mutate(
|
||||
{ note, status },
|
||||
{
|
||||
onSuccess: (result) => {
|
||||
setBody(result.body);
|
||||
setGenerated(true);
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
isPending={draft.isPending}
|
||||
error={draft.error}
|
||||
generated={generated}
|
||||
/>
|
||||
<div className="form-actions compact-actions">
|
||||
)
|
||||
}
|
||||
isPending={draft.isPending}
|
||||
error={draft.error}
|
||||
generated={generated}
|
||||
/>
|
||||
{post.isError && (
|
||||
<p className="form-error" role="alert">
|
||||
{post.error.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="incident-dialog-actions">
|
||||
<Button variant="unstyled" className="secondary-button" type="button" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
@@ -72,11 +79,6 @@ export function IncidentUpdateDialog({ incident, onClose }: { incident: Incident
|
||||
{post.isPending ? 'Publishing…' : status === 'resolved' ? 'Resolve incident' : 'Post update'}
|
||||
</Button>
|
||||
</div>
|
||||
{post.isError && (
|
||||
<p className="form-error" role="alert">
|
||||
{post.error.message}
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -1815,6 +1815,13 @@ button {
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* Semantic text and surface tokens used by both page styles and shared UI. */
|
||||
--ink: #f3f4f6;
|
||||
--muted: #aeb4bf;
|
||||
--faint: #858d9a;
|
||||
--hairline: rgb(255 255 255 / 0.12);
|
||||
--soft: #171a20;
|
||||
--primary-deep: #3ecf8e;
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
@@ -1826,7 +1833,7 @@ button {
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--shadcn-muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--muted-foreground: oklch(0.76 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
|
||||
@@ -498,35 +498,63 @@
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.incident-dialog {
|
||||
display: flex;
|
||||
width: min(860px, calc(100% - 32px));
|
||||
max-width: 860px;
|
||||
max-height: min(90dvh, 820px);
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 14px;
|
||||
}
|
||||
.incident-dialog [data-slot='dialog-header'] {
|
||||
gap: 5px;
|
||||
padding-right: 36px;
|
||||
padding-bottom: 18px;
|
||||
padding: 24px 64px 20px 28px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: linear-gradient(180deg, #fff, #fdfdfd);
|
||||
}
|
||||
.incident-dialog [data-slot='dialog-header'] .overline {
|
||||
margin-bottom: 3px;
|
||||
color: #27845e;
|
||||
}
|
||||
.incident-dialog [data-slot='dialog-title'] {
|
||||
font-size: 19px;
|
||||
font-size: 21px;
|
||||
line-height: 1.25;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
.incident-form {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 20px;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.incident-form > * {
|
||||
grid-column: 1;
|
||||
.incident-form-body {
|
||||
display: grid;
|
||||
min-height: 0;
|
||||
padding: 24px 28px 28px;
|
||||
gap: 22px;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
.incident-form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 2fr);
|
||||
grid-template-columns: minmax(150px, 0.85fr) minmax(150px, 0.85fr) minmax(260px, 1.6fr);
|
||||
gap: 16px;
|
||||
}
|
||||
.incident-form-grid [data-slot='select-trigger'],
|
||||
.incident-update-status [data-slot='select-trigger'] {
|
||||
width: 100%;
|
||||
}
|
||||
.incident-title-field {
|
||||
min-width: 0;
|
||||
}
|
||||
.incident-title-field input::placeholder {
|
||||
font-size: 13px;
|
||||
}
|
||||
.incident-select-option {
|
||||
display: inline-flex;
|
||||
min-width: 0;
|
||||
@@ -581,7 +609,7 @@
|
||||
background: #fee9e7;
|
||||
}
|
||||
.incident-form textarea {
|
||||
min-height: 138px;
|
||||
min-height: 154px;
|
||||
width: 100%;
|
||||
resize: vertical;
|
||||
border: 1px solid var(--border);
|
||||
@@ -628,6 +656,7 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
min-height: 52px;
|
||||
max-height: 104px;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
@@ -664,11 +693,23 @@
|
||||
height: 14px;
|
||||
accent-color: var(--primary-deep);
|
||||
}
|
||||
.incident-service-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 30px;
|
||||
margin: 0;
|
||||
padding-inline: 2px;
|
||||
font-size: 13px;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
.ai-compose-field {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.ai-compose-field > .form-error {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.ai-compose-pane {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
@@ -688,6 +729,7 @@
|
||||
}
|
||||
.ai-compose-pane-header small {
|
||||
margin-left: 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ai-compose-button {
|
||||
@@ -703,23 +745,31 @@
|
||||
}
|
||||
.ai-compose-hint button {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: var(--primary);
|
||||
color: #17865b;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.incident-form > .form-actions {
|
||||
align-items: center;
|
||||
margin-top: 2px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
.incident-update-status {
|
||||
max-width: 260px;
|
||||
}
|
||||
.incident-form > .form-actions button {
|
||||
.incident-dialog-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex: 0 0 auto;
|
||||
gap: 10px;
|
||||
padding: 16px 28px;
|
||||
border-top: 1px solid var(--border);
|
||||
background: #fafafa;
|
||||
}
|
||||
.incident-dialog-actions button {
|
||||
min-height: 40px;
|
||||
padding-inline: 18px;
|
||||
}
|
||||
.incident-form > .form-actions .secondary-button {
|
||||
.incident-dialog-actions .secondary-button {
|
||||
border-color: #d4d4d4;
|
||||
color: #444;
|
||||
background: #fff;
|
||||
@@ -727,12 +777,12 @@
|
||||
0 1px 2px rgb(0 0 0 / 0.05),
|
||||
inset 0 1px rgb(255 255 255 / 0.7);
|
||||
}
|
||||
.incident-form > .form-actions .secondary-button:hover:not(:disabled) {
|
||||
.incident-dialog-actions .secondary-button:hover:not(:disabled) {
|
||||
border-color: #aaa;
|
||||
color: var(--foreground);
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.incident-form > .form-actions .primary-button {
|
||||
.incident-dialog-actions .primary-button {
|
||||
min-width: 148px;
|
||||
box-shadow:
|
||||
0 1px 2px rgb(0 0 0 / 0.08),
|
||||
@@ -838,7 +888,19 @@
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.incident-dialog {
|
||||
padding: 22px;
|
||||
width: calc(100% - 24px);
|
||||
max-height: calc(100dvh - 24px);
|
||||
border-radius: 12px;
|
||||
}
|
||||
.incident-dialog [data-slot='dialog-header'] {
|
||||
padding: 21px 58px 18px 20px;
|
||||
}
|
||||
.incident-form-body {
|
||||
padding: 20px;
|
||||
gap: 20px;
|
||||
}
|
||||
.incident-dialog-actions {
|
||||
padding: 14px 20px;
|
||||
}
|
||||
.dashboard-incident-list article {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -890,22 +952,50 @@
|
||||
}
|
||||
|
||||
@media (max-width: 440px) {
|
||||
.incident-dialog {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
max-height: calc(100dvh - 8px);
|
||||
translate: -50% 0;
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
.incident-dialog [data-slot='dialog-header'] {
|
||||
padding: 20px 52px 17px 18px;
|
||||
}
|
||||
.incident-dialog [data-slot='dialog-title'] {
|
||||
font-size: 20px;
|
||||
}
|
||||
.incident-form-body {
|
||||
padding: 18px;
|
||||
gap: 18px;
|
||||
}
|
||||
.incident-form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.incident-title-field {
|
||||
grid-column: auto;
|
||||
}
|
||||
.incident-form > .form-actions {
|
||||
gap: 8px;
|
||||
.incident-form textarea {
|
||||
min-height: 132px;
|
||||
}
|
||||
.incident-form > .form-actions button {
|
||||
.incident-dialog-actions {
|
||||
gap: 8px;
|
||||
padding: 12px 18px max(12px, env(safe-area-inset-bottom));
|
||||
}
|
||||
.incident-dialog-actions button {
|
||||
flex: 1;
|
||||
padding-inline: 12px;
|
||||
}
|
||||
.incident-form > .form-actions .primary-button {
|
||||
.incident-dialog-actions .primary-button {
|
||||
min-width: 0;
|
||||
}
|
||||
.ai-compose-pane-header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 380px) {
|
||||
@@ -1062,7 +1152,7 @@
|
||||
}
|
||||
|
||||
.dark .metric-card span {
|
||||
color: #6b7280;
|
||||
color: var(--faint);
|
||||
}
|
||||
|
||||
/* Services / Monitor List Panel */
|
||||
@@ -1127,7 +1217,7 @@
|
||||
}
|
||||
|
||||
.dark .monitor-meta {
|
||||
color: #6b7280;
|
||||
color: var(--faint);
|
||||
}
|
||||
|
||||
.dark .monitor-result code {
|
||||
@@ -1234,7 +1324,7 @@
|
||||
/* Dashboard Page Footer */
|
||||
.dark .dashboard-page-footer {
|
||||
border-top-color: rgba(255, 255, 255, 0.08);
|
||||
color: #6b7280;
|
||||
color: var(--faint);
|
||||
}
|
||||
|
||||
/* Dialogs & Forms in Dark Mode (Add/Edit Monitor, Incident, Delete) */
|
||||
@@ -1335,8 +1425,31 @@
|
||||
}
|
||||
|
||||
/* Incident Dialog specifics */
|
||||
.dark [data-slot='dialog-overlay'] {
|
||||
background: rgba(0, 0, 0, 0.58);
|
||||
}
|
||||
|
||||
.dark .incident-dialog [data-slot='dialog-header'] {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.08);
|
||||
background: linear-gradient(180deg, #111419, #101318);
|
||||
}
|
||||
|
||||
.dark .incident-dialog-actions {
|
||||
border-top-color: rgba(255, 255, 255, 0.08);
|
||||
background: #0e1116;
|
||||
}
|
||||
|
||||
.dark .incident-dialog-actions .secondary-button {
|
||||
border-color: rgba(255, 255, 255, 0.14);
|
||||
color: #d1d5db;
|
||||
background: #181c23;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.dark .incident-dialog-actions .secondary-button:hover:not(:disabled) {
|
||||
border-color: rgba(255, 255, 255, 0.24);
|
||||
color: #f9fafb;
|
||||
background: #20252e;
|
||||
}
|
||||
|
||||
.dark .incident-service-options {
|
||||
@@ -1361,6 +1474,10 @@
|
||||
background: rgba(62, 207, 142, 0.12);
|
||||
}
|
||||
|
||||
.dark .incident-service-empty {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.dark .ai-compose-pane-header label {
|
||||
color: #d1d5db;
|
||||
}
|
||||
@@ -1539,7 +1656,7 @@
|
||||
}
|
||||
|
||||
.dark .metric-card span {
|
||||
color: #6b7280;
|
||||
color: var(--faint);
|
||||
}
|
||||
|
||||
/* Services / Monitor List Panel */
|
||||
@@ -1604,7 +1721,7 @@
|
||||
}
|
||||
|
||||
.dark .monitor-meta {
|
||||
color: #6b7280;
|
||||
color: var(--faint);
|
||||
}
|
||||
|
||||
.dark .monitor-result code {
|
||||
@@ -1711,7 +1828,7 @@
|
||||
/* Dashboard Page Footer */
|
||||
.dark .dashboard-page-footer {
|
||||
border-top-color: rgba(255, 255, 255, 0.08);
|
||||
color: #6b7280;
|
||||
color: var(--faint);
|
||||
}
|
||||
|
||||
/* Dialogs & Forms in Dark Mode (Add/Edit Monitor, Incident, Delete) */
|
||||
|
||||
+259
-259
@@ -1,259 +1,259 @@
|
||||
.auth-page {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
justify-items: center;
|
||||
min-height: 100dvh;
|
||||
padding: 32px 24px 24px;
|
||||
background: radial-gradient(circle at 50% 38%, rgb(62 207 142 / 0.08), transparent 34%), linear-gradient(#fff, #fcfcfc);
|
||||
transition:
|
||||
background 0.25s ease,
|
||||
color 0.25s ease;
|
||||
}
|
||||
.dark .auth-page {
|
||||
background:
|
||||
radial-gradient(ellipse 75% 55% at 50% 30%, rgba(62, 207, 142, 0.12), transparent 65%),
|
||||
radial-gradient(ellipse 40% 30% at 50% 12%, rgba(36, 180, 126, 0.07), transparent 50%),
|
||||
linear-gradient(180deg, #0c0e10 0%, #070809 100%);
|
||||
}
|
||||
.auth-theme-toggle {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 28px;
|
||||
right: 28px;
|
||||
transition:
|
||||
color 0.15s ease,
|
||||
background 0.15s ease,
|
||||
border-color 0.15s ease;
|
||||
}
|
||||
.dark .auth-theme-toggle {
|
||||
color: #9ca3af;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.dark .auth-theme-toggle:hover:not(:disabled) {
|
||||
color: #3ecf8e;
|
||||
background: rgba(62, 207, 142, 0.1);
|
||||
border-color: rgba(62, 207, 142, 0.25);
|
||||
}
|
||||
.auth-page::before {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
background-image: radial-gradient(#dcdcdc 0.65px, transparent 0.65px);
|
||||
background-size: 16px 16px;
|
||||
mask-image: linear-gradient(to bottom, transparent, black 24%, transparent 76%);
|
||||
content: '';
|
||||
}
|
||||
.dark .auth-page::before {
|
||||
background-image: radial-gradient(rgba(255, 255, 255, 0.07) 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
mask-image: radial-gradient(ellipse 70% 60% at 50% 40%, black 25%, transparent 80%);
|
||||
}
|
||||
.auth-brand {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.6px;
|
||||
}
|
||||
.dark .auth-brand {
|
||||
color: #ffffff;
|
||||
}
|
||||
.auth-brand-mark {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
color: var(--primary-deep);
|
||||
}
|
||||
.dark .auth-brand-mark {
|
||||
color: #3ecf8e;
|
||||
filter: drop-shadow(0 0 12px rgba(62, 207, 142, 0.45));
|
||||
}
|
||||
.auth-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
align-self: center;
|
||||
width: min(100%, 420px);
|
||||
padding: 36px;
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: 12px;
|
||||
background: rgb(255 255 255 / 0.96);
|
||||
box-shadow:
|
||||
0 18px 55px rgb(24 74 52 / 0.08),
|
||||
0 2px 8px rgb(0 0 0 / 0.04);
|
||||
animation: enter 500ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
}
|
||||
.dark .auth-card {
|
||||
background: rgba(17, 19, 23, 0.82);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05),
|
||||
0 24px 64px -12px rgba(0, 0, 0, 0.8),
|
||||
0 0 45px -10px rgba(62, 207, 142, 0.08),
|
||||
inset 0 1px 0 0 rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
.auth-heading p {
|
||||
margin: 0 0 12px;
|
||||
font:
|
||||
500 10px/1.4 'IBM Plex Mono',
|
||||
monospace;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: #55816e;
|
||||
}
|
||||
.dark .auth-heading p {
|
||||
color: #3ecf8e;
|
||||
}
|
||||
.auth-heading h1 {
|
||||
margin: 0;
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
line-height: 1.16;
|
||||
letter-spacing: -1.2px;
|
||||
}
|
||||
.dark .auth-heading h1 {
|
||||
color: #f9fafb;
|
||||
}
|
||||
.auth-heading > span {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
font-size: 19px;
|
||||
line-height: 1.55;
|
||||
color: #626262;
|
||||
}
|
||||
.dark .auth-heading > span {
|
||||
color: #9ca3af;
|
||||
}
|
||||
.auth-form {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.auth-field {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
.auth-field label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #353535;
|
||||
}
|
||||
.dark .auth-field label {
|
||||
color: #d1d5db;
|
||||
}
|
||||
.dark .auth-field input {
|
||||
background-color: rgba(11, 13, 15, 0.85);
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
color: #f9fafb;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.4);
|
||||
transition:
|
||||
border-color 0.15s ease,
|
||||
box-shadow 0.15s ease;
|
||||
}
|
||||
.dark .auth-field input:hover {
|
||||
border-color: rgba(255, 255, 255, 0.24);
|
||||
}
|
||||
.dark .auth-field input:focus-visible {
|
||||
border-color: #3ecf8e;
|
||||
box-shadow:
|
||||
0 0 0 3px rgba(62, 207, 142, 0.2),
|
||||
inset 0 1px 2px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.auth-error {
|
||||
margin: -4px 0 0;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #efcaca;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: #9f2f2f;
|
||||
background: #fff6f6;
|
||||
}
|
||||
.dark .auth-error {
|
||||
background: rgba(239, 68, 68, 0.12);
|
||||
border-color: rgba(239, 68, 68, 0.28);
|
||||
color: #fca5a5;
|
||||
}
|
||||
.auth-submit {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #35c586;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
background: var(--primary);
|
||||
box-shadow:
|
||||
0 1px 2px rgb(0 0 0 / 0.08),
|
||||
inset 0 1px rgb(255 255 255 / 0.2);
|
||||
cursor: pointer;
|
||||
}
|
||||
.auth-submit:hover:not(:disabled) {
|
||||
background: #36c487;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.auth-submit:active:not(:disabled) {
|
||||
background: var(--primary-deep);
|
||||
transform: translateY(1px);
|
||||
}
|
||||
.auth-submit:disabled {
|
||||
cursor: wait;
|
||||
opacity: 0.62;
|
||||
}
|
||||
.dark .auth-submit {
|
||||
background: #3ecf8e;
|
||||
border-color: #3ecf8e;
|
||||
color: #090a0c;
|
||||
font-weight: 600;
|
||||
box-shadow:
|
||||
0 1px 2px rgba(0, 0, 0, 0.3),
|
||||
0 4px 14px rgba(62, 207, 142, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.35);
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.dark .auth-submit:hover:not(:disabled) {
|
||||
background: #4ae09e;
|
||||
border-color: #4ae09e;
|
||||
box-shadow:
|
||||
0 4px 20px rgba(62, 207, 142, 0.45),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.dark .auth-submit:active:not(:disabled) {
|
||||
background: #24b47e;
|
||||
transform: translateY(1px);
|
||||
}
|
||||
.auth-footnote {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
color: #858585;
|
||||
}
|
||||
.dark .auth-footnote {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
@media (max-width: 440px) {
|
||||
.auth-page {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
.auth-card {
|
||||
padding: 28px 22px;
|
||||
}
|
||||
.auth-heading h1 {
|
||||
font-size: 27px;
|
||||
}
|
||||
}
|
||||
.auth-page {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
justify-items: center;
|
||||
min-height: 100dvh;
|
||||
padding: 32px 24px 24px;
|
||||
background: radial-gradient(circle at 50% 38%, rgb(62 207 142 / 0.08), transparent 34%), linear-gradient(#fff, #fcfcfc);
|
||||
transition:
|
||||
background 0.25s ease,
|
||||
color 0.25s ease;
|
||||
}
|
||||
.dark .auth-page {
|
||||
background:
|
||||
radial-gradient(ellipse 75% 55% at 50% 30%, rgba(62, 207, 142, 0.12), transparent 65%),
|
||||
radial-gradient(ellipse 40% 30% at 50% 12%, rgba(36, 180, 126, 0.07), transparent 50%),
|
||||
linear-gradient(180deg, #0c0e10 0%, #070809 100%);
|
||||
}
|
||||
.auth-theme-toggle {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 28px;
|
||||
right: 28px;
|
||||
transition:
|
||||
color 0.15s ease,
|
||||
background 0.15s ease,
|
||||
border-color 0.15s ease;
|
||||
}
|
||||
.dark .auth-theme-toggle {
|
||||
color: #9ca3af;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.dark .auth-theme-toggle:hover:not(:disabled) {
|
||||
color: #3ecf8e;
|
||||
background: rgba(62, 207, 142, 0.1);
|
||||
border-color: rgba(62, 207, 142, 0.25);
|
||||
}
|
||||
.auth-page::before {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
background-image: radial-gradient(#dcdcdc 0.65px, transparent 0.65px);
|
||||
background-size: 16px 16px;
|
||||
mask-image: linear-gradient(to bottom, transparent, black 24%, transparent 76%);
|
||||
content: '';
|
||||
}
|
||||
.dark .auth-page::before {
|
||||
background-image: radial-gradient(rgba(255, 255, 255, 0.07) 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
mask-image: radial-gradient(ellipse 70% 60% at 50% 40%, black 25%, transparent 80%);
|
||||
}
|
||||
.auth-brand {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.6px;
|
||||
}
|
||||
.dark .auth-brand {
|
||||
color: #ffffff;
|
||||
}
|
||||
.auth-brand-mark {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
color: var(--primary-deep);
|
||||
}
|
||||
.dark .auth-brand-mark {
|
||||
color: #3ecf8e;
|
||||
filter: drop-shadow(0 0 12px rgba(62, 207, 142, 0.45));
|
||||
}
|
||||
.auth-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
align-self: center;
|
||||
width: min(100%, 420px);
|
||||
padding: 36px;
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: 12px;
|
||||
background: rgb(255 255 255 / 0.96);
|
||||
box-shadow:
|
||||
0 18px 55px rgb(24 74 52 / 0.08),
|
||||
0 2px 8px rgb(0 0 0 / 0.04);
|
||||
animation: enter 500ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
}
|
||||
.dark .auth-card {
|
||||
background: rgba(17, 19, 23, 0.82);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05),
|
||||
0 24px 64px -12px rgba(0, 0, 0, 0.8),
|
||||
0 0 45px -10px rgba(62, 207, 142, 0.08),
|
||||
inset 0 1px 0 0 rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
.auth-heading p {
|
||||
margin: 0 0 12px;
|
||||
font:
|
||||
500 10px/1.4 'IBM Plex Mono',
|
||||
monospace;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: #55816e;
|
||||
}
|
||||
.dark .auth-heading p {
|
||||
color: #3ecf8e;
|
||||
}
|
||||
.auth-heading h1 {
|
||||
margin: 0;
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
line-height: 1.16;
|
||||
letter-spacing: -1.2px;
|
||||
}
|
||||
.dark .auth-heading h1 {
|
||||
color: #f9fafb;
|
||||
}
|
||||
.auth-heading > span {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
font-size: 19px;
|
||||
line-height: 1.55;
|
||||
color: #626262;
|
||||
}
|
||||
.dark .auth-heading > span {
|
||||
color: #9ca3af;
|
||||
}
|
||||
.auth-form {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.auth-field {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
.auth-field label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #353535;
|
||||
}
|
||||
.dark .auth-field label {
|
||||
color: #d1d5db;
|
||||
}
|
||||
.dark .auth-field input {
|
||||
background-color: rgba(11, 13, 15, 0.85);
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
color: #f9fafb;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.4);
|
||||
transition:
|
||||
border-color 0.15s ease,
|
||||
box-shadow 0.15s ease;
|
||||
}
|
||||
.dark .auth-field input:hover {
|
||||
border-color: rgba(255, 255, 255, 0.24);
|
||||
}
|
||||
.dark .auth-field input:focus-visible {
|
||||
border-color: #3ecf8e;
|
||||
box-shadow:
|
||||
0 0 0 3px rgba(62, 207, 142, 0.2),
|
||||
inset 0 1px 2px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.auth-error {
|
||||
margin: -4px 0 0;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #efcaca;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: #9f2f2f;
|
||||
background: #fff6f6;
|
||||
}
|
||||
.dark .auth-error {
|
||||
background: rgba(239, 68, 68, 0.12);
|
||||
border-color: rgba(239, 68, 68, 0.28);
|
||||
color: #fca5a5;
|
||||
}
|
||||
.auth-submit {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #35c586;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
background: var(--primary);
|
||||
box-shadow:
|
||||
0 1px 2px rgb(0 0 0 / 0.08),
|
||||
inset 0 1px rgb(255 255 255 / 0.2);
|
||||
cursor: pointer;
|
||||
}
|
||||
.auth-submit:hover:not(:disabled) {
|
||||
background: #36c487;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.auth-submit:active:not(:disabled) {
|
||||
background: var(--primary-deep);
|
||||
transform: translateY(1px);
|
||||
}
|
||||
.auth-submit:disabled {
|
||||
cursor: wait;
|
||||
opacity: 0.62;
|
||||
}
|
||||
.dark .auth-submit {
|
||||
background: #3ecf8e;
|
||||
border-color: #3ecf8e;
|
||||
color: #090a0c;
|
||||
font-weight: 600;
|
||||
box-shadow:
|
||||
0 1px 2px rgba(0, 0, 0, 0.3),
|
||||
0 4px 14px rgba(62, 207, 142, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.35);
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.dark .auth-submit:hover:not(:disabled) {
|
||||
background: #4ae09e;
|
||||
border-color: #4ae09e;
|
||||
box-shadow:
|
||||
0 4px 20px rgba(62, 207, 142, 0.45),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.dark .auth-submit:active:not(:disabled) {
|
||||
background: #24b47e;
|
||||
transform: translateY(1px);
|
||||
}
|
||||
.auth-footnote {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
color: #858585;
|
||||
}
|
||||
.dark .auth-footnote {
|
||||
color: var(--faint);
|
||||
}
|
||||
|
||||
@media (max-width: 440px) {
|
||||
.auth-page {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
.auth-card {
|
||||
padding: 28px 22px;
|
||||
}
|
||||
.auth-heading h1 {
|
||||
font-size: 27px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1505,7 +1505,7 @@
|
||||
}
|
||||
|
||||
.dark .uptime-days-caption {
|
||||
color: #6b7280;
|
||||
color: var(--faint);
|
||||
}
|
||||
|
||||
.dark .uptime-days-caption i {
|
||||
@@ -1598,7 +1598,7 @@
|
||||
}
|
||||
|
||||
.dark .past-incident-meta {
|
||||
color: #6b7280;
|
||||
color: var(--faint);
|
||||
}
|
||||
|
||||
.dark .past-incident-chevron {
|
||||
@@ -1634,7 +1634,7 @@
|
||||
/* Status Footer */
|
||||
.dark .status-footer {
|
||||
border-top-color: rgba(255, 255, 255, 0.08);
|
||||
color: #6b7280;
|
||||
color: var(--faint);
|
||||
}
|
||||
|
||||
.dark .status-footer i {
|
||||
@@ -1676,7 +1676,7 @@
|
||||
}
|
||||
|
||||
.dark .incident-detail-summary dt {
|
||||
color: #6b7280;
|
||||
color: var(--faint);
|
||||
}
|
||||
|
||||
.dark .incident-detail-summary dd {
|
||||
@@ -1692,7 +1692,7 @@
|
||||
}
|
||||
|
||||
.dark .incident-timeline-heading > span {
|
||||
color: #6b7280;
|
||||
color: var(--faint);
|
||||
}
|
||||
|
||||
.dark .incident-timeline strong {
|
||||
@@ -1700,7 +1700,7 @@
|
||||
}
|
||||
|
||||
.dark .incident-timeline time {
|
||||
color: #6b7280;
|
||||
color: var(--faint);
|
||||
}
|
||||
|
||||
.dark .incident-timeline-entry p {
|
||||
|
||||
@@ -6,11 +6,11 @@ import { cn } from '@/lib/utils';
|
||||
const badgeVariants = cva('inline-flex items-center gap-1.75 text-[12px] font-medium [&>i]:size-1.75 [&>i]:shrink-0 [&>i]:rounded-full', {
|
||||
variants: {
|
||||
variant: {
|
||||
online: 'text-[#16885b] [&>i]:bg-(--primary-deep) [&>i]:shadow-[0_0_0_3px_rgb(62_207_142/0.12)]',
|
||||
offline: 'text-[#ae3d3d] [&>i]:bg-[#d05a5a]',
|
||||
pending: 'text-[#8a5a1f] [&>i]:bg-[#e0913f] [&>i]:shadow-[0_0_0_3px_rgb(224_145_63/0.14)]',
|
||||
checking: 'text-[#8b7722] [&>i]:animate-[blink_1.1s_ease-in-out_infinite] [&>i]:bg-[#d7bd53]',
|
||||
maintenance: 'text-[#3f6fb8] [&>i]:bg-[#5a8fd0]',
|
||||
online: 'text-[#16885b] dark:text-[#4ade80] [&>i]:bg-(--primary-deep) [&>i]:shadow-[0_0_0_3px_rgb(62_207_142/0.12)]',
|
||||
offline: 'text-[#ae3d3d] dark:text-[#f87171] [&>i]:bg-[#d05a5a]',
|
||||
pending: 'text-[#8a5a1f] dark:text-[#f6c86a] [&>i]:bg-[#e0913f] [&>i]:shadow-[0_0_0_3px_rgb(224_145_63/0.14)]',
|
||||
checking: 'text-[#8b7722] dark:text-[#fde047] [&>i]:animate-[blink_1.1s_ease-in-out_infinite] [&>i]:bg-[#d7bd53]',
|
||||
maintenance: 'text-[#3f6fb8] dark:text-[#8bb5ee] [&>i]:bg-[#5a8fd0]',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
@@ -7,7 +7,16 @@ import { cn } from '@/lib/utils';
|
||||
function Card({ className, asChild = false, ...props }: React.ComponentProps<'div'> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot.Root : 'div';
|
||||
|
||||
return <Comp data-slot="card" className={cn('flex flex-col rounded-[8px] border border-[#e3e3e3] bg-white', className)} {...props} />;
|
||||
return (
|
||||
<Comp
|
||||
data-slot="card"
|
||||
className={cn(
|
||||
'flex flex-col rounded-[8px] border border-[#e3e3e3] bg-white text-(--ink) dark:border-white/10 dark:bg-[#111419]',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
|
||||
@@ -7,7 +7,7 @@ const emptyVariants = cva('grid justify-items-center bg-transparent p-[56px_24px
|
||||
variants: {
|
||||
variant: {
|
||||
default: '',
|
||||
error: 'rounded-[8px] border border-[#ebd1d1] bg-[#fffafa]',
|
||||
error: 'rounded-[8px] border border-[#ebd1d1] bg-[#fffafa] dark:border-red-400/25 dark:bg-red-400/6',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
@@ -19,7 +19,7 @@ const emptyMediaVariants = cva('', {
|
||||
variants: {
|
||||
variant: {
|
||||
default: '',
|
||||
icon: 'mb-4.5 grid size-12 place-items-center rounded-[8px] border border-[#dcdcdc] bg-[#fafafa] text-[#666] [&_svg]:size-5.5',
|
||||
icon: 'mb-4.5 grid size-12 place-items-center rounded-[8px] border border-[#dcdcdc] bg-[#fafafa] text-[#666] dark:border-white/12 dark:bg-white/4 dark:text-[#aeb4bf] [&_svg]:size-5.5',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
@@ -59,7 +59,9 @@ function EmptyMedia({ className, variant, ...props }: React.ComponentProps<'div'
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
emptyMediaVariants({ variant }),
|
||||
emptyVariant === 'error' && variant === 'icon' && 'border-[#ebcaca] bg-[#fff3f3] text-[#a34242]',
|
||||
emptyVariant === 'error' &&
|
||||
variant === 'icon' &&
|
||||
'border-[#ebcaca] bg-[#fff3f3] text-[#a34242] dark:border-red-400/30 dark:bg-red-400/10 dark:text-red-300',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -73,7 +75,7 @@ function EmptyTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="empty-title"
|
||||
className={cn('text-[16px] font-medium text-[#171717]', variant === 'error' && 'text-[#8b3434]', className)}
|
||||
className={cn('text-[16px] font-medium text-(--ink)', variant === 'error' && 'text-[#8b3434] dark:text-red-300', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -12,10 +12,10 @@ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
|
||||
type={type}
|
||||
data-slot="input"
|
||||
className={cn(
|
||||
'flex min-h-10.5 w-full rounded-[6px] border border-[#cfcfcf] bg-white px-3 py-2',
|
||||
'flex min-h-10.5 w-full rounded-[6px] border border-[#cfcfcf] bg-white px-3 py-2 dark:border-white/12 dark:bg-[#0b0d0f]',
|
||||
'text-[14px] text-(--ink) shadow-[inset_0_1px_2px_rgb(0_0_0/0.025)] transition-colors outline-none',
|
||||
'placeholder:text-(--faint)',
|
||||
'hover:border-[#aaa]',
|
||||
'hover:border-[#aaa] dark:hover:border-white/22',
|
||||
'focus-visible:border-(--primary-deep) focus-visible:shadow-[0_0_0_3px_rgb(36_180_126/0.14)] focus-visible:outline-none!',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'aria-invalid:border-destructive aria-invalid:shadow-[0_0_0_3px_rgb(220_98_98/0.14)]',
|
||||
|
||||
@@ -25,9 +25,9 @@ function SelectTrigger({ className, children, ...props }: React.ComponentProps<t
|
||||
<SelectPrimitive.Trigger
|
||||
data-slot="select-trigger"
|
||||
className={cn(
|
||||
'group flex min-h-10.5 w-full cursor-pointer items-center justify-between gap-2 rounded-[6px] border border-[#cfcfcf] bg-white px-3 py-2 text-left',
|
||||
'group flex min-h-10.5 w-full cursor-pointer items-center justify-between gap-2 rounded-[6px] border border-[#cfcfcf] bg-white px-3 py-2 text-left dark:border-white/12 dark:bg-[#0b0d0f]',
|
||||
'text-[14px] text-(--ink) shadow-[inset_0_1px_2px_rgb(0_0_0/0.025)] transition-colors outline-none data-placeholder:text-(--faint) [&_svg]:shrink-0',
|
||||
'hover:border-[#aaa]',
|
||||
'hover:border-[#aaa] dark:hover:border-white/22',
|
||||
'focus-visible:border-(--primary-deep) focus-visible:shadow-[0_0_0_3px_rgb(36_180_126/0.14)] focus-visible:outline-none!',
|
||||
'data-[state=open]:border-(--primary-deep) data-[state=open]:shadow-[0_0_0_3px_rgb(36_180_126/0.14)]',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
@@ -51,7 +51,7 @@ function SelectContent({ className, children, position = 'popper', ...props }: R
|
||||
data-slot="select-content"
|
||||
className={cn(
|
||||
'relative z-50 max-h-(--radix-select-content-available-height) min-w-32 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto',
|
||||
'rounded-[8px] border border-(--hairline) bg-white text-(--ink) shadow-[0_8px_24px_rgb(0_0_0/0.08)]',
|
||||
'rounded-[8px] border border-(--hairline) bg-white text-(--ink) shadow-[0_8px_24px_rgb(0_0_0/0.08)] dark:bg-[#111419] dark:shadow-[0_12px_32px_rgb(0_0_0/0.55)]',
|
||||
'duration-100 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
|
||||
'data-[side=bottom]:slide-in-from-top-1 data-[side=top]:slide-in-from-bottom-1',
|
||||
position === 'popper' &&
|
||||
@@ -87,7 +87,7 @@ function SelectItem({ className, children, ...props }: React.ComponentProps<type
|
||||
data-slot="select-item"
|
||||
className={cn(
|
||||
'relative flex w-full cursor-pointer items-center gap-2 rounded-lg py-1.5 pr-8 pl-2 text-[14px] text-(--ink) outline-hidden select-none',
|
||||
'focus:bg-[#ededed] data-[state=checked]:font-medium',
|
||||
'focus:bg-[#ededed] dark:focus:bg-white/8 data-[state=checked]:font-medium',
|
||||
'data-disabled:pointer-events-none data-disabled:opacity-50',
|
||||
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
|
||||
@@ -26,7 +26,10 @@ function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
|
||||
return (
|
||||
<th
|
||||
data-slot="table-head"
|
||||
className={cn('sticky top-0 z-1 bg-[#fafafa] px-4 py-2.75 text-left font-normal text-[#888] shadow-[0_1px_#e5e5e5]', className)}
|
||||
className={cn(
|
||||
'sticky top-0 z-1 bg-[#fafafa] px-4 py-2.75 text-left font-normal text-[#707070] shadow-[0_1px_#e5e5e5] dark:bg-[#14171d] dark:text-[#aeb4bf] dark:shadow-[0_1px_rgb(255_255_255/0.08)]',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -36,14 +39,17 @@ function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
|
||||
return (
|
||||
<td
|
||||
data-slot="table-cell"
|
||||
className={cn('whitespace-nowrap border-t border-[#ededed] px-4 py-3.5 text-[#555]', className)}
|
||||
className={cn(
|
||||
'whitespace-nowrap border-t border-[#ededed] px-4 py-3.5 text-[#555] dark:border-white/8 dark:text-[#d1d5db]',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function TableCaption({ className, ...props }: React.ComponentProps<'caption'>) {
|
||||
return <caption data-slot="table-caption" className={cn('mt-4 text-[12px] text-[#888]', className)} {...props} />;
|
||||
return <caption data-slot="table-caption" className={cn('mt-4 text-[12px] text-[#707070] dark:text-[#aeb4bf]', className)} {...props} />;
|
||||
}
|
||||
|
||||
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
|
||||
|
||||
Reference in New Issue
Block a user