Some appearance cleanup.
A bit of refactoring, prepping for increased flexibility, and less hard-coding.
This commit is contained in:
parent
d926a9cd36
commit
454f12003a
44
src/app.css
44
src/app.css
|
@ -1,7 +1,8 @@
|
||||||
@import '@fontsource/fira-mono';
|
@import '@fontsource/fira-mono';
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Fira+Sans&family=Montserrat:wght@500&display=swap');
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
font-family: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
font-family: 'Fira Sans', Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||||
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
--font-mono: 'Fira Mono', monospace;
|
--font-mono: 'Fira Mono', monospace;
|
||||||
--pure-white: #ffffff;
|
--pure-white: #ffffff;
|
||||||
|
@ -9,13 +10,31 @@
|
||||||
--secondary-color: #d0dde9;
|
--secondary-color: #d0dde9;
|
||||||
--tertiary-color: #edf0f8;
|
--tertiary-color: #edf0f8;
|
||||||
--accent-color: #ff0df8;
|
--accent-color: #ff0df8;
|
||||||
--heading-color: rgba(0, 0, 0, 0.7);
|
--heading-color: rgba(0, 0, 0, 0.8);
|
||||||
--text-color: #444444;
|
--text-color: #444444;
|
||||||
--background-without-opacity: rgba(255, 255, 255, 0.7);
|
--background-without-opacity: rgba(255, 255, 255, 0.7);
|
||||||
--column-width: 42rem;
|
--column-width: 42rem;
|
||||||
--column-margin-top: 4rem;
|
--column-margin-top: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select, button {
|
||||||
|
font-family: 'Fira Sans', Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
|
font-size: 90%;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 0;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: #888888;
|
||||||
|
padding: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0.2em 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
font-family: 'Liberation Sans', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -54,14 +73,21 @@ body::before {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #111111;
|
||||||
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
p {
|
p {
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--heading-color);
|
color: var(--heading-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +134,20 @@ button {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.data-view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: #95c1d3;
|
||||||
|
flex-grow: 2;
|
||||||
|
padding: 1em;
|
||||||
|
background-color: #e2eaff;
|
||||||
|
height: 86vh;
|
||||||
|
max-width: 70vw;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
.settings-option {
|
.settings-option {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
|
|
@ -2,3 +2,7 @@ export const queryMode = {
|
||||||
REAL_TIME: 'realTime',
|
REAL_TIME: 'realTime',
|
||||||
ONE_SHOT: 'oneShot'
|
ONE_SHOT: 'oneShot'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const backendAddressAndPort = 'localhost:3000'
|
||||||
|
|
||||||
|
export const backendUrl = `http://${backendAddressAndPort}`
|
|
@ -8,7 +8,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
<ul>
|
<ul>
|
||||||
<li class:active={$page.url.pathname === '/'}>
|
<li class:active={$page.url.pathname === '/'}>
|
||||||
<a sveltekit:prefetch href="/">Home</a>
|
<a sveltekit:prefetch href="/">Message Search</a>
|
||||||
</li>
|
</li>
|
||||||
<li class:active={$page.url.pathname === '/settings'}>
|
<li class:active={$page.url.pathname === '/settings'}>
|
||||||
<a sveltekit:prefetch href="/settings">Settings</a>
|
<a sveltekit:prefetch href="/settings">Settings</a>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// noinspection JSCheckFunctionSignatures
|
// noinspection JSCheckFunctionSignatures
|
||||||
|
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
import {queryMode} from "./constants.js";
|
import { backendAddressAndPort, queryMode } from "./constants.js";
|
||||||
|
|
||||||
const mockItems = [
|
const mockItems = [
|
||||||
{
|
{
|
||||||
|
@ -21,7 +21,7 @@ const mockItems = [
|
||||||
},
|
},
|
||||||
].map(o => ({ value: o, timestamp: new Date().getTime() }))
|
].map(o => ({ value: o, timestamp: new Date().getTime() }))
|
||||||
|
|
||||||
const testMode = true
|
const testMode = false
|
||||||
export const state = writable({
|
export const state = writable({
|
||||||
items: [],
|
items: [],
|
||||||
itemCount: undefined,
|
itemCount: undefined,
|
||||||
|
@ -38,13 +38,7 @@ let disconnected = false
|
||||||
|
|
||||||
const getRandomFromArray = array => array[Math.floor(Math.random() * array.length)]
|
const getRandomFromArray = array => array[Math.floor(Math.random() * array.length)]
|
||||||
|
|
||||||
export const query = async ({ cluster, topic, mode, jsFilter, queryCode, maxItems, mutableObjects }) => {
|
const testQuery = (mode, jsFilter, queryCode) => {
|
||||||
updateClearError(s => ({...s, items: [], itemCount: 0}))
|
|
||||||
if (disconnected) {
|
|
||||||
connect()
|
|
||||||
disconnected = false
|
|
||||||
}
|
|
||||||
if (testMode) {
|
|
||||||
try {
|
try {
|
||||||
const f = new Function('message', 'value', queryCode)
|
const f = new Function('message', 'value', queryCode)
|
||||||
if (mode === queryMode.REAL_TIME) {
|
if (mode === queryMode.REAL_TIME) {
|
||||||
|
@ -63,9 +57,20 @@ export const query = async ({ cluster, topic, mode, jsFilter, queryCode, maxItem
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
updateClearError(s => ({ ...s, error: e.toString() }))
|
updateClearError(s => ({ ...s, error: e.toString() }))
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
updateClearError(s => ({ ...s, items: [], itemCount: 0 }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
export const query = async ({ cluster, topic, mode, jsFilter, queryCode, maxItems, mutableObjects }) => {
|
||||||
|
updateClearError(s => ({ ...s, items: [], itemCount: 0 }))
|
||||||
|
if (testMode) {
|
||||||
|
testQuery(mode, jsFilter, queryCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (disconnected) {
|
||||||
|
connect()
|
||||||
|
disconnected = false
|
||||||
|
}
|
||||||
|
updateClearError(s => ({ ...s, items: [], itemCount: 0 }))
|
||||||
itemLimit = maxItems
|
itemLimit = maxItems
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
cluster,
|
cluster,
|
||||||
|
@ -78,27 +83,26 @@ export const query = async ({ cluster, topic, mode, jsFilter, queryCode, maxItem
|
||||||
}
|
}
|
||||||
|
|
||||||
export const connect = () => {
|
export const connect = () => {
|
||||||
ws = new WebSocket(`ws://localhost:3000`)
|
ws = new WebSocket(`ws://${backendAddressAndPort}`)
|
||||||
if (!ws) {
|
if (!ws) {
|
||||||
updateClearError(s => ({ ...s, error: 'Unable to connect to websocket.' }))
|
updateClearError(s => ({ ...s, error: 'Unable to connect to websocket.' }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ws.addEventListener('close', () => { disconnected = true })
|
ws.addEventListener('close', () => {
|
||||||
|
disconnected = true
|
||||||
|
})
|
||||||
|
|
||||||
ws.addEventListener('open', () => {
|
ws.addEventListener('open', () => {
|
||||||
console.log('WebSocket opened')
|
console.log('WebSocket opened')
|
||||||
// ws.send(JSON.stringify({
|
|
||||||
// searchCode: 'return message.value?.UserId === "MINTERCI"'
|
|
||||||
// }))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ws.addEventListener('message', message => {
|
ws.addEventListener('message', message => {
|
||||||
//console.log('WebSocket message received', message)
|
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(message.data)
|
data = JSON.parse(message.data)
|
||||||
switch (data.type) {
|
console.log('WebSocket message received', data)
|
||||||
|
switch (data?.type.toLowerCase()) {
|
||||||
case 'complete':
|
case 'complete':
|
||||||
updateClearError(s => ({
|
updateClearError(s => ({
|
||||||
...s,
|
...s,
|
||||||
|
@ -114,7 +118,7 @@ export const connect = () => {
|
||||||
case 'message':
|
case 'message':
|
||||||
updateClearError(s => ({
|
updateClearError(s => ({
|
||||||
...s,
|
...s,
|
||||||
items: [data.message, ...s.items].slice(0, itemLimit)
|
items: console.log('new item', data.message) || [data.message, ...s.items].slice(0, itemLimit)
|
||||||
}))
|
}))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,16 @@
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { JsonView } from '@zerodevx/svelte-json-view'
|
import { JsonView } from '@zerodevx/svelte-json-view'
|
||||||
import { state, connect, query } from '$lib/state';
|
import { state, connect, query } from '$lib/state';
|
||||||
import { queryMode } from "../lib/constants.js";
|
import { queryMode, backendUrl } from "../lib/constants.js";
|
||||||
|
|
||||||
let expandAll = false
|
let expandAll = true
|
||||||
let showMetadata = false
|
let showMetadata = true
|
||||||
|
|
||||||
const querySettings = {
|
const querySettings = {
|
||||||
topic: null,
|
topic: null,
|
||||||
cluster: null,
|
cluster: null,
|
||||||
mode: queryMode.REAL_TIME,
|
mode: queryMode.REAL_TIME,
|
||||||
jsFilter: true,
|
jsFilter: false,
|
||||||
maxItems: 20,
|
maxItems: 20,
|
||||||
mutableObjects: false,
|
mutableObjects: false,
|
||||||
queryCode:
|
queryCode:
|
||||||
|
@ -21,21 +21,23 @@
|
||||||
return message.value.eventType === "Television";`
|
return message.value.eventType === "Television";`
|
||||||
}
|
}
|
||||||
|
|
||||||
const makeRequest = async () => query(querySettings)
|
|
||||||
|
|
||||||
let topics = []
|
let topics = []
|
||||||
let clusters = []
|
let clusters = []
|
||||||
const updateTopics = async () => {
|
const updateTopics = async () => {
|
||||||
topics = []
|
topics = []
|
||||||
const response = await fetch(`http://localhost:3000/topics/${querySettings.cluster}`)
|
try {
|
||||||
|
const response = await fetch(`${backendUrl}/topics/${querySettings.cluster}`)
|
||||||
topics = await response.json()
|
topics = await response.json()
|
||||||
topics.sort()
|
topics.sort()
|
||||||
console.log('topics', topics)
|
console.log('topics', topics)
|
||||||
querySettings.topic = topics[0]
|
querySettings.topic = topics[0]
|
||||||
|
} catch (e) {
|
||||||
|
console.log('fetch error:', e.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
connect();
|
connect();
|
||||||
const response = await fetch(`http://localhost:3000/clusters`)
|
const response = await fetch(`${backendUrl}/clusters`)
|
||||||
clusters = Object.keys(await response.json())
|
clusters = Object.keys(await response.json())
|
||||||
querySettings.cluster = clusters[0]
|
querySettings.cluster = clusters[0]
|
||||||
await updateTopics()
|
await updateTopics()
|
||||||
|
@ -86,7 +88,7 @@ return message.value.eventType === "Television";`
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<div class="settings-option">
|
<div class="settings-option">
|
||||||
<div class="query-input-header">Use JavaScript to filter messages <input type=checkbox bind:checked={querySettings.jsFilter}></div>
|
<div class="query-input-header"><div on:click={() => querySettings.jsFilter = !querySettings.jsFilter}>Use JavaScript to filter messages</div> <input type=checkbox bind:checked={querySettings.jsFilter}></div>
|
||||||
{#if querySettings.jsFilter}
|
{#if querySettings.jsFilter}
|
||||||
<div class="query-input-display">
|
<div class="query-input-display">
|
||||||
<div title="If enabled, mutations made by the below code will be displayed in the result data.">
|
<div title="If enabled, mutations made by the below code will be displayed in the result data.">
|
||||||
|
@ -113,7 +115,7 @@ return message.value.eventType === "Television";`
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="query-button">
|
<div class="query-button">
|
||||||
<button on:click={makeRequest}>Start Query</button>
|
<button on:click={() => query(querySettings)}>Start Query</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
@ -127,12 +129,12 @@ return message.value.eventType === "Television";`
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $state.items?.length > 0}
|
{#if $state.items?.length > 0}
|
||||||
<div class="json-view">
|
<div class="data-view">
|
||||||
<JsonView json={showMetadata ? $state.items : $state.items.map(item => item.value)}
|
<JsonView json={console.log('state.items', $state.items) || showMetadata ? $state.items : $state.items.map(item => item.value)}
|
||||||
depth={expandAll ? Infinity : 1}/>
|
depth={expandAll ? Infinity : 1}/>
|
||||||
</div>
|
</div>
|
||||||
{:else }
|
{:else }
|
||||||
<div class="json-view no-query-data">
|
<div class="data-view no-query-data">
|
||||||
<h2>No query data</h2>
|
<h2>No query data</h2>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -144,6 +146,7 @@ return message.value.eventType === "Television";`
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
@ -171,8 +174,16 @@ return message.value.eventType === "Television";`
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 1em;
|
||||||
|
height: 86vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.query-input-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
.query-input-display {
|
.query-input-display {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -180,6 +191,7 @@ return message.value.eventType === "Television";`
|
||||||
}
|
}
|
||||||
.query-input {
|
.query-input {
|
||||||
min-height: 4vw;
|
min-height: 4vw;
|
||||||
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-type {
|
.query-type {
|
||||||
|
@ -220,15 +232,6 @@ return message.value.eventType === "Television";`
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.json-view {
|
|
||||||
border-radius: 4px;
|
|
||||||
flex-grow: 6;
|
|
||||||
padding: 1em;
|
|
||||||
background-color: #e2eaff;
|
|
||||||
height: 86vh;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-query-data {
|
.no-query-data {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import { backendUrl } from "../../lib/constants.js";
|
||||||
|
|
||||||
const jsonRequest = type => async (path, object) => fetch('http://localhost:3000' + path, {
|
const jsonRequest = type => async (path, object) => fetch(backendUrl + path, {
|
||||||
method: type,
|
method: type,
|
||||||
body: JSON.stringify(object),
|
body: JSON.stringify(object),
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -9,23 +10,48 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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 post = jsonRequest('POST')
|
||||||
const put = jsonRequest('PUT')
|
const put = jsonRequest('PUT')
|
||||||
|
|
||||||
const addCluster = async cluster => post('/clusters', cluster)
|
const addCluster = async cluster => fetchClusters(await post('/clusters', cluster))
|
||||||
const updateCluster = async cluster => put('/clusters', cluster)
|
const updateCluster = async cluster => fetchClusters(await put('/clusters', cluster))
|
||||||
|
const deleteCluster = async clusterName => fetchClusters(await post('/clusters/delete', { clusterName }))
|
||||||
|
|
||||||
let clusters = []
|
let clusters = []
|
||||||
onMount(async () => {
|
const fetchClusters = async (response) => {
|
||||||
const response = await fetch(`http://localhost:3000/clusters`)
|
response ??= await fetch(`${backendUrl}/clusters`)
|
||||||
clusters = await response.json()
|
clusters = await response.json()
|
||||||
if (Object.keys(clusters).length > 0) {
|
clusters = Object.fromEntries(
|
||||||
config = Object.entries(clusters).map(([key, value]) => ({...value, clusterName: key}))[0]
|
Object.entries(clusters).map(([name, cluster]) =>
|
||||||
}
|
[name, ({ ...cluster, clusterName: name, originalName: name })]))
|
||||||
})
|
|
||||||
|
|
||||||
let config = {
|
const values = Object.values(clusters)
|
||||||
clusterName: '',
|
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: '',
|
clientId: '',
|
||||||
brokers: [''],
|
brokers: [''],
|
||||||
ssl: true,
|
ssl: true,
|
||||||
|
@ -34,7 +60,8 @@
|
||||||
username: '',
|
username: '',
|
||||||
password: ''
|
password: ''
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
|
let config = emptyConfig()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -58,7 +85,9 @@
|
||||||
{#each config.brokers as broker}
|
{#each config.brokers as broker}
|
||||||
<input bind:value={broker}>
|
<input bind:value={broker}>
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if findLast(config.brokers) !== ''}
|
||||||
<button on:click={() => {config.brokers = [...config.brokers, '']}}>+</button>
|
<button on:click={() => {config.brokers = [...config.brokers, '']}}>+</button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-option">
|
<div class="settings-option">
|
||||||
|
@ -69,22 +98,63 @@
|
||||||
|
|
||||||
<div class="settings-option">
|
<div class="settings-option">
|
||||||
<div>SASL:</div>
|
<div>SASL:</div>
|
||||||
<div>
|
<div class="settings-sub-option">
|
||||||
Mechanism: <input bind:value={config.sasl.mechanism}>
|
<div>Mechanism:</div>
|
||||||
|
<input bind:value={config.sasl.mechanism}>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="settings-sub-option">
|
||||||
Username: <input bind:value={config.sasl.username}>
|
<div>Username:</div>
|
||||||
|
<input bind:value={config.sasl.username}>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="settings-sub-option">
|
||||||
Password: <input type="password" bind:value={config.sasl.password}>
|
<div>Password:</div>
|
||||||
|
<input type="password" bind:value={config.sasl.password}>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button style="float: right;" on:click={() => updateCluster(config)}>Save</button>
|
<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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<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 {
|
section {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -114,6 +184,18 @@
|
||||||
|
|
||||||
.query-settings {
|
.query-settings {
|
||||||
margin-right: 1rem;
|
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 {
|
.no-query-data h2 {
|
||||||
|
|
Loading…
Reference in New Issue