Prefill the demo's login and say on its home page that it's a simulation
Two demo-only affordances, both off by default everywhere else. The login form starts with the demo's read-only account already in both fields, so a visitor doesn't need credentials handed to them out of band. It's a build-time opt-in: the web image is built with VITE_DEMO_USERNAME/VITE_DEMO_PASSWORD, and the page prefills only when it has both, so a deployment that sets neither -- every deployment except the demo -- gets the ordinary empty form, and a half-configured one can't leave a password next to an empty username box. This does bake a password into a static bundle, which is fine for exactly this case and nothing else: a Viewer-role account on a deployment whose database is wiped and reseeded nightly. api.ts says so next to the export, so nobody later points these at an account that can do something. The home page then explains what a visitor is actually looking at -- synthetic data from a simulated fleet, a nightly reset that discards anything they change, and the features that are deliberately limited (read-only account, alerts that notify a placeholder webhook, no time-series charts). Gated on the same signal as the prefill rather than a second flag that could drift out of sync with it. Also carries the landing page's light/dark logo swap, which touches the same file.
This commit is contained in:
@@ -26,6 +26,28 @@ export const enterpriseAuthBase = import.meta.env.VITE_ENTERPRISE_AUTH_BASE_URL
|
||||
// which never sets either of these.
|
||||
export const localAuthEnabled = import.meta.env.VITE_LOCAL_AUTH_ENABLED === 'true';
|
||||
|
||||
// Public-demo convenience: with both set at build time, the login page
|
||||
// starts with these credentials already in the fields, so a visitor to a
|
||||
// public demo can sign in without being handed a password out of band.
|
||||
// Two separate vars, neither with a default, and the login page requires
|
||||
// BOTH before prefilling anything -- a deployment that sets neither (every
|
||||
// deployment except the demo) gets exactly today's empty form, and a
|
||||
// half-configured one can't leave a password sitting next to an empty
|
||||
// username box.
|
||||
//
|
||||
// This bakes a password into a static bundle, which is only acceptable
|
||||
// for what it's for: a throwaway read-only Viewer account on a deployment
|
||||
// whose entire database is wiped and reseeded nightly. Never point these
|
||||
// at an account that can do anything worth doing.
|
||||
export const demoUsername = import.meta.env.VITE_DEMO_USERNAME as string | undefined;
|
||||
export const demoPassword = import.meta.env.VITE_DEMO_PASSWORD as string | undefined;
|
||||
|
||||
// A deployment that prints its own login credentials on its login screen
|
||||
// is, by definition, the public demo -- so the same two build args also
|
||||
// gate the demo notice on the landing page, rather than a third flag
|
||||
// that could drift out of sync with them.
|
||||
export const isPublicDemo = Boolean(demoUsername && demoPassword);
|
||||
|
||||
export type Language = '' | 'sql' | 'spl';
|
||||
|
||||
// warnings (Phase 7) is populated by the shared costguard package's
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<script lang="ts">
|
||||
// Shown on the landing page of a public-demo deployment only (see
|
||||
// $lib/api.ts's isPublicDemo). Says three things a visitor would
|
||||
// otherwise have to guess at, and would eventually be misled by: that
|
||||
// none of this data is real, that anything they do here is gone by
|
||||
// morning, and which features are deliberately limited on the demo
|
||||
// rather than broken.
|
||||
//
|
||||
// Colour comes from --color-accent, which the design system reserves
|
||||
// for interactive elements and explicitly bars from severity use.
|
||||
// That's the right side of the line: this is a UI notice about the
|
||||
// deployment, not a claim about any log record's severity -- the same
|
||||
// reasoning ui/Badge.svelte's `accent` tone already follows.
|
||||
import { Badge } from '$lib/components/ui';
|
||||
|
||||
const points: { label: string; body: string }[] = [
|
||||
{
|
||||
label: 'Synthetic data',
|
||||
body: 'Every log line comes from a simulated fleet — 14 hosts running nginx, an API tier, workers, Postgres, Redis, mail, Linux journals and Windows event logs. No real system is being monitored. There are incidents seeded in the last few days worth finding, and new events keep arriving while you browse.'
|
||||
},
|
||||
{
|
||||
label: 'Resets nightly',
|
||||
body: 'The whole database is wiped and reseeded at 04:00 UTC so timestamps stay recent. Anything you change here is gone after that — and the fleet is regenerated, not restored.'
|
||||
},
|
||||
{
|
||||
label: 'Some limits',
|
||||
body: "You're signed in to a read-only account, so creating and editing are disabled. Alert rules evaluate for real, but notify a placeholder webhook that goes nowhere. Dashboards have no time-series charts yet: the query language can't bucket by time."
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<aside>
|
||||
<header>
|
||||
<Badge tone="accent">Demo</Badge>
|
||||
<h2>You're looking at a simulation</h2>
|
||||
</header>
|
||||
<dl>
|
||||
{#each points as p (p.label)}
|
||||
<div class="point">
|
||||
<dt>{p.label}</dt>
|
||||
<dd>{p.body}</dd>
|
||||
</div>
|
||||
{/each}
|
||||
</dl>
|
||||
</aside>
|
||||
|
||||
<style>
|
||||
aside {
|
||||
width: 100%;
|
||||
margin-top: var(--space-6);
|
||||
padding: var(--space-5) var(--space-5) var(--space-5) var(--space-4);
|
||||
text-align: left;
|
||||
border: 1px solid color-mix(in srgb, var(--color-accent) 30%, var(--color-border));
|
||||
/* The left rule is what makes it read as a notice at a glance,
|
||||
before any of the text is parsed. */
|
||||
border-left: 3px solid var(--color-accent);
|
||||
border-radius: var(--radius-md);
|
||||
background:
|
||||
linear-gradient(
|
||||
to bottom right,
|
||||
color-mix(in srgb, var(--color-accent) 9%, transparent),
|
||||
color-mix(in srgb, var(--color-accent) 3%, transparent)
|
||||
),
|
||||
var(--color-surface);
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-4);
|
||||
padding-bottom: var(--space-3);
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--color-accent) 20%, transparent);
|
||||
}
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text);
|
||||
}
|
||||
dl {
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--space-4);
|
||||
}
|
||||
.point {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
dt {
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--font-weight-bold);
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
dd {
|
||||
margin: 0;
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--line-height-normal);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
/* Three columns need the room; below that they stack, which also
|
||||
suits the narrow single-column layout the shortcuts grid drops to
|
||||
at the same breakpoint. */
|
||||
@media (max-width: 52rem) {
|
||||
dl {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -7,6 +7,8 @@
|
||||
import logoLight from '$lib/assets/logo-stacked-light.svg';
|
||||
import { isLight } from '$lib/theme.svelte';
|
||||
import { Button } from '$lib/components/ui';
|
||||
import DemoNotice from '$lib/components/DemoNotice.svelte';
|
||||
import { isPublicDemo } from '$lib/api';
|
||||
|
||||
const shortcuts: { href: string; label: string; hint: string }[] = [
|
||||
{ href: '/search', label: 'Search', hint: 'Query logs with filters, free-text, or raw SQL' },
|
||||
@@ -16,13 +18,20 @@
|
||||
];
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<!-- The demo notice wants three readable columns, which 40rem can't
|
||||
give it; widened only in that case so no other deployment's landing
|
||||
page changes width. -->
|
||||
<main class:with-notice={isPublicDemo}>
|
||||
<img src={isLight() ? logoLight : logoDark} alt="Cairn OBS" class="logo" />
|
||||
<p class="tagline">
|
||||
One query bar for filter/stats queries and free-text search across every host and service
|
||||
you're shipping logs from.
|
||||
</p>
|
||||
|
||||
{#if isPublicDemo}
|
||||
<DemoNotice />
|
||||
{/if}
|
||||
|
||||
<div class="shortcuts">
|
||||
{#each shortcuts as s (s.href)}
|
||||
<Button href={s.href} variant="secondary">
|
||||
@@ -43,6 +52,9 @@
|
||||
margin: 0 auto;
|
||||
padding-top: var(--space-8, 4rem);
|
||||
}
|
||||
main.with-notice {
|
||||
max-width: 48rem;
|
||||
}
|
||||
.logo {
|
||||
width: 11rem;
|
||||
height: 11rem;
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { login } from '$lib/api';
|
||||
import { login, demoUsername, demoPassword } from '$lib/api';
|
||||
|
||||
let username = $state('');
|
||||
let password = $state('');
|
||||
// A public demo fills its own credentials in (see api.ts's
|
||||
// demoUsername). Both or neither: prefilling one field alone would
|
||||
// just look like a bug. Still an ordinary editable form -- the values
|
||||
// are a starting point, not a lock, so the demo's admin account can
|
||||
// still be typed in over them.
|
||||
const prefilled = Boolean(demoUsername && demoPassword);
|
||||
|
||||
let username = $state(prefilled ? (demoUsername as string) : '');
|
||||
let password = $state(prefilled ? (demoPassword as string) : '');
|
||||
let error = $state('');
|
||||
let submitting = $state(false);
|
||||
|
||||
@@ -30,6 +37,12 @@
|
||||
|
||||
<main>
|
||||
<h1>Sign in</h1>
|
||||
{#if prefilled}
|
||||
<p class="demo-note">
|
||||
Public demo — the read-only <code>{demoUsername}</code> account is already filled in.
|
||||
Just sign in.
|
||||
</p>
|
||||
{/if}
|
||||
<form onsubmit={submit}>
|
||||
{#if error}
|
||||
<p class="error">{error}</p>
|
||||
@@ -106,4 +119,18 @@
|
||||
color: var(--color-danger);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.demo-note {
|
||||
margin-bottom: var(--space-4);
|
||||
padding: var(--space-3);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-sm);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.demo-note code {
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-text);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user