Initial commit on branch: Rewrite config.tmpl as svelte component(s)

This commit is contained in:
Christoph Kluge 2022-08-26 11:45:14 +02:00
parent a0dafbac99
commit 7bfdc83779
13 changed files with 547 additions and 4 deletions

View File

@ -36,6 +36,7 @@ type Route struct {
var routes []Route = []Route{ var routes []Route = []Route{
{"/", "home.tmpl", "ClusterCockpit", false, setupHomeRoute}, {"/", "home.tmpl", "ClusterCockpit", false, setupHomeRoute},
{"/config", "config.tmpl", "Settings", false, func(i InfoType, r *http.Request) InfoType { return i }}, {"/config", "config.tmpl", "Settings", false, func(i InfoType, r *http.Request) InfoType { return i }},
{"/configsvelte", "configsvelte.tmpl", "Settings as Svelte", false, func(i InfoType, r *http.Request) InfoType { return i }},
{"/monitoring/jobs/", "monitoring/jobs.tmpl", "Jobs - ClusterCockpit", true, func(i InfoType, r *http.Request) InfoType { return i }}, {"/monitoring/jobs/", "monitoring/jobs.tmpl", "Jobs - ClusterCockpit", true, func(i InfoType, r *http.Request) InfoType { return i }},
{"/monitoring/job/{id:[0-9]+}", "monitoring/job.tmpl", "Job <ID> - ClusterCockpit", false, setupJobRoute}, {"/monitoring/job/{id:[0-9]+}", "monitoring/job.tmpl", "Job <ID> - ClusterCockpit", false, setupJobRoute},
{"/monitoring/users/", "monitoring/list.tmpl", "Users - ClusterCockpit", true, func(i InfoType, r *http.Request) InfoType { i["listType"] = "USER"; return i }}, {"/monitoring/users/", "monitoring/list.tmpl", "Users - ClusterCockpit", true, func(i InfoType, r *http.Request) InfoType { i["listType"] = "USER"; return i }},

View File

@ -65,6 +65,6 @@ export default [
entrypoint('systems', 'src/systems.entrypoint.js'), entrypoint('systems', 'src/systems.entrypoint.js'),
entrypoint('node', 'src/node.entrypoint.js'), entrypoint('node', 'src/node.entrypoint.js'),
entrypoint('analysis', 'src/analysis.entrypoint.js'), entrypoint('analysis', 'src/analysis.entrypoint.js'),
entrypoint('status', 'src/status.entrypoint.js') entrypoint('status', 'src/status.entrypoint.js'),
entrypoint('configsvelte', 'src/configsvelte.entrypoint.js')
]; ];

View File

@ -0,0 +1,31 @@
<script>
import { getContext } from 'svelte'
import { init } from './utils.js'
import { Card, CardHeader, CardTitle } from 'sveltestrap'
import PlotSettings from './config/PlotSettings.svelte'
import AdminSettings from './config/AdminSettings.svelte'
const { query: initq } = init()
const ccconfig = getContext('cc-config')
export let user
</script>
{#if user.IsAdmin}
<Card style="margin-bottom: 1.5em;">
<CardHeader>
<CardTitle class="mb-1">Admin Options</CardTitle>
</CardHeader>
<AdminSettings/>
</Card>
{/if}
<Card>
<CardHeader>
<CardTitle class="mb-1">Plotting Options</CardTitle>
</CardHeader>
<PlotSettings config={ccconfig}/>
</Card>

View File

@ -0,0 +1,36 @@
<script>
import { Row, Col } from 'sveltestrap'
import { onMount } from 'svelte'
import AddRole from './admin/AddRole.svelte'
import AddUser from './admin/AddUser.svelte'
import ShowUsers from './admin/ShowUsers.svelte'
import Options from './admin/Options.svelte'
let users = []
function getUserList() {
fetch('/api/users/?via-ldap=false&not-just-user=true')
.then(res => res.json())
.then(usersRaw => {
users = usersRaw
})
}
onMount(() => getUserList())
</script>
<Row cols={2} class="p-2 g-2" >
<Col class="mb-1">
<AddUser on:reload={getUserList}/>
</Col>
<Col class="mb-1">
<ShowUsers on:reload={getUserList} bind:users={users}/>
</Col>
<Col>
<AddRole on:reload={getUserList}/>
</Col>
<Col>
<Options/>
</Col>
</Row>

View File

@ -0,0 +1,170 @@
<script>
import { Button, Table, Row, Col, Card, CardBody, CardTitle } from 'sveltestrap'
import { fade } from 'svelte/transition'
export let config
let message = {msg: '', target: '', color: '#d63384'}
let displayMessage = false
const colorschemes = {
'Default': ["#00bfff","#0000ff","#ff00ff","#ff0000","#ff8000","#ffff00","#80ff00"],
'Autumn': ['rgb(255,0,0)','rgb(255,11,0)','rgb(255,20,0)','rgb(255,30,0)','rgb(255,41,0)','rgb(255,50,0)','rgb(255,60,0)','rgb(255,71,0)','rgb(255,80,0)','rgb(255,90,0)','rgb(255,101,0)','rgb(255,111,0)','rgb(255,120,0)','rgb(255,131,0)','rgb(255,141,0)','rgb(255,150,0)','rgb(255,161,0)','rgb(255,171,0)','rgb(255,180,0)','rgb(255,190,0)','rgb(255,201,0)','rgb(255,210,0)','rgb(255,220,0)','rgb(255,231,0)','rgb(255,240,0)','rgb(255,250,0)'],
'Beach': ['rgb(0,252,0)','rgb(0,233,0)','rgb(0,212,0)','rgb(0,189,0)','rgb(0,169,0)','rgb(0,148,0)','rgb(0,129,4)','rgb(0,145,46)','rgb(0,162,90)','rgb(0,180,132)','rgb(29,143,136)','rgb(73,88,136)','rgb(115,32,136)','rgb(81,9,64)','rgb(124,51,23)','rgb(162,90,0)','rgb(194,132,0)','rgb(220,171,0)','rgb(231,213,0)','rgb(0,0,13)','rgb(0,0,55)','rgb(0,0,92)','rgb(0,0,127)','rgb(0,0,159)','rgb(0,0,196)','rgb(0,0,233)'],
'BlueRed': ['rgb(0,0,131)','rgb(0,0,168)','rgb(0,0,208)','rgb(0,0,247)','rgb(0,27,255)','rgb(0,67,255)','rgb(0,108,255)','rgb(0,148,255)','rgb(0,187,255)','rgb(0,227,255)','rgb(8,255,247)','rgb(48,255,208)','rgb(87,255,168)','rgb(127,255,127)','rgb(168,255,87)','rgb(208,255,48)','rgb(247,255,8)','rgb(255,224,0)','rgb(255,183,0)','rgb(255,143,0)','rgb(255,104,0)','rgb(255,64,0)','rgb(255,23,0)','rgb(238,0,0)','rgb(194,0,0)','rgb(150,0,0)'],
'Rainbow': ['rgb(125,0,255)','rgb(85,0,255)','rgb(39,0,255)','rgb(0,6,255)','rgb(0,51,255)','rgb(0,97,255)','rgb(0,141,255)','rgb(0,187,255)','rgb(0,231,255)','rgb(0,255,233)','rgb(0,255,189)','rgb(0,255,143)','rgb(0,255,99)','rgb(0,255,53)','rgb(0,255,9)','rgb(37,255,0)','rgb(83,255,0)','rgb(127,255,0)','rgb(173,255,0)','rgb(217,255,0)','rgb(255,248,0)','rgb(255,203,0)','rgb(255,159,0)','rgb(255,113,0)','rgb(255,69,0)','rgb(255,23,0)'],
'Binary': ['rgb(215,215,215)','rgb(206,206,206)','rgb(196,196,196)','rgb(185,185,185)','rgb(176,176,176)','rgb(166,166,166)','rgb(155,155,155)','rgb(145,145,145)','rgb(136,136,136)','rgb(125,125,125)','rgb(115,115,115)','rgb(106,106,106)','rgb(95,95,95)','rgb(85,85,85)','rgb(76,76,76)','rgb(66,66,66)','rgb(55,55,55)','rgb(46,46,46)','rgb(36,36,36)','rgb(25,25,25)','rgb(16,16,16)','rgb(6,6,6)'],
'GistEarth': ['rgb(0,0,0)','rgb(2,7,117)','rgb(9,30,118)','rgb(16,53,120)','rgb(23,73,122)','rgb(31,93,124)','rgb(39,110,125)','rgb(47,126,127)','rgb(51,133,119)','rgb(57,138,106)','rgb(62,145,94)','rgb(66,150,82)','rgb(74,157,71)','rgb(97,162,77)','rgb(121,168,83)','rgb(136,173,85)','rgb(153,176,88)','rgb(170,180,92)','rgb(185,182,94)','rgb(189,173,99)','rgb(192,164,101)','rgb(203,169,124)','rgb(215,178,149)','rgb(226,192,176)','rgb(238,212,204)','rgb(248,236,236)'],
'BlueWaves': ['rgb(83,0,215)','rgb(43,6,108)','rgb(9,16,16)','rgb(8,32,25)','rgb(0,50,8)','rgb(27,64,66)','rgb(69,67,178)','rgb(115,62,210)','rgb(155,50,104)','rgb(178,43,41)','rgb(180,51,34)','rgb(161,78,87)','rgb(124,117,187)','rgb(78,155,203)','rgb(34,178,85)','rgb(4,176,2)','rgb(9,152,27)','rgb(4,118,2)','rgb(34,92,85)','rgb(78,92,203)','rgb(124,127,187)','rgb(161,187,87)','rgb(180,248,34)','rgb(178,220,41)','rgb(155,217,104)','rgb(115,254,210)'],
'BlueGreenRedYellow': ['rgb(0,0,0)','rgb(0,0,20)','rgb(0,0,41)','rgb(0,0,62)','rgb(0,25,83)','rgb(0,57,101)','rgb(0,87,101)','rgb(0,118,101)','rgb(0,150,101)','rgb(0,150,69)','rgb(0,148,37)','rgb(0,141,6)','rgb(60,120,0)','rgb(131,87,0)','rgb(180,25,0)','rgb(203,13,0)','rgb(208,36,0)','rgb(213,60,0)','rgb(219,83,0)','rgb(224,106,0)','rgb(229,129,0)','rgb(233,152,0)','rgb(238,176,0)','rgb(243,199,0)','rgb(248,222,0)','rgb(254,245,0)']
};
async function handleSettingSubmit(selector, target) {
let form = document.querySelector(selector)
let formData = new FormData(form)
try {
const res = await fetch(form.action, { method: 'POST', body: formData });
if (res.ok) {
let text = await res.text()
popMessage(text, target, '#048109')
} else {
throw new Error('Response Code ' + res.status + '-> ' + res.statusText);
}
} catch (err) {
popMessage(err, target, '#d63384')
}
return false
}
function popMessage(response, restarget, rescolor) {
message = {msg: response, target: restarget, color: rescolor}
displayMessage = true
setTimeout(function() {
displayMessage = false
}, 3500)
}
</script>
<Row cols={3} class="p-2 g-2">
<!-- LINE WIDTH -->
<Col><Card class="h-100">
<!-- Important: Function with arguments needs to be event-triggered like on:submit={() => functionName('Some','Args')} OR no arguments and like this: on:submit={functionName} -->
<form id="line-width-form" method="post" action="/api/configuration/" class="card-body" on:submit|preventDefault={() => handleSettingSubmit('#line-width-form', 'lw')}>
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
<CardTitle style="margin-bottom: 1em; display: flex; align-items: center;">
<div>Line Width</div>
<!-- Expand If-Clause for clarity once -->
{#if displayMessage && message.target == 'lw'}
<div style="margin-left: auto; font-size: 0.9em;">
<code style="color: {message.color};" out:fade>
Update: {message.msg}
</code>
</div>
{/if}
</CardTitle>
<input type="hidden" name="key" value="plot_general_lineWidth"/>
<div class="mb-3">
<label for="value" class="form-label">Line Width</label>
<input type="number" class="form-control" id="lwvalue" name="value" aria-describedby="lineWidthHelp" value="{config.plot_general_lineWidth}" min="1"/>
<div id="lineWidthHelp" class="form-text">Width of the lines in the timeseries plots.</div>
</div>
<Button color="primary" type="submit">Submit</Button>
</form>
</Card></Col>
<!-- PLOTS PER ROW -->
<Col><Card class="h-100">
<form id="plots-per-row-form" method="post" action="/api/configuration/" class="card-body" on:submit|preventDefault={() => handleSettingSubmit('#plots-per-row-form', 'ppr')}>
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
<CardTitle style="margin-bottom: 1em; display: flex; align-items: center;">
<div>Plots per Row</div>
{#if displayMessage && message.target == 'ppr'}<div style="margin-left: auto; font-size: 0.9em;"><code style="color: {message.color};" out:fade>Update: {message.msg}</code></div>{/if}
</CardTitle>
<input type="hidden" name="key" value="plot_view_plotsPerRow"/>
<div class="mb-3">
<label for="value" class="form-label">Plots per Row</label>
<input type="number" class="form-control" id="pprvalue" name="value" aria-describedby="plotsperrowHelp" value="{config.plot_view_plotsPerRow }" min="1"/>
<div id="plotsperrowHelp" class="form-text">How many plots to show next to each other on pages such as /monitoring/job/, /monitoring/system/...</div>
</div>
<Button color="primary" type="submit">Submit</Button>
</form>
</Card></Col>
<!-- BACKGROUND -->
<Col><Card class="h-100">
<form id="backgrounds-form" method="post" action="/api/configuration/" class="card-body" on:submit|preventDefault={() => handleSettingSubmit('#backgrounds-form', 'bg')}>
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
<CardTitle style="margin-bottom: 1em; display: flex; align-items: center;">
<div>Colored Backgrounds</div>
{#if displayMessage && message.target == 'bg'}<div style="margin-left: auto; font-size: 0.9em;"><code style="color: {message.color};" out:fade>Update: {message.msg}</code></div>{/if}
</CardTitle>
<input type="hidden" name="key" value="plot_general_colorBackground"/>
<div class="mb-3">
<div>
{#if config.plot_general_colorBackground}
<input type="radio" id="true" name="value" value="true" checked/>
{:else}
<input type="radio" id="true" name="value" value="true" />
{/if}
<label for="true">Yes</label>
</div>
<div>
{#if config.plot_general_colorBackground}
<input type="radio" id="false" name="value" value="false" />
{:else}
<input type="radio" id="false" name="value" value="false" checked/>
{/if}
<label for="false">No</label>
</div>
</div>
<Button color="primary" type="submit">Submit</Button>
</form>
</Card></Col>
</Row>
<Row cols={1} class="p-2 g-2">
<!-- COLORSCHEME -->
<Col><Card>
<form id="colorscheme-form" method="post" action="/api/configuration/" class="card-body" on:submit|preventDefault={() => handleSettingSubmit("#colorscheme-form", "cs")}>
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
<CardTitle style="margin-bottom: 1em; display: flex; align-items: center;">
<div>Color Scheme for Timeseries Plots</div>
{#if displayMessage && message.target == 'cs'}<div style="margin-left: auto; font-size: 0.9em;"><code style="color: {message.color};" out:fade>Update: {message.msg}</code></div>{/if}
</CardTitle>
<input type="hidden" name="key" value="plot_general_colorscheme"/>
<Table hover>
<tbody>
{#each Object.entries(colorschemes) as [name, rgbrow]}
<tr>
<th scope="col">{name}</th>
<td>
{#if rgbrow.join(',') == config.plot_general_colorscheme}
<input type="radio" name="value" value={JSON.stringify(rgbrow)} checked/>
{:else}
<input type="radio" name="value" value={JSON.stringify(rgbrow)}/>
{/if}
</td>
<td>
{#each rgbrow as rgb}
<span class="color-dot" style="background-color: {rgb};"></span>
{/each}
</td>
</tr>
{/each}
</tbody>
</Table>
<Button color="primary" type="submit">Submit</Button>
</form>
</Card></Col>
</Row>
<style>
.color-dot {
height: 10px;
width: 10px;
border-radius: 50%;
display: inline-block;
}
</style>

View File

@ -0,0 +1,70 @@
<script>
import { Card, CardTitle, CardBody } from 'sveltestrap'
import { createEventDispatcher } from 'svelte'
import { fade } from 'svelte/transition'
const dispatch = createEventDispatcher()
let message = {msg: '', color: '#d63384'}
let displayMessage = false
async function handleAddRole() {
const username = document.querySelector('#add-role-username').value
const role = document.querySelector('#add-role-select').value
if (username == "" || role == "") {
alert('Please fill in a username and select a role.')
return
}
let formData = new FormData()
formData.append('username', username)
formData.append('add-role', role)
try {
const res = await fetch(`/api/user/${username}`, { method: 'POST', body: formData })
if (res.ok) {
let text = await res.text()
popMessage(text, '#048109')
reloadUserList()
} else {
throw new Error('Response Code ' + res.status + '-> ' + res.statusText)
}
} catch (err) {
popMessage(err, '#d63384')
}
}
function popMessage(response, rescolor) {
message = {msg: response, color: rescolor}
displayMessage = true
setTimeout(function() {
displayMessage = false
}, 3500)
}
function reloadUserList() {
dispatch('reload')
}
</script>
<Card>
<CardBody>
<CardTitle class="mb-3">Add Role to User</CardTitle>
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="username" id="add-role-username"/>
<select class="form-select" id="add-role-select">
<option selected value="">Role...</option>
<option value="user">User</option>
<option value="admin">Admin</option>
<option value="api">API</option>
</select>
<!-- PreventDefault on Sveltestrap-Button more complex to achieve than just use good ol' html button -->
<!-- see: https://stackoverflow.com/questions/69630422/svelte-how-to-use-event-modifiers-in-my-own-components -->
<button class="btn btn-primary" type="button" id="add-role-button" on:click|preventDefault={handleAddRole}>Add</button>
</div>
<p>
{#if displayMessage}<b><code style="color: {message.color};" out:fade>Update: {message.msg}</code></b>{/if}
</p>
</CardBody>
</Card>

View File

@ -0,0 +1,85 @@
<script>
import { Button, Card, CardTitle } from 'sveltestrap'
import { createEventDispatcher } from 'svelte'
import { fade } from 'svelte/transition'
const dispatch = createEventDispatcher()
let message = {msg: '', color: '#d63384'}
let displayMessage = false
async function handleUserSubmit() {
let form = document.querySelector('#create-user-form')
let formData = new FormData(form)
try {
const res = await fetch(form.action, { method: 'POST', body: formData });
if (res.ok) {
let text = await res.text()
popMessage(text, '#048109')
reloadUserList()
} else {
throw new Error('Response Code ' + res.status + '-> ' + res.statusText);
}
} catch (err) {
popMessage(err, '#d63384')
}
}
function popMessage(response, rescolor) {
message = {msg: response, color: rescolor}
displayMessage = true
setTimeout(function() {
displayMessage = false
}, 3500)
}
function reloadUserList() {
dispatch('reload')
}
</script>
<Card>
<form id="create-user-form" method="post" action="/api/users/" class="card-body" on:submit|preventDefault={handleUserSubmit}>
<CardTitle class="mb-3">Create User</CardTitle>
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" aria-describedby="nameHelp"/>
<div id="nameHelp" class="form-text">Optional, can be blank.</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" name="email" aria-describedby="emailHelp"/>
<div id="emailHelp" class="form-text">Optional, can be blank.</div>
</div>
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" aria-describedby="usernameHelp"/>
<div id="usernameHelp" class="form-text">Must be unique.</div>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" aria-describedby="passwordHelp"/>
<div id="passwordHelp" class="form-text">Only API users are allowed to have a blank password. Users with a blank password can only authenticate via Tokens.</div>
</div>
<div class="mb-3">
<p>Role:</p>
<div>
<input type="radio" id="user" name="role" value="user" checked/>
<label for="user">User (regular user, same as if created via LDAP sync.)</label>
</div>
<div>
<input type="radio" id="api" name="role" value="api"/>
<label for="api">API</label>
</div>
<div>
<input type="radio" id="admin" name="role" value="admin"/>
<label for="admin">Admin</label>
</div>
</div>
<p style="display: flex; align-items: center;">
<Button type="submit" color="primary">Submit</Button>
{#if displayMessage}<div style="margin-left: 1.5em;"><b><code style="color: {message.color};" out:fade>{message.msg}</code></b></div>{/if}
</p>
</form>
</Card>

View File

@ -0,0 +1,29 @@
<script>
import { onMount } from 'svelte'
import { Card, CardBody, CardTitle } from 'sveltestrap'
let scrambled
onMount(() => {
scrambled = window.localStorage.getItem("cc-scramble-names") != null
})
function handleScramble() {
if (!scrambled) {
scrambled = true
window.localStorage.setItem("cc-scramble-names", "true")
} else {
scrambled = false
window.localStorage.removeItem("cc-scramble-names")
}
}
</script>
<Card class="h-100">
<CardBody>
<CardTitle class="mb-3">Scramble Names / Presentation Mode</CardTitle>
<input type="checkbox" id="scramble-names-checkbox" style="margin-right: 1em;" on:click={handleScramble} bind:checked={scrambled}/>
Active?
</CardBody>
</Card>

View File

@ -0,0 +1,67 @@
<script>
import { Button, Table, Card, CardTitle, CardBody } from 'sveltestrap'
import { onMount, createEventDispatcher } from "svelte";
import ShowUsersRow from './ShowUsersRow.svelte'
export let users = []
const dispatch = createEventDispatcher();
function reloadUserList() {
dispatch('reload')
}
function deleteUser(username) {
if (confirm('Are you sure?')) {
let formData = new FormData()
formData.append('username', username)
fetch('/api/users/', { method: 'DELETE', body: formData }).then(res => {
if (res.status == 200) {
reloadUserList()
} else {
confirm(res.statusText)
}
})
}
}
$: userList = users
</script>
<Card class="h-100">
<CardBody>
<CardTitle class="mb-3">Special Users</CardTitle>
<p>
Not created by an LDAP sync and/or having a role other than <code>user</code>
<Button color="secondary" size="sm" on:click={reloadUserList} style="float: right;">Reload</Button>
</p>
<div style="width: 100%; max-height: 500px; overflow-y: scroll;">
<Table hover>
<thead>
<tr>
<th>Username</th>
<th>Name</th>
<th>Email</th>
<th>Roles</th>
<th>JWT</th>
<th>Delete</th>
</tr>
</thead>
<tbody id="users-list">
{#each userList as user}
<tr id="user-{user.username}">
<ShowUsersRow {user}/>
<td><button class="btn btn-danger del-user" on:click={deleteUser(user.username)}>Delete</button></td>
</tr>
{:else}
<tr>
<td colspan="4">
<div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div>
</td>
</tr>
{/each}
</tbody>
</Table>
</div>
</CardBody>
</Card>

View File

@ -0,0 +1,27 @@
<script>
import { Button } from 'sveltestrap'
export let user
let jwt = ""
function getUserJwt(username) {
fetch(`/api/jwt/?username=${username}`)
.then(res => res.text())
.then(text => {
jwt = text
navigator.clipboard.writeText(text).catch(reason => console.error(reason))
})
}
</script>
<td>{user.username}</td>
<td>{user.name}</td>
<td>{user.email}</td>
<td><code>{user.roles.join(', ')}</code></td>
<td>
{#if ! jwt}
<Button color="success" on:click={getUserJwt(user.username)}>Gen. JWT</Button>
{:else}
<textarea rows="3" cols="20">{jwt}</textarea>
{/if}
</td>

View File

@ -0,0 +1,12 @@
import {} from './header.entrypoint.js'
import Configsvelte from './Configsvelte.root.svelte'
new Configsvelte({
target: document.getElementById('svelte-app'),
props: {
user: user
},
context: new Map([
['cc-config', clusterCockpitConfig]
])
})

View File

@ -0,0 +1,15 @@
{{define "content"}}
<div id="svelte-app"></div>
{{end}}
{{define "stylesheets"}}
<link rel='stylesheet' href='/build/configsvelte.css'>
{{end}}
{{define "javascript"}}
<script>
const user = {{ .User }};
const filterPresets = {{ .FilterPresets }};
const clusterCockpitConfig = {{ .Config }};
</script>
<script src='/build/configsvelte.js'></script>
{{end}}