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

361 lines
9.0 KiB
Svelte

<script>
import { onMount } from 'svelte'
import { slide, fade } from 'svelte/transition'
import { JsonView } from '@zerodevx/svelte-json-view'
import QueryInput from '$lib/QueryInput.svelte'
import { state, connect } from '$lib/state'
import { queryMode, queryState } from '../lib/constants.js'
import Modal from '../lib/Modal.svelte';
import {
defaultQueryCode,
getClusterNames,
getTopics,
mapFromLocalStorage,
startQuery,
stopQuery
} from './+page.js';
export let errors, data
let showQuerySettings = true
let showQueryModal = false
let queryRunning = ''
const fontSizes = [30, 50, 67, 80, 90, 100, 110, 120, 133, 150]
let jsonDisplay = {
expandAll: true,
showMetadata: true,
fontSize: fontSizes.indexOf(100),
fontUp: () => setFontSize(Math.min(jsonDisplay.fontSize + 1, fontSizes.length - 1)),
fontDown: () => setFontSize(Math.max(jsonDisplay.fontSize - 1, 0))
}
const setFontSize = size => {
jsonDisplay.fontSize = size
localStorage.jsonDisplay = JSON.stringify(jsonDisplay)
}
let querySettings = {
topic: null,
cluster: null,
mode: queryMode.REAL_TIME,
jsFilter: false,
maxItems: 20,
mutableObjects: false,
queryCode: defaultQueryCode
}
let topics = []
let clusterNames = []
const updateTopics = async () => {
topics = await getTopics(querySettings.cluster)
if (!querySettings.topic || !topics.includes(querySettings.topic)) {
querySettings.topic = topics[0]
}
}
onMount(async () => {
const loadSave = ({ checked }) => {
if (localStorage) {
mapFromLocalStorage({
checked,
jsonDisplay,
querySettings,
clusterNames,
topics,
})
jsonDisplay = jsonDisplay
querySettings = querySettings
}
}
// Instantly load saved data as if cluster and topic names are accurate
// This should usually be the case
loadSave({ checked: false })
connect()
clusterNames = await getClusterNames()
if (!clusterNames.includes(querySettings.cluster)) {
querySettings.cluster = clusterNames[0]
}
await updateTopics()
// Reload with confirmed truthful values
// Will only flicker if topic or cluster names have changed
loadSave({ checked: true })
})
</script>
<svelte:head>
<title>{querySettings.topic ? `${querySettings.topic} - ` : ''}Kafka Dance</title>
<meta name="description" content="Kafka Dance message search" />
</svelte:head>
<section>
{#if showQuerySettings}
<div class="query-settings">
<!--
<Header />
<NavBar />
-->
<h1>Topic Search</h1>
<div class="state-error">
{#if $state.error}
<span transition:fade={{duration: 100}}>{$state.error}</span>
{#if $state.errorDetails}
<div class="state-error-details">{$state.errorDetails}</div>
{/if}
{:else}
&nbsp;
{/if}
</div>
<div class="settings-option">
<h3>Cluster</h3>
<select disabled={clusterNames.length === 0} bind:value={querySettings.cluster} name="cluster" id="cluster" on:change={updateTopics}>
{#each clusterNames as clusterName}
<option value={clusterName}>{clusterName}</option>
{/each}
</select>
</div>
<div class="settings-option">
<h3>Topic</h3>
<select disabled={topics.length === 0} bind:value={querySettings.topic} name="topic" id="topic">
{#each topics as topic}
<option value={topic}>{topic}</option>
{/each}
</select>
</div>
<div class="settings-option">
<label>
Maximum Results
<input type=number bind:value={querySettings.maxItems} min=0 max=10000>
</label>
</div>
<br/>
<div class="settings-option">
<QueryInput
bind:jsFilter={querySettings.jsFilter}
bind:mutableObjects={querySettings.mutableObjects}
bind:queryCode={querySettings.queryCode} />
{#if querySettings.jsFilter}
<button class="query-input-button" on:click={() => showQueryModal = true}>Open Editor Window</button>
{/if}
{#if showQueryModal}
<Modal onClickOut={() => showQueryModal = false}
onCancel={null}
onConfirm={() => showQueryModal = false}
confirm="Close"
title={'JS Filter'}>
<QueryInput big
bind:jsFilter={querySettings.jsFilter}
bind:mutableObjects={querySettings.mutableObjects}
bind:queryCode={querySettings.queryCode} />
</Modal>
{/if}
</div>
<div class="query-button">
{#if queryRunning && $state.queryState !== queryState.DONE}
<div transition:slide|local class="stop-button">
<button class="danger" on:click={() => queryRunning = stopQuery(querySettings)}>
{queryRunning === queryMode.REAL_TIME ? 'End Real Time' : 'Cancel One-Shot'} Query
</button>
</div>
{:else}
<div transition:slide|local class="start-buttons colored">
<button class="colored" on:click={() => queryRunning = startQuery({...querySettings, mode: queryMode.ONE_SHOT})}>
Start One-Shot
</button>
<div class="separator"></div>
<button class="colored" on:click={() => queryRunning = startQuery({...querySettings, mode: queryMode.REAL_TIME})}>
Start Real-Time
</button>
</div>
{/if}
</div>
</div>
{/if}
<div class="data-view-container">
<div class="data-view-bar">
<div class="border-between">
<button class={showQuerySettings ? "query-settings-hider" : "query-settings-shower"}
on:click={() => showQuerySettings = !showQuerySettings}></button>
<button class={jsonDisplay.expandAll ? 'selected' : ''}
on:click={() => jsonDisplay.expandAll = !jsonDisplay.expandAll}>
Expand All Objects
</button>
<button class={jsonDisplay.showMetadata ? 'selected' : ''}
on:click={() => jsonDisplay.showMetadata = !jsonDisplay.showMetadata}>
Show Metadata
</button>
</div>
<div style="display: flex; align-items: center">
{#if $state.itemCount >= 0}
<div class="data-view-results">
{$state.itemCount} Messages
<!-- TODO: Error indicator? -->
{#if $state.queryState === queryState.DONE}
<span style="color: green; font-weight: bold;"></span>
{:else if $state.queryState === queryState.LOADING}
<span class="lds-dual-ring"></span>
{/if}
</div>
{/if}
<div class="border-between zoom-buttons">
<button on:click={jsonDisplay.fontDown} style="border-color: #aaa;">-</button>
<button on:click={jsonDisplay.fontUp}>+</button>
</div>
</div>
</div>
{#if $state.queryState !== queryState.IDLE}
<div class="data-view" style={`font-size: ${fontSizes[jsonDisplay.fontSize]}%`}>
<JsonView json={jsonDisplay.showMetadata ? $state.items : $state.items.map(item => item.value)}
depth={jsonDisplay.expandAll ? Infinity : 1}/>
</div>
{:else }
<div class="data-view no-query-data" style={`font-size: ${fontSizes[jsonDisplay.fontSize]}%`}>
<JsonView json={{value: {"Hey": "There's no data here yet!", "When you start a query": "your results will appear here!"}}}
depth={jsonDisplay.expandAll ? Infinity : -1}/>
</div>
{/if}
</div>
</section>
<style>
.data-view {
border-top: none;
}
.data-view-container {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.data-view-bar {
background-color: #d7dbf3;
border-style: solid;
border-width: 1px;
border-color: #95c1d3;
border-bottom: none;
padding: 0;
margin: 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.border-between {
border-width: 1px;
border-style: none;
display: flex;
flex-grow: 1;
}
.border-between button:last-child {
border-right: none;
}
.zoom-buttons button {
width: 2.5em;
}
.data-view-bar button {
border-color: white;
border-style: none;
border-right: solid;
border-width: 1px;
border-radius: 0;
margin: 0;
}
button.selected {
background-color: #4732a5;
color: white;
}
button.selected:hover {
background-color: #53429d;
color: white;
}
.data-view-results {
color: #333;
font-size: 80%;
margin-right: 1em;
}
h1 {
width: 100%;
}
.query-input-button {
border-style: none;
border-radius: 0;
background-color: #313131;
color: #d0dde9;
}
.query-input-button:hover {
background-color: #575757;
}
.query-type {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.query-type label {
padding-left: 0.5em;
}
.query-button {
text-align: right;
border-radius: 0;
border-style: none;
/* New button CSS */
display: flex;
flex-direction: column;
}
.start-buttons, .stop-button {
display: flex;
flex-grow: 1;
}
.separator {
width: 1em;
}
.query-button button {
flex-grow: 1;
}
.query-button button:hover {
flex-grow: 2;
}
.state-error {
color: #ff2222;
font-weight: bold;
}
.state-error-details {
color: #ff2222;
font-weight: normal;
font-size: 80%;
}
/* JsonView config
:root {
--nodePaddingLeft: 1rem;
--nodeBorderLeft: 1px dotted #9ca3af;
--nodeColor: #6d81a4;
--bracketHoverBackground: #d1d5db;
--leafDefaultColor: #9ca3af;
--leafStringColor: #14cb8f;
--leafNumberColor: #d97706;
--leafBooleanColor: #2563eb;
}
.bracket {
color: #d0dde9;
}
*/
.no-query-data {
opacity: 65%;
}
</style>