mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-07-27 06:36:07 +02:00
Initial commit on branch: Rewrite config.tmpl as svelte component(s)
This commit is contained in:
70
web/frontend/src/config/admin/AddRole.svelte
Normal file
70
web/frontend/src/config/admin/AddRole.svelte
Normal 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>
|
85
web/frontend/src/config/admin/AddUser.svelte
Normal file
85
web/frontend/src/config/admin/AddUser.svelte
Normal 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>
|
29
web/frontend/src/config/admin/Options.svelte
Normal file
29
web/frontend/src/config/admin/Options.svelte
Normal 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>
|
67
web/frontend/src/config/admin/ShowUsers.svelte
Normal file
67
web/frontend/src/config/admin/ShowUsers.svelte
Normal 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>
|
27
web/frontend/src/config/admin/ShowUsersRow.svelte
Normal file
27
web/frontend/src/config/admin/ShowUsersRow.svelte
Normal 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>
|
Reference in New Issue
Block a user