431 lines
16 KiB
HTML
431 lines
16 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="no">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="robots" content="noindex, nofollow">
|
||
<title>CostGuard — Kom i gang</title>
|
||
<style>
|
||
:root{
|
||
--bg:#0a0a0f;--bg-2:#11131a;--bg-3:#161922;
|
||
--border:rgba(255,255,255,0.08);--border-2:rgba(255,255,255,0.14);
|
||
--text:#e8eaf0;--text-dim:#8b909c;--text-faint:#5a5f6b;
|
||
--accent:#7c5cff;--accent-2:#00d4a8;
|
||
--green:#3ecf8e;--amber:#ffb547;--danger:#ff5c7c;
|
||
--radius:12px;
|
||
--sans:-apple-system,BlinkMacSystemFont,'Inter',system-ui,sans-serif;
|
||
}
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
html,body{min-height:100%;background:var(--bg);color:var(--text);font-family:var(--sans);-webkit-font-smoothing:antialiased;display:flex;align-items:center;justify-content:center;padding:24px}
|
||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
|
||
@keyframes spin{to{transform:rotate(360deg)}}
|
||
@keyframes fadeIn{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
|
||
|
||
.card{
|
||
width:100%;max-width:560px;
|
||
background:var(--bg-2);border:1px solid var(--border-2);
|
||
border-radius:20px;overflow:hidden;
|
||
animation:fadeIn .35s ease;
|
||
}
|
||
.card-head{
|
||
padding:32px 36px 24px;
|
||
border-bottom:1px solid var(--border);
|
||
background:linear-gradient(135deg,rgba(124,92,255,.08),rgba(0,212,168,.05));
|
||
}
|
||
.logo{display:flex;align-items:center;gap:12px;margin-bottom:20px}
|
||
.logo-icon{
|
||
width:40px;height:40px;border-radius:12px;
|
||
background:linear-gradient(135deg,var(--accent),var(--accent-2));
|
||
display:flex;align-items:center;justify-content:center;
|
||
font-weight:900;font-size:14px;color:#050509;
|
||
}
|
||
.logo-name{font-size:18px;font-weight:800;letter-spacing:-.02em}
|
||
.logo-sub{font-size:11px;color:var(--text-faint)}
|
||
h1{font-size:22px;font-weight:800;letter-spacing:-.02em;margin-bottom:6px}
|
||
.subtitle{font-size:14px;color:var(--text-dim)}
|
||
|
||
/* STEPS */
|
||
.steps{display:flex;gap:0;padding:20px 36px 0;border-bottom:1px solid var(--border)}
|
||
.step{
|
||
flex:1;text-align:center;padding-bottom:16px;
|
||
border-bottom:2px solid transparent;
|
||
font-size:11px;font-weight:700;color:var(--text-faint);
|
||
text-transform:uppercase;letter-spacing:.06em;
|
||
transition:color .2s,border-color .2s;
|
||
cursor:default;
|
||
}
|
||
.step.active{color:var(--accent);border-color:var(--accent)}
|
||
.step.done{color:var(--green);border-color:var(--green)}
|
||
.step-num{
|
||
display:inline-flex;align-items:center;justify-content:center;
|
||
width:22px;height:22px;border-radius:50%;
|
||
background:var(--bg-3);font-size:11px;font-weight:700;
|
||
margin-bottom:4px;margin-right:0;
|
||
border:1px solid var(--border);
|
||
}
|
||
.step.active .step-num{background:var(--accent);color:#fff;border-color:var(--accent)}
|
||
.step.done .step-num{background:var(--green);color:#050509;border-color:var(--green)}
|
||
|
||
/* FORM */
|
||
.card-body{padding:28px 36px 32px}
|
||
.panel{display:none}
|
||
.panel.active{display:block;animation:fadeIn .2s ease}
|
||
|
||
.field{margin-bottom:18px}
|
||
label{display:block;font-size:12px;font-weight:700;color:var(--text-dim);text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px}
|
||
input,select{
|
||
width:100%;padding:10px 14px;
|
||
background:var(--bg-3);border:1px solid var(--border);
|
||
border-radius:9px;color:var(--text);font-size:14px;
|
||
font-family:inherit;outline:none;
|
||
transition:border-color .15s;
|
||
}
|
||
input:focus,select:focus{border-color:var(--accent)}
|
||
input::placeholder{color:var(--text-faint)}
|
||
select option{background:var(--bg-2)}
|
||
|
||
.tier-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
||
.tier-opt{
|
||
padding:12px 14px;border-radius:10px;
|
||
background:var(--bg-3);border:1px solid var(--border);
|
||
cursor:pointer;transition:border-color .15s,background .15s;
|
||
}
|
||
.tier-opt:hover{border-color:var(--border-2)}
|
||
.tier-opt.selected{border-color:var(--accent);background:rgba(124,92,255,.08)}
|
||
.tier-name{font-size:13px;font-weight:700}
|
||
.tier-price{font-size:11px;color:var(--text-faint);margin-top:2px}
|
||
.tier-limit{font-size:10px;color:var(--text-faint);margin-top:3px;font-style:italic}
|
||
|
||
.ch-toggle{display:flex;flex-direction:column;gap:8px}
|
||
.ch-row{
|
||
display:flex;align-items:center;gap:12px;
|
||
background:var(--bg-3);border:1px solid var(--border);
|
||
border-radius:10px;padding:12px 14px;
|
||
cursor:pointer;transition:border-color .15s;
|
||
}
|
||
.ch-row.selected{border-color:var(--accent);background:rgba(124,92,255,.06)}
|
||
.ch-emoji{font-size:18px;flex-shrink:0}
|
||
.ch-label{flex:1}
|
||
.ch-label-name{font-size:13px;font-weight:600}
|
||
.ch-label-desc{font-size:11px;color:var(--text-faint);margin-top:1px}
|
||
.ch-check{
|
||
width:18px;height:18px;border-radius:5px;
|
||
border:1px solid var(--border);background:var(--bg-2);
|
||
display:flex;align-items:center;justify-content:center;
|
||
font-size:10px;flex-shrink:0;
|
||
}
|
||
.ch-row.selected .ch-check{background:var(--accent);border-color:var(--accent);color:#fff}
|
||
|
||
.ch-extra{margin-top:10px;display:none}
|
||
.ch-extra.show{display:block}
|
||
|
||
/* NAV */
|
||
.nav{display:flex;gap:10px;margin-top:24px}
|
||
.btn{
|
||
padding:11px 22px;border-radius:10px;
|
||
font-size:14px;font-weight:700;cursor:pointer;
|
||
border:none;font-family:inherit;
|
||
transition:background .15s,opacity .15s;
|
||
}
|
||
.btn-primary{background:var(--accent);color:#fff;flex:1}
|
||
.btn-primary:hover{background:#6a4ce8}
|
||
.btn-primary:disabled{opacity:.45;cursor:not-allowed}
|
||
.btn-ghost{
|
||
background:var(--bg-3);color:var(--text-dim);
|
||
border:1px solid var(--border);
|
||
}
|
||
.btn-ghost:hover{background:var(--bg-2)}
|
||
|
||
/* SUCCESS */
|
||
.success-wrap{text-align:center;padding:12px 0}
|
||
.success-icon{font-size:52px;margin-bottom:16px}
|
||
.success-title{font-size:22px;font-weight:800;margin-bottom:8px}
|
||
.success-sub{font-size:14px;color:var(--text-dim);line-height:1.5}
|
||
.success-channels{display:flex;justify-content:center;gap:8px;margin-top:16px;flex-wrap:wrap}
|
||
.ch-pill{
|
||
padding:5px 12px;border-radius:99px;
|
||
background:rgba(62,207,142,.12);color:var(--green);
|
||
border:1px solid rgba(62,207,142,.25);font-size:12px;font-weight:600;
|
||
}
|
||
.dashboard-btn{
|
||
display:inline-block;margin-top:24px;
|
||
padding:12px 28px;border-radius:10px;
|
||
background:var(--accent);color:#fff;
|
||
font-size:14px;font-weight:700;cursor:pointer;
|
||
border:none;font-family:inherit;
|
||
transition:background .15s;
|
||
}
|
||
.dashboard-btn:hover{background:#6a4ce8}
|
||
|
||
/* ERROR */
|
||
.err-box{
|
||
background:rgba(255,92,124,.08);border:1px solid rgba(255,92,124,.2);
|
||
border-radius:8px;padding:10px 14px;
|
||
font-size:13px;color:var(--danger);
|
||
margin-top:14px;display:none;
|
||
}
|
||
.err-box.show{display:block}
|
||
|
||
.spinner{display:inline-block;width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .7s linear infinite;vertical-align:middle;margin-right:8px}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="card">
|
||
<div class="card-head">
|
||
<div class="logo">
|
||
<div class="logo-icon">CG</div>
|
||
<div>
|
||
<div class="logo-name">CostGuard</div>
|
||
<div class="logo-sub">av Vauco AS</div>
|
||
</div>
|
||
</div>
|
||
<h1>Kom i gang på 2 minutter</h1>
|
||
<p class="subtitle">Koble CostGuard til ditt GCP-prosjekt og velg varslings-kanaler.</p>
|
||
</div>
|
||
|
||
<div class="steps">
|
||
<div class="step active" id="step-1"><div class="step-num">1</div><div>Bedrift</div></div>
|
||
<div class="step" id="step-2"><div class="step-num">2</div><div>Tier</div></div>
|
||
<div class="step" id="step-3"><div class="step-num">3</div><div>Varsler</div></div>
|
||
<div class="step" id="step-4"><div class="step-num">4</div><div>Ferdig</div></div>
|
||
</div>
|
||
|
||
<div class="card-body">
|
||
|
||
<!-- PANEL 1: Bedrift -->
|
||
<div class="panel active" id="panel-1">
|
||
<div class="field">
|
||
<label>Bedriftsnavn</label>
|
||
<input id="f-company" type="text" placeholder="Acme AS" autocomplete="organization">
|
||
</div>
|
||
<div class="field">
|
||
<label>Kontaktperson</label>
|
||
<input id="f-contact" type="text" placeholder="Ola Nordmann" autocomplete="name">
|
||
</div>
|
||
<div class="field">
|
||
<label>E-postadresse</label>
|
||
<input id="f-email" type="email" placeholder="ola@acme.no" autocomplete="email">
|
||
</div>
|
||
<div class="field">
|
||
<label>GCP Project ID</label>
|
||
<input id="f-project" type="text" placeholder="mitt-prosjekt-123456" autocomplete="off" spellcheck="false">
|
||
</div>
|
||
<div class="err-box" id="err-1"></div>
|
||
<div class="nav">
|
||
<button class="btn btn-primary" onclick="goTo(2)">Neste →</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PANEL 2: Tier -->
|
||
<div class="panel" id="panel-2">
|
||
<div class="tier-grid">
|
||
<div class="tier-opt selected" data-tier="starter" onclick="selectTier(this)">
|
||
<div class="tier-name">Starter</div>
|
||
<div class="tier-price">$499 / mnd</div>
|
||
<div class="tier-limit">GCP-spend $0–$3k/mnd</div>
|
||
</div>
|
||
<div class="tier-opt" data-tier="guard" onclick="selectTier(this)">
|
||
<div class="tier-name">Guard</div>
|
||
<div class="tier-price">$999 / mnd</div>
|
||
<div class="tier-limit">GCP-spend $3k–$15k/mnd</div>
|
||
</div>
|
||
<div class="tier-opt" data-tier="shield" onclick="selectTier(this)">
|
||
<div class="tier-name">Shield</div>
|
||
<div class="tier-price">$1.999 / mnd</div>
|
||
<div class="tier-limit">GCP-spend $15k–$50k/mnd</div>
|
||
</div>
|
||
<div class="tier-opt" data-tier="enterprise" onclick="selectTier(this)">
|
||
<div class="tier-name">Enterprise</div>
|
||
<div class="tier-price">$3.500+ / mnd</div>
|
||
<div class="tier-limit">GCP-spend >$50k/mnd</div>
|
||
</div>
|
||
</div>
|
||
<div class="nav">
|
||
<button class="btn btn-ghost" onclick="goTo(1)">← Tilbake</button>
|
||
<button class="btn btn-primary" onclick="goTo(3)">Neste →</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PANEL 3: Kanaler -->
|
||
<div class="panel" id="panel-3">
|
||
<div class="ch-toggle">
|
||
<div class="ch-row selected" data-ch="webhook" onclick="toggleChannel(this)">
|
||
<div class="ch-emoji">🔗</div>
|
||
<div class="ch-label">
|
||
<div class="ch-label-name">Webhook <span style="font-size:10px;color:var(--text-faint);font-weight:400">(Slack/Teams/Chat)</span></div>
|
||
<div class="ch-label-desc">Øyeblikkelige varsler i din eksisterende kanal</div>
|
||
</div>
|
||
<div class="ch-check">✓</div>
|
||
</div>
|
||
<div class="ch-extra show" id="ex-webhook">
|
||
<div class="field" style="margin-bottom:0">
|
||
<label>Webhook URL</label>
|
||
<input id="f-webhook" type="url" placeholder="https://hooks.slack.com/services/...">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ch-row selected" data-ch="email" onclick="toggleChannel(this)">
|
||
<div class="ch-emoji">📧</div>
|
||
<div class="ch-label">
|
||
<div class="ch-label-name">E-post</div>
|
||
<div class="ch-label-desc">Daglig kostnadsoppsummering + spike-varsler</div>
|
||
</div>
|
||
<div class="ch-check">✓</div>
|
||
</div>
|
||
<div class="ch-extra show" id="ex-email">
|
||
<div class="field" style="margin-bottom:0">
|
||
<label>Varsel-e-post</label>
|
||
<input id="f-email-to" type="email" placeholder="alerts@acme.no">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ch-row" data-ch="sms" onclick="toggleChannel(this)" id="sms-row">
|
||
<div class="ch-emoji">📱</div>
|
||
<div class="ch-label">
|
||
<div class="ch-label-name">SMS <span style="font-size:10px;color:var(--amber);font-weight:600">Guard+</span></div>
|
||
<div class="ch-label-desc">Kritiske spike-varsler rett på mobil</div>
|
||
</div>
|
||
<div class="ch-check"></div>
|
||
</div>
|
||
</div>
|
||
<div class="err-box" id="err-3"></div>
|
||
<div class="nav">
|
||
<button class="btn btn-ghost" onclick="goTo(2)">← Tilbake</button>
|
||
<button class="btn btn-primary" id="submit-btn" onclick="submit()">Aktiver CostGuard</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PANEL 4: Success -->
|
||
<div class="panel" id="panel-4">
|
||
<div class="success-wrap">
|
||
<div class="success-icon">🎉</div>
|
||
<div class="success-title">CostGuard er aktivert!</div>
|
||
<div class="success-sub">Jason overvåker nå <strong id="s-project"></strong>.<br>Varsler sendes via:</div>
|
||
<div class="success-channels" id="s-channels"></div>
|
||
<button class="dashboard-btn" onclick="window.location='/static/costguard-dashboard.html'">Gå til dashboard →</button>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let selectedTier = 'starter'
|
||
let selectedChannels = new Set(['webhook','email'])
|
||
|
||
function selectTier(el){
|
||
document.querySelectorAll('.tier-opt').forEach(e=>e.classList.remove('selected'))
|
||
el.classList.add('selected')
|
||
selectedTier = el.dataset.tier
|
||
// SMS kun Guard+
|
||
const smsRow = document.getElementById('sms-row')
|
||
if(['guard','shield','enterprise'].includes(selectedTier)){
|
||
smsRow.style.opacity='1';smsRow.style.pointerEvents='auto'
|
||
} else {
|
||
smsRow.style.opacity='.4';smsRow.style.pointerEvents='none'
|
||
smsRow.classList.remove('selected');selectedChannels.delete('sms')
|
||
}
|
||
}
|
||
|
||
function toggleChannel(el){
|
||
const ch = el.dataset.ch
|
||
const extra = document.getElementById('ex-'+ch)
|
||
if(el.classList.contains('selected')){
|
||
el.classList.remove('selected')
|
||
el.querySelector('.ch-check').textContent=''
|
||
selectedChannels.delete(ch)
|
||
if(extra){ extra.classList.remove('show') }
|
||
} else {
|
||
el.classList.add('selected')
|
||
el.querySelector('.ch-check').textContent='✓'
|
||
selectedChannels.add(ch)
|
||
if(extra){ extra.classList.add('show') }
|
||
}
|
||
}
|
||
|
||
function showErr(id, msg){
|
||
const el = document.getElementById(id)
|
||
el.textContent = msg; el.classList.add('show')
|
||
}
|
||
function clearErr(id){ document.getElementById(id).classList.remove('show') }
|
||
|
||
function goTo(n){
|
||
// Validate step 1
|
||
if(n===2){
|
||
clearErr('err-1')
|
||
const c = v('f-company'), ct = v('f-contact'), e = v('f-email'), p = v('f-project')
|
||
if(!c||!ct||!e||!p){ showErr('err-1','Fyll ut alle felt.'); return }
|
||
if(!e.includes('@')){ showErr('err-1','Ugyldig e-postadresse.'); return }
|
||
}
|
||
// Validate step 3
|
||
if(n===4 && !doSubmit) return
|
||
|
||
for(let i=1;i<=4;i++){
|
||
document.getElementById('panel-'+i).classList.toggle('active', i===n)
|
||
const s = document.getElementById('step-'+i)
|
||
s.classList.remove('active','done')
|
||
if(i===n) s.classList.add('active')
|
||
else if(i<n) s.classList.add('done')
|
||
}
|
||
}
|
||
|
||
function v(id){ return document.getElementById(id).value.trim() }
|
||
|
||
async function submit(){
|
||
clearErr('err-3')
|
||
const channels = [...selectedChannels]
|
||
if(!channels.length){ showErr('err-3','Velg minst én varslingskanal.'); return }
|
||
const webhookUrl = selectedChannels.has('webhook') ? v('f-webhook') : null
|
||
const emailTo = selectedChannels.has('email') ? v('f-email-to') : null
|
||
if(selectedChannels.has('webhook') && !webhookUrl){
|
||
showErr('err-3','Lim inn Webhook URL.'); return
|
||
}
|
||
if(selectedChannels.has('email') && !emailTo){
|
||
showErr('err-3','Fyll inn varsel-e-post.'); return
|
||
}
|
||
|
||
const btn = document.getElementById('submit-btn')
|
||
btn.disabled = true
|
||
btn.innerHTML = '<span class="spinner"></span>Aktiverer...'
|
||
|
||
const body = {
|
||
company: v('f-company'),
|
||
contact: v('f-contact'),
|
||
email: v('f-email'),
|
||
gcp_project: v('f-project'),
|
||
tier: selectedTier,
|
||
channels,
|
||
webhook_url: webhookUrl,
|
||
email_to: emailTo,
|
||
}
|
||
|
||
try{
|
||
const r = await fetch('/onboard/complete', {
|
||
method:'POST',
|
||
headers:{'Content-Type':'application/json'},
|
||
body: JSON.stringify(body)
|
||
})
|
||
const data = await r.json()
|
||
if(!r.ok){
|
||
showErr('err-3', data.detail || 'Feil fra server (' + r.status + ').')
|
||
btn.disabled=false; btn.textContent='Aktiver CostGuard'
|
||
return
|
||
}
|
||
// Success
|
||
document.getElementById('s-project').textContent = body.gcp_project
|
||
const pillHtml = channels.map(ch => {
|
||
const icons = {webhook:'🔗',email:'📧',sms:'📱'}
|
||
return `<div class="ch-pill">${icons[ch]||''} ${ch}</div>`
|
||
}).join('')
|
||
document.getElementById('s-channels').innerHTML = pillHtml
|
||
goTo(4)
|
||
} catch(e){
|
||
showErr('err-3', 'Nettverksfeil: ' + e.message)
|
||
btn.disabled=false; btn.textContent='Aktiver CostGuard'
|
||
}
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|