More re-org.
Try to move message searching implementation into separate .js file. Have apiFetch() put errors into state, as appropriate. Add errorDetails display.
This commit is contained in:
parent
8719371bd2
commit
bc1ad5e45a
|
@ -18,10 +18,11 @@
|
|||
Allow JavaScript to mutate objects
|
||||
<input type=checkbox bind:checked={mutableObjects}>
|
||||
</div>
|
||||
<textarea class={"query-input" + (big ? ' big' : '')} bind:value={queryCode}></textarea>
|
||||
<textarea class={'query-input' + (big ? ' big' : '')} bind:value={queryCode}></textarea>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!--suppress CssUnusedSymbol -->
|
||||
<style>
|
||||
.query-input-header {
|
||||
display: flex;
|
||||
|
|
|
@ -41,10 +41,12 @@ export const state = writable({
|
|||
itemCount: undefined,
|
||||
matchCount: undefined,
|
||||
error: 'Connecting to WebSocket...',
|
||||
errorDetails: undefined,
|
||||
})
|
||||
|
||||
const updateClearError = updater => state.update(s => {
|
||||
s.error = null
|
||||
s.errorDetails = null
|
||||
return updater(s)
|
||||
})
|
||||
|
||||
|
@ -129,6 +131,9 @@ export const query = async ({ cluster, topic, mode, jsFilter, queryCode, maxItem
|
|||
}
|
||||
|
||||
export const connect = () => {
|
||||
if (ws?.readyState === WebSocket.OPEN) {
|
||||
return;
|
||||
}
|
||||
if (testMode) {
|
||||
updateClearError(s => ({ ...s, error: 'TEST MODE ENABLED' }))
|
||||
return
|
||||
|
|
|
@ -1 +1,65 @@
|
|||
export const prerender = true;
|
||||
import { apiFetch } from "../utils.js";
|
||||
import { killQuery, query } from "../lib/state.js";
|
||||
|
||||
export const getTopics = async cluster =>
|
||||
(await apiFetch(`/topics/${cluster}`) || []).sort()
|
||||
|
||||
export const mapFromLocalStorage = ({ checked, querySettings, jsonDisplay, clusterNames, topics }) => {
|
||||
if (localStorage.querySettings) {
|
||||
let storedSettings
|
||||
try {
|
||||
storedSettings = JSON.parse(localStorage.querySettings)
|
||||
} catch (e) {
|
||||
storedSettings = {}
|
||||
}
|
||||
|
||||
querySettings.maxItems = storedSettings?.maxItems ?? querySettings.maxItems
|
||||
querySettings.jsFilter = storedSettings?.jsFilter ?? querySettings.jsFilter
|
||||
querySettings.queryCode = storedSettings?.queryCode ?? querySettings.queryCode
|
||||
querySettings.mode = storedSettings?.mode ?? querySettings.mode
|
||||
|
||||
if (!checked || clusterNames.includes(storedSettings?.cluster)) {
|
||||
querySettings.cluster = storedSettings.cluster
|
||||
} else {
|
||||
querySettings.cluster = clusterNames[0]
|
||||
}
|
||||
|
||||
if (!checked || topics.includes(storedSettings?.topic)) {
|
||||
querySettings.topic = storedSettings.topic
|
||||
} else {
|
||||
querySettings.topic = topics[0]
|
||||
}
|
||||
}
|
||||
|
||||
if (localStorage.jsonDisplay) {
|
||||
try {
|
||||
const savedJsonDisplay = JSON.parse(localStorage.jsonDisplay)
|
||||
Object.entries(savedJsonDisplay).forEach(([key, value]) => {
|
||||
if (savedJsonDisplay[key] !== undefined) {
|
||||
jsonDisplay[key] = value
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getClusterNames = async () => Object.keys((await apiFetch('/clusters')) || {})
|
||||
|
||||
export const startQuery = querySettings => {
|
||||
localStorage.setItem('querySettings', JSON.stringify(querySettings))
|
||||
query(querySettings)
|
||||
return true
|
||||
}
|
||||
|
||||
export const stopQuery = querySettings => {
|
||||
killQuery(querySettings)
|
||||
return false
|
||||
}
|
||||
export const defaultQueryCode =
|
||||
`// \`message\` contains all metadata
|
||||
// \`value\` is shorthand for message.value
|
||||
// it contains the actual produced data
|
||||
// \`json\` is a JSON representation of \`message\`
|
||||
|
||||
return message.value.eventType === "Television";`
|
||||
|
|
|
@ -2,92 +2,86 @@
|
|||
import { onMount } from 'svelte'
|
||||
import { JsonView } from '@zerodevx/svelte-json-view'
|
||||
import QueryInput from '$lib/QueryInput.svelte'
|
||||
import { state, connect, query, killQuery } from '$lib/state'
|
||||
import { queryMode } from "../lib/constants.js"
|
||||
import { apiFetch } from "../utils.js"
|
||||
import Modal from "../lib/Modal.svelte";
|
||||
import { state, connect } from '$lib/state'
|
||||
import { queryMode } from '../lib/constants.js'
|
||||
import Modal from '../lib/Modal.svelte';
|
||||
import {
|
||||
defaultQueryCode,
|
||||
getClusterNames,
|
||||
getTopics,
|
||||
mapFromLocalStorage,
|
||||
startQuery,
|
||||
stopQuery
|
||||
} from './+page.js';
|
||||
|
||||
let showQueryModal = false
|
||||
let queryRunning = false
|
||||
|
||||
let expandAll = true
|
||||
let showMetadata = true
|
||||
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)
|
||||
}
|
||||
|
||||
const querySettings = {
|
||||
let querySettings = {
|
||||
topic: null,
|
||||
cluster: null,
|
||||
mode: queryMode.REAL_TIME,
|
||||
jsFilter: false,
|
||||
maxItems: 20,
|
||||
mutableObjects: false,
|
||||
queryCode:
|
||||
`// \`message\` contains all metadata
|
||||
// \`value\` is shorthand for message.value
|
||||
// it contains the actual produced data
|
||||
// \`json\` is a JSON representation of \`message\`
|
||||
|
||||
return message.value.eventType === "Television";`
|
||||
queryCode: defaultQueryCode
|
||||
}
|
||||
|
||||
let topics = []
|
||||
let clusters = []
|
||||
const updateTopics = async () => {
|
||||
topics = await apiFetch(`/topics/${querySettings.cluster}`)
|
||||
topics ??= []
|
||||
topics.sort()
|
||||
console.log('topics', topics)
|
||||
let clusterNames = []
|
||||
const updateTopics = async (updateTopic = true) => {
|
||||
topics = await getTopics(querySettings.cluster)
|
||||
if (updateTopic) {
|
||||
querySettings.topic = topics[0]
|
||||
}
|
||||
const fontSizes = [30, 50, 67, 80, 90, 100, 110, 120, 133, 150]
|
||||
let dataViewFontSize = 5
|
||||
const setFontSize = size => {
|
||||
dataViewFontSize = size
|
||||
localStorage.dataViewFontSize = size
|
||||
}
|
||||
const fontUp = () => setFontSize(Math.min(dataViewFontSize + 1, fontSizes.length - 1))
|
||||
const fontDown = () => setFontSize(Math.max(dataViewFontSize - 1, 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()
|
||||
clusters = Object.keys((await apiFetch('/clusters')) || {})
|
||||
querySettings.cluster = clusters[0]
|
||||
clusterNames = await getClusterNames()
|
||||
await updateTopics()
|
||||
|
||||
if (localStorage) {
|
||||
dataViewFontSize = localStorage.dataViewFontSize || dataViewFontSize
|
||||
if (localStorage.querySettings) {
|
||||
let storedSettings = {}
|
||||
try {
|
||||
storedSettings = JSON.parse(localStorage.querySettings)
|
||||
} catch (e) {}
|
||||
querySettings.maxItems = storedSettings?.maxItems || querySettings.maxItems
|
||||
querySettings.jsFilter = storedSettings?.jsFilter || querySettings.jsFilter
|
||||
querySettings.queryCode = storedSettings?.queryCode || querySettings.queryCode
|
||||
querySettings.mode = storedSettings?.mode || querySettings.mode
|
||||
if (clusters.includes(storedSettings?.cluster)) {
|
||||
querySettings.cluster = storedSettings.cluster
|
||||
}
|
||||
if (topics.includes(storedSettings?.topic)) {
|
||||
querySettings.topic = storedSettings.topic
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reload with confirmed truthful values
|
||||
// Will only flicker if topic or cluster names have changed
|
||||
loadSave({ checked: true })
|
||||
})
|
||||
|
||||
let queryRunning = false
|
||||
const startQuery = () => {
|
||||
queryRunning = true
|
||||
localStorage.setItem('querySettings', JSON.stringify(querySettings))
|
||||
query(querySettings)
|
||||
}
|
||||
const stopQuery = () => {
|
||||
queryRunning = false
|
||||
killQuery(querySettings)
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{querySettings.topic ? `${querySettings.topic} - ` : ''}Kafka Dance</title>
|
||||
<meta name="description" content="Kafka Dance" />
|
||||
<meta name="description" content="Kafka Dance message search" />
|
||||
</svelte:head>
|
||||
|
||||
<section>
|
||||
|
@ -96,16 +90,19 @@ return message.value.eventType === "Television";`
|
|||
<Header />
|
||||
<NavBar />
|
||||
-->
|
||||
<h1>Message Search</h1>
|
||||
<h1>Topic Search</h1>
|
||||
{#if $state.error}
|
||||
<div class="state-error">{$state.error}</div>
|
||||
{#if $state.errorDetails}
|
||||
<div class="state-error-details">{$state.errorDetails}</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class="settings-option">
|
||||
<h3>Cluster</h3>
|
||||
<select disabled={clusters.length === 0} bind:value={querySettings.cluster} name="cluster" id="cluster" on:change={updateTopics}>
|
||||
{#each clusters as cluster}
|
||||
<option value={cluster}>{cluster}</option>
|
||||
<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>
|
||||
|
@ -167,27 +164,22 @@ return message.value.eventType === "Television";`
|
|||
|
||||
<div class="query-button">
|
||||
{#if queryRunning}
|
||||
<button class="danger" on:click={() => stopQuery()}>Stop Query</button>
|
||||
<button class="danger" on:click={() => queryRunning = stopQuery(querySettings)}>Stop Query</button>
|
||||
{:else}
|
||||
<button class="colored" on:click={() => startQuery()}>Start Query</button>
|
||||
<button class="colored" on:click={() => queryRunning = startQuery(querySettings)}>Start Query</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div class="live-settings">
|
||||
<!--{#if $state.itemCount >= 0}
|
||||
<h2>{$state.itemCount} Matches Found...</h2>
|
||||
{/if}-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="data-view-container">
|
||||
<div class="data-view-bar">
|
||||
<div class="border-between">
|
||||
<button class={expandAll ? 'selected' : ''} on:click={() => expandAll = !expandAll}>
|
||||
<button class={jsonDisplay.expandAll ? 'selected' : ''}
|
||||
on:click={() => jsonDisplay.expandAll = !jsonDisplay.expandAll}>
|
||||
Expand All Objects
|
||||
</button>
|
||||
<button class={showMetadata ? 'selected' : ''} on:click={() => showMetadata = !showMetadata}>
|
||||
<button class={jsonDisplay.showMetadata ? 'selected' : ''}
|
||||
on:click={() => jsonDisplay.showMetadata = !jsonDisplay.showMetadata}>
|
||||
Show Metadata
|
||||
</button>
|
||||
</div>
|
||||
|
@ -198,15 +190,15 @@ return message.value.eventType === "Television";`
|
|||
</div>
|
||||
{/if}
|
||||
<div class="border-between zoom-buttons">
|
||||
<button on:click={fontDown} style="border-color: #aaa;">-</button>
|
||||
<button on:click={fontUp}>+</button>
|
||||
<button on:click={jsonDisplay.fontDown} style="border-color: #aaa;">-</button>
|
||||
<button on:click={jsonDisplay.fontUp}>+</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if $state.items?.length > 0}
|
||||
<div class="data-view" style={`font-size: ${fontSizes[dataViewFontSize]}%`}>
|
||||
<JsonView json={showMetadata ? $state.items : $state.items.map(item => item.value)}
|
||||
depth={expandAll ? Infinity : 1}/>
|
||||
<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">
|
||||
|
@ -322,6 +314,11 @@ return message.value.eventType === "Television";`
|
|||
color: #ff2222;
|
||||
font-weight: bold;
|
||||
}
|
||||
.state-error-details {
|
||||
color: #ff2222;
|
||||
font-weight: normal;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/* JsonView config
|
||||
:root {
|
||||
|
|
|
@ -41,8 +41,7 @@
|
|||
clusterName: '', // and will be POSTed as a new cluster, not PUTted, updating an existing one
|
||||
clientId: '',
|
||||
brokers: [''],
|
||||
useSasl: true,
|
||||
ssl: true,
|
||||
ssl: false,
|
||||
sasl: {
|
||||
mechanism: '',
|
||||
username: '',
|
||||
|
@ -69,7 +68,7 @@
|
|||
</svelte:head>
|
||||
|
||||
<section>
|
||||
<div class="query-settings">
|
||||
<form class="query-settings" method="post">
|
||||
<h1>Cluster Configuration</h1>
|
||||
|
||||
<div class="settings-option">
|
||||
|
@ -109,7 +108,7 @@
|
|||
|
||||
<!-- TODO: Add options for SSL config -->
|
||||
<div class="settings-option">
|
||||
<div>SASL: <input type=checkbox bind:checked={useSasl} /></div>
|
||||
<div>Use SASL: <input type=checkbox bind:checked={useSasl} /></div>
|
||||
{#if useSasl}
|
||||
<div class="settings-sub-option">
|
||||
<div>Auth Mechanism:</div>
|
||||
|
@ -132,7 +131,7 @@
|
|||
|
||||
<button style="float: right;"
|
||||
on:click={() => (config.originalName ? updateCluster() : addCluster())}>{config.originalName ? 'Update Cluster Config' : 'Add Cluster Config'}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="data-view">
|
||||
<button style="margin: 1em;" on:click={startNewCluster}>Add New Cluster</button>
|
||||
|
|
|
@ -65,7 +65,11 @@ export const apiFetch = async (path, options = undefined) => {
|
|||
return undefined
|
||||
}
|
||||
try {
|
||||
return response.json()
|
||||
const json = await response.json()
|
||||
if (json.error) {
|
||||
state.update(s => ({ ...s, error: json.error, errorDetails: json.errorDetails }))
|
||||
}
|
||||
return json
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
state.update(s => ({ ...s, error: 'Received non-JSON response from backend' }))
|
||||
|
|
Loading…
Reference in New Issue