kafka-dance-frontend/src/routes/settings/+page.svelte

226 lines
6.3 KiB
Svelte

<script>
import { onMount } from "svelte";
import { backendUrl, saslAuthMethods } from "../../lib/constants.js";
import { apiFetch } from "../../utils.js";
const jsonRequest = type => async (path, object) => fetch(backendUrl + path, {
method: type,
body: JSON.stringify(object),
headers: {
'Content-Type': 'application/json'
}
})
const findLast = array => {
if (!array || array.length === 0) {
return undefined
}
return array[array.length - 1]
}
const deepCopy = object => JSON.parse(JSON.stringify(object))
const post = jsonRequest('POST')
const put = jsonRequest('PUT')
const addCluster = async cluster => fetchClusters(await post('/clusters', cluster))
const updateCluster = async cluster => fetchClusters(await put('/clusters', cluster))
const deleteCluster = async clusterName => fetchClusters(await post('/clusters/delete', { clusterName }))
let clusters = []
const fetchClusters = async (response) => {
if (response) {
clusters = await response.json()
} else {
clusters = await apiFetch('/clusters')
}
clusters = Object.fromEntries(
Object.entries(clusters).map(([name, cluster]) =>
[name, ({ ...cluster, clusterName: name, originalName: name })]))
const values = Object.values(clusters)
if (values.length > 0) {
config = deepCopy(values[0])
}
}
onMount(async () => {
await fetchClusters()
console.log('onMount', clusters)
})
console.log('instant', clusters)
const startNewCluster = () => {
config = emptyConfig()
}
const emptyConfig = () => ({
originalName: null, // A `null` originalName indicates that the config is new,
clusterName: '', // and will be POSTed as a new cluster, not PUTted, updating an existing one
clientId: '',
brokers: [''],
ssl: true,
sasl: {
mechanism: '',
username: '',
password: ''
}
})
let config = emptyConfig()
</script>
<svelte:head>
<meta name="description" content="Kafka Dance Settings"/>
</svelte:head>
<section>
<div class="query-settings">
<h1>Cluster Configuration</h1>
<div class="settings-option">
Cluster Name: <input bind:value={config.clusterName}>
</div>
<div class="settings-option">
Client Id: <input bind:value={config.clientId}>
</div>
<div class="settings-option">
Brokers:
{#each config.brokers as broker}
<input bind:value={broker}>
{/each}
{#if findLast(config.brokers) !== ''}
<button on:click={() => {config.brokers = [...config.brokers, '']}}>+</button>
{/if}
</div>
<div class="settings-option">
<div>
SSL:
<select bind:value={config.ssl}>
<option value={true}>True</option>
<option value={false}>False</option>
<option value={{}}>Advanced</option>
</select>
{#if typeof(config.ssl) === 'object'}
<div>
Reject Unauthorized
<input type="checkbox" bind:checked={config.ssl.rejectUnauthorized}>
</div>
{/if}
</div>
</div>
<!-- TODO: Add options for SSL config -->
<div class="settings-option">
<div>SASL:</div>
<div class="settings-sub-option">
<div>Auth Mechanism:</div>
<select bind:value={config.sasl.mechanism}>
{#each saslAuthMethods as method}
<option value={method}>{method}</option>
{/each}
</select>
</div>
<div class="settings-sub-option">
<div>Username:</div>
<input bind:value={config.sasl.username}>
</div>
<div class="settings-sub-option">
<div>Password:</div>
<input type="password" bind:value={config.sasl.password}>
</div>
</div>
<button style="float: right;"
on:click={() => (config.originalName ? updateCluster : addCluster)(config)}>{config.originalName ? 'Update Cluster Config' : 'Add Cluster Config'}</button>
</div>
<div class="data-view">
<button style="margin: 1em;" on:click={startNewCluster}>Add New Cluster</button>
{#each Object.entries(clusters) as [clusterName, cluster]}
<div class={"cluster-listing" + (clusterName === config.clusterName ? " selected" : "")}>
<h2 class="cluster-title">{cluster.clusterName}</h2>
<div class="cluster-buttons">
<button on:click={() => { config = deepCopy(clusters[clusterName]); }}>View & Edit</button>
<button on:click={() => deleteCluster(clusterName)}>Delete</button>
</div>
</div>
{/each}
</div>
</section>
<style>
.cluster-listing {
display: flex;
flex-direction: row;
border-style: solid;
border-width: 1px;
padding: 1em;
margin: 1em;
justify-content: space-between;
align-items: center;
}
.selected {
background-color: rgba(0, 0, 0, 0.1);
}
.cluster-buttons button {
min-width: 5em;
margin-left: 1em;
}
.cluster-title {
}
section {
display: flex;
flex-direction: row;
flex: 1;
justify-content: space-between;
}
h1 {
width: 100%;
}
textarea {
overflow: hidden;
z-index: 1;
margin-left: 0;
background-color: #222222;
color: #d0dde9;
border: none;
font-size: 14px;
font-family: var(--font-mono);
padding: 1em;
overflow-x: auto;
min-width: 20vw;
}
.query-settings {
margin-right: 1rem;
padding: 1em;
height: 86vh;
overflow: auto;
display: flex;
flex-direction: column;
}
.settings-sub-option {
margin-top: 0.2em;
display: flex;
flex-direction: row;
justify-content: space-between;
}
.no-query-data h2 {
font-size: 32px;
color: rgba(0, 0, 0, 0.3);
}
</style>