Compare commits
No commits in common. "2859f5768119750e8e7730e2f4c82df395d3af19" and "91cca57992a9ebfee692d5b1e3d6b0a9f14170ee" have entirely different histories.
2859f57681
...
91cca57992
86
src/app.css
86
src/app.css
|
@ -11,7 +11,7 @@
|
||||||
--tertiary-color: #edf0f8;
|
--tertiary-color: #edf0f8;
|
||||||
--accent-color: #de09fa;
|
--accent-color: #de09fa;
|
||||||
/*--accent-color: #ff0df8;*/
|
/*--accent-color: #ff0df8;*/
|
||||||
--heading-color: rgba(0, 0, 0, 0.9);
|
--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;
|
||||||
|
@ -38,11 +38,6 @@ select, input {
|
||||||
padding: 0.3em;
|
padding: 0.3em;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
max-width: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
height: calc(100% - 4em);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select option {
|
select option {
|
||||||
|
@ -75,25 +70,6 @@ button.colored {
|
||||||
border-color: var(--accent-color);
|
border-color: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-dual-ring {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.lds-dual-ring:after {
|
|
||||||
content: " ";
|
|
||||||
display: inline-block;
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
margin: 0 0 0 8px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2px solid var(--accent-color);
|
|
||||||
border-color: var(--accent-color) transparent var(--accent-color) transparent;
|
|
||||||
animation: spin 1.2s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
button.colored:hover {
|
button.colored:hover {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@ -235,12 +211,13 @@ button {
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-color: #95c1d3;
|
border-color: #95c1d3;
|
||||||
flex-grow: 1;
|
flex-grow: 2;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
background-color: #e2eaff;
|
background-color: #e2eaff;
|
||||||
|
height: 84vh;
|
||||||
|
max-width: 65vw;
|
||||||
min-width: 65vw;
|
min-width: 65vw;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-option {
|
.settings-option {
|
||||||
|
@ -258,63 +235,8 @@ button:focus:not(:focus-visible) {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-settings-hider {
|
|
||||||
color: rgba(0,0,0, 0.5)
|
|
||||||
}
|
|
||||||
.query-settings-shower {
|
|
||||||
color: rgba(0,0,0, 0.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
.query-settings-hider:before {
|
|
||||||
content: "«";
|
|
||||||
}
|
|
||||||
.query-settings-shower:before {
|
|
||||||
content: "»";
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex: 1;
|
|
||||||
justify-content: space-between;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.query-settings {
|
|
||||||
margin-right: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
@media (min-width: 720px) {
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2.4rem;
|
font-size: 2.4rem;
|
||||||
}
|
}
|
||||||
.query-settings {
|
|
||||||
overflow: scroll;
|
|
||||||
}
|
|
||||||
.mobile {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 719px) {
|
|
||||||
.desktop {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: scroll;
|
|
||||||
}
|
|
||||||
.query-settings {
|
|
||||||
flex-grow: 1;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
.query-settings-hider:before {
|
|
||||||
content: '▲';
|
|
||||||
}
|
|
||||||
.query-settings-shower:before {
|
|
||||||
content: '▼';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,20 +7,19 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="query-input-header">
|
<div class="query-input-header">
|
||||||
<label>
|
<div on:click={() => jsFilter = !jsFilter}>
|
||||||
Use JavaScript to filter messages
|
Use JavaScript to filter messages
|
||||||
<input type=checkbox bind:checked={jsFilter}>
|
<input type=checkbox bind:checked={jsFilter}>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class={"query-input-display" + (big ? ' big' : '')}>
|
<div class={"query-input-display" + (big ? ' big' : '')}>
|
||||||
{#if jsFilter}
|
{#if jsFilter}
|
||||||
<div class="query-input-hideable" transition:slide|local>
|
<div class="query-input-hideable" transition:slide|local>
|
||||||
<label 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.">
|
||||||
on:click={() => mutableObjects = !mutableObjects} >
|
Allow JavaScript to mutate objects
|
||||||
Allow mutation of message data
|
|
||||||
<input type=checkbox bind:checked={mutableObjects}>
|
<input type=checkbox bind:checked={mutableObjects}>
|
||||||
</label>
|
</div>
|
||||||
<textarea class='query-input' bind:value={queryCode}></textarea>
|
<textarea class='query-input' bind:value={queryCode}></textarea>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -43,7 +42,7 @@
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
/* For some reason, using flex-grow: 1; broke the slide-in transition */
|
/* For some reason, using flex-grow: 1; broke the slide in transition */
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,6 @@ export const queryMode = {
|
||||||
KILL: 'kill'
|
KILL: 'kill'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const queryState = {
|
|
||||||
LOADING: 'loading',
|
|
||||||
DONE: 'done',
|
|
||||||
IDLE: 'idle',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const backendAddressAndPort = 'localhost:3000'
|
export const backendAddressAndPort = 'localhost:3000'
|
||||||
|
|
||||||
export const backendUrl = `http://${backendAddressAndPort}`
|
export const backendUrl = `http://${backendAddressAndPort}`
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
<NavBar />
|
<NavBar />
|
||||||
|
|
||||||
<div class="desktop corner">
|
<div class="corner">
|
||||||
<!--
|
<!--
|
||||||
{#if testMode}
|
{#if testMode}
|
||||||
<div class="error-display">TEST MODE ENABLED</div>
|
<div class="error-display">TEST MODE ENABLED</div>
|
||||||
|
@ -59,6 +59,66 @@
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
--background: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 2em;
|
||||||
|
height: 3em;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
path {
|
||||||
|
fill: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
height: 3em;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
list-style: none;
|
||||||
|
background: var(--background);
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.active::before {
|
||||||
|
--size: 6px;
|
||||||
|
content: '';
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: calc(50% - var(--size));
|
||||||
|
border: var(--size) solid transparent;
|
||||||
|
border-top: var(--size) solid var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 1em;
|
||||||
|
color: var(--heading-color);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s linear;
|
||||||
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,15 @@
|
||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { fade } from 'svelte/transition'
|
|
||||||
|
|
||||||
let dropdownVisible = false
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="desktop">
|
<nav>
|
||||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||||
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
|
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
|
||||||
</svg>
|
</svg>
|
||||||
<ul class="desktop">
|
<ul>
|
||||||
<li class:active={$page.url.pathname === '/'}>
|
<li class:active={$page.url.pathname === '/'}>
|
||||||
<a sveltekit:prefetch href="/">Topic Search</a>
|
<a sveltekit:prefetch href="/">Message Search</a>
|
||||||
</li>
|
</li>
|
||||||
<!--
|
|
||||||
<li class:active={$page.url.pathname === '/post-message'}>
|
|
||||||
<a sveltekit:prefetch href="/post-message">Post Message</a>
|
|
||||||
</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>
|
||||||
</li>
|
</li>
|
||||||
|
@ -27,38 +19,44 @@
|
||||||
</svg>
|
</svg>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<nav class="mobile">
|
|
||||||
<button style="font-size: 3rem;" on:click={() => dropdownVisible = !dropdownVisible}>{dropdownVisible ? 'x' : '≡'}</button>
|
|
||||||
{#if dropdownVisible}
|
|
||||||
<!-- TODO: Click-away layer -->
|
|
||||||
<ul class="mobile" transition:fade={{duration: 300}}>
|
|
||||||
<li class:active={$page.url.pathname === '/'}>
|
|
||||||
<a sveltekit:prefetch href="/">Topic Search</a>
|
|
||||||
</li>
|
|
||||||
<!--
|
|
||||||
<li class:active={$page.url.pathname === '/post-message'}>
|
|
||||||
<a sveltekit:prefetch href="/post-message">Post Message</a>
|
|
||||||
</li>
|
|
||||||
-->
|
|
||||||
<li class:active={$page.url.pathname === '/settings'}>
|
|
||||||
<a sveltekit:prefetch href="/settings">Settings</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
{/if}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
color: black;
|
||||||
|
height: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner span {
|
||||||
|
margin-top: 0.45em;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner img {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
height: 2em;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
--background: rgba(255, 255, 255, 0.7);
|
--background: rgba(255, 255, 255, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.mobile {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
|
@ -69,7 +67,7 @@
|
||||||
fill: var(--background);
|
fill: var(--background);
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.desktop {
|
ul {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -82,20 +80,6 @@
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.mobile {
|
|
||||||
flex-direction: column;
|
|
||||||
position: absolute;
|
|
||||||
padding: 256px 0 1em;
|
|
||||||
margin: 0;
|
|
||||||
width: 50vw;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
list-style: none;
|
|
||||||
background-color: rgba(255, 255, 255);
|
|
||||||
background-size: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
li {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -127,31 +111,8 @@
|
||||||
transition: color 0.2s linear;
|
transition: color 0.2s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 719px) {
|
|
||||||
nav a {
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 200%;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
|
||||||
z-index: 1;
|
|
||||||
padding: 0;
|
|
||||||
height: 64px;
|
|
||||||
width: 64px;
|
|
||||||
font-size: 64px;
|
|
||||||
border-radius: 0;
|
|
||||||
border-style: none;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
|
@ -1,7 +1,7 @@
|
||||||
// noinspection JSCheckFunctionSignatures
|
// noinspection JSCheckFunctionSignatures
|
||||||
|
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
import { backendAddressAndPort, queryMode, queryState } from './constants.js'
|
import { backendAddressAndPort, queryMode } from './constants.js'
|
||||||
|
|
||||||
const getRandomFromArray = array => array[Math.floor(Math.random() * array.length)]
|
const getRandomFromArray = array => array[Math.floor(Math.random() * array.length)]
|
||||||
|
|
||||||
|
@ -16,46 +16,32 @@ const addMetadata = mockItem => ({
|
||||||
key: null,
|
key: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
const mockUsernames = [
|
const mockItems = [
|
||||||
'sagev', 'cameronl', 'westonm', 'connorl', 'cameronf', 'thomase'
|
{
|
||||||
]
|
"eventType": "Television",
|
||||||
|
"broughtToYouBy": "AMC",
|
||||||
|
"title": "Breaking Bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventType": "Television",
|
||||||
|
"broughtToYouBy": "CBS",
|
||||||
|
"title": "Breaking Bad Bang Theory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventType": "Movie",
|
||||||
|
"broughtToYouBy": "20th Century Fox",
|
||||||
|
"title": "Star Wars 2: The Star Warsening, brought to you by the Big Stink Corporation, a division of PepsiCo and somehow also Disney."
|
||||||
|
},
|
||||||
|
].map(addMetadata)
|
||||||
|
|
||||||
const mockEventTypes = [
|
export const testMode = true
|
||||||
'purchase', 'refund',
|
|
||||||
]
|
|
||||||
|
|
||||||
const mockItemTypes = [
|
|
||||||
'Taco', 'Enchilada', 'Soda', 'Chalupa', 'Tostada'
|
|
||||||
]
|
|
||||||
|
|
||||||
const buildMockItem = () => {
|
|
||||||
const purchasedBy = getRandomFromArray(mockUsernames)
|
|
||||||
let transactionHandledBy = getRandomFromArray(mockUsernames)
|
|
||||||
while (transactionHandledBy === purchasedBy) {
|
|
||||||
transactionHandledBy = getRandomFromArray(mockUsernames)
|
|
||||||
}
|
|
||||||
return addMetadata({
|
|
||||||
eventType: getRandomFromArray(mockEventTypes),
|
|
||||||
itemType: getRandomFromArray(mockItemTypes),
|
|
||||||
purchasedBy,
|
|
||||||
transactionHandledBy,
|
|
||||||
purchaseId: crypto.randomUUID()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockItems = Array(100).fill(buildMockItem, 0).map(mocker => mocker())
|
|
||||||
|
|
||||||
export const testMode = import.meta.env.VITE_KAFKA_FRONTEND_DEMO === 'true'
|
|
||||||
|
|
||||||
export const state = writable({
|
export const state = writable({
|
||||||
items: [],
|
items: [],
|
||||||
itemCount: undefined,
|
itemCount: undefined,
|
||||||
matchCount: undefined,
|
matchCount: undefined,
|
||||||
queryState: queryState.IDLE,
|
|
||||||
error: 'Connecting to WebSocket...',
|
error: 'Connecting to WebSocket...',
|
||||||
errorDetails: undefined,
|
errorDetails: undefined,
|
||||||
queryStartTime: undefined,
|
|
||||||
queryEndTime: undefined,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const updateClearError = updater => state.update(s => {
|
const updateClearError = updater => state.update(s => {
|
||||||
|
@ -75,12 +61,8 @@ const testQuery = (mode) => {
|
||||||
runTestQuery = true
|
runTestQuery = true
|
||||||
try {
|
try {
|
||||||
if (mode === queryMode.REAL_TIME) {
|
if (mode === queryMode.REAL_TIME) {
|
||||||
state.update(s => ({
|
|
||||||
...s,
|
|
||||||
queryStartTime: new Date()
|
|
||||||
}))
|
|
||||||
const addItem = () => {
|
const addItem = () => {
|
||||||
const item = buildMockItem()
|
const item = getRandomFromArray(mockItems)
|
||||||
if (filterFunc(item, item.value, JSON.stringify(item))) {
|
if (filterFunc(item, item.value, JSON.stringify(item))) {
|
||||||
item.timestamp = new Date().getTime()
|
item.timestamp = new Date().getTime()
|
||||||
state.update(s => ({
|
state.update(s => ({
|
||||||
|
@ -94,16 +76,12 @@ const testQuery = (mode) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(addItem, testTimeout)
|
setTimeout(addItem, testTimeout)
|
||||||
} else { // ONE_SHOT
|
} else {
|
||||||
setTimeout(() => {
|
state.update(s => ({
|
||||||
const filteredItems = mockItems.filter(item => filterFunc(item, item.value, JSON.stringify(item))).slice(0, itemLimit)
|
...s,
|
||||||
state.update(s => ({
|
items: mockItems.filter(item => filterFunc(item, item.value, JSON.stringify(item))),
|
||||||
...s,
|
itemCount: (s.itemCount || 0) + 1
|
||||||
items: filteredItems,
|
}))
|
||||||
itemCount: filteredItems.length,
|
|
||||||
queryState: queryState.DONE
|
|
||||||
}))
|
|
||||||
}, 2000)
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('Caught an error:', e.toString())
|
console.log('Caught an error:', e.toString())
|
||||||
|
@ -112,12 +90,9 @@ const testQuery = (mode) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const killQuery = async ({ }) => {
|
export const killQuery = async ({ }) => {
|
||||||
|
// TODO
|
||||||
if (testMode) {
|
if (testMode) {
|
||||||
runTestQuery = false
|
runTestQuery = false
|
||||||
updateClearError(s => ({
|
|
||||||
...s,
|
|
||||||
queryState: queryState.DONE
|
|
||||||
}))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
|
@ -134,13 +109,7 @@ export const query = async ({ cluster, topic, mode, jsFilter, queryCode, maxItem
|
||||||
}
|
}
|
||||||
itemLimit = maxItems
|
itemLimit = maxItems
|
||||||
|
|
||||||
updateClearError(s => ({
|
updateClearError(s => ({ ...s, items: [], itemCount: 0 }))
|
||||||
...s,
|
|
||||||
items: [],
|
|
||||||
itemCount: 0,
|
|
||||||
queryState: queryState.LOADING,
|
|
||||||
queryStartTime: new Date()
|
|
||||||
}))
|
|
||||||
if (testMode) {
|
if (testMode) {
|
||||||
testQuery(mode)
|
testQuery(mode)
|
||||||
return
|
return
|
||||||
|
@ -202,8 +171,7 @@ export const connect = () => {
|
||||||
case 'complete':
|
case 'complete':
|
||||||
updateClearError(s => ({
|
updateClearError(s => ({
|
||||||
...s,
|
...s,
|
||||||
items: data.message,
|
items: data.message
|
||||||
queryState: queryState.DONE
|
|
||||||
}))
|
}))
|
||||||
break;
|
break;
|
||||||
case 'item_count':
|
case 'item_count':
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { apiFetch, setStateError } from "../utils.js";
|
import { apiFetch } from "../utils.js";
|
||||||
import { killQuery, query } from "../lib/state.js";
|
import { killQuery, query } from "../lib/state.js";
|
||||||
|
|
||||||
export const getTopics = async cluster =>
|
export const getTopics = async cluster =>
|
||||||
|
@ -48,12 +48,12 @@ export const getClusterNames = async () => Object.keys((await apiFetch('/cluster
|
||||||
|
|
||||||
export const startQuery = querySettings => {
|
export const startQuery = querySettings => {
|
||||||
localStorage.setItem('querySettings', JSON.stringify(querySettings))
|
localStorage.setItem('querySettings', JSON.stringify(querySettings))
|
||||||
query(querySettings).catch(setStateError)
|
query(querySettings)
|
||||||
return querySettings.mode
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export const stopQuery = querySettings => {
|
export const stopQuery = querySettings => {
|
||||||
killQuery(querySettings).catch(setStateError)
|
killQuery(querySettings)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
export const defaultQueryCode =
|
export const defaultQueryCode =
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { slide, fade } from 'svelte/transition'
|
import { slide, fly, scale } from 'svelte/transition'
|
||||||
import { JsonView } from '@zerodevx/svelte-json-view'
|
import { JsonView } from '@zerodevx/svelte-json-view'
|
||||||
import QueryInput from '$lib/QueryInput.svelte'
|
import QueryInput from '$lib/QueryInput.svelte'
|
||||||
import { state, connect } from '$lib/state'
|
import { state, connect } from '$lib/state'
|
||||||
import { queryMode, queryState } from '../lib/constants.js'
|
import { queryMode } from '../lib/constants.js'
|
||||||
import Modal from '../lib/Modal.svelte';
|
import Modal from '../lib/Modal.svelte';
|
||||||
import {
|
import {
|
||||||
defaultQueryCode,
|
defaultQueryCode,
|
||||||
|
@ -16,9 +16,8 @@
|
||||||
} from './+page.js';
|
} from './+page.js';
|
||||||
export let errors, data
|
export let errors, data
|
||||||
|
|
||||||
let showQuerySettings = true
|
|
||||||
let showQueryModal = false
|
let showQueryModal = false
|
||||||
let queryRunning = ''
|
let queryRunning = false
|
||||||
|
|
||||||
const fontSizes = [30, 50, 67, 80, 90, 100, 110, 120, 133, 150]
|
const fontSizes = [30, 50, 67, 80, 90, 100, 110, 120, 133, 150]
|
||||||
let jsonDisplay = {
|
let jsonDisplay = {
|
||||||
|
@ -45,9 +44,9 @@
|
||||||
|
|
||||||
let topics = []
|
let topics = []
|
||||||
let clusterNames = []
|
let clusterNames = []
|
||||||
const updateTopics = async () => {
|
const updateTopics = async (updateTopic = true) => {
|
||||||
topics = await getTopics(querySettings.cluster)
|
topics = await getTopics(querySettings.cluster)
|
||||||
if (!querySettings.topic || !topics.includes(querySettings.topic)) {
|
if (updateTopic) {
|
||||||
querySettings.topic = topics[0]
|
querySettings.topic = topics[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,9 +73,6 @@
|
||||||
|
|
||||||
connect()
|
connect()
|
||||||
clusterNames = await getClusterNames()
|
clusterNames = await getClusterNames()
|
||||||
if (!clusterNames.includes(querySettings.cluster)) {
|
|
||||||
querySettings.cluster = clusterNames[0]
|
|
||||||
}
|
|
||||||
await updateTopics()
|
await updateTopics()
|
||||||
|
|
||||||
// Reload with confirmed truthful values
|
// Reload with confirmed truthful values
|
||||||
|
@ -91,100 +87,112 @@
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
{#if showQuerySettings}
|
<div class="query-settings">
|
||||||
<div class="query-settings">
|
<!--
|
||||||
<!--
|
<Header />
|
||||||
<Header />
|
<NavBar />
|
||||||
<NavBar />
|
-->
|
||||||
-->
|
<h1>Topic Search</h1>
|
||||||
<h1>Topic Search</h1>
|
{#if $state.error}
|
||||||
<div class="state-error">
|
<div class="state-error">{$state.error}</div>
|
||||||
{#if $state.error}
|
{#if $state.errorDetails}
|
||||||
<span transition:fade={{duration: 100}}>{$state.error}</span>
|
<div class="state-error-details">{$state.errorDetails}</div>
|
||||||
{#if $state.errorDetails}
|
{/if}
|
||||||
<div class="state-error-details">{$state.errorDetails}</div>
|
{/if}
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="settings-option">
|
<div class="settings-option">
|
||||||
<h3>Cluster</h3>
|
<h3>Cluster</h3>
|
||||||
<select disabled={clusterNames.length === 0} bind:value={querySettings.cluster} name="cluster" id="cluster" on:change={updateTopics}>
|
<select disabled={clusterNames.length === 0} bind:value={querySettings.cluster} name="cluster" id="cluster" on:change={updateTopics}>
|
||||||
{#each clusterNames as clusterName}
|
{#each clusterNames as clusterName}
|
||||||
<option value={clusterName}>{clusterName}</option>
|
<option value={clusterName}>{clusterName}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</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>
|
</div>
|
||||||
<hr>
|
|
||||||
{/if}
|
<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 Large Editor</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>
|
||||||
|
|
||||||
|
<!-- Previous 'Start' style
|
||||||
|
<div class="settings-option query-type">
|
||||||
|
<span>Query Type:</span>
|
||||||
|
<span>
|
||||||
|
<label title="Consume and display new events as they are produced.">
|
||||||
|
<input type=radio bind:group={querySettings.mode} name="mode" value={queryMode.REAL_TIME}>
|
||||||
|
Real Time
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label title="Query all messages that currently exist, then stop.">
|
||||||
|
<input type=radio bind:group={querySettings.mode} name="mode" value={queryMode.ONE_SHOT}>
|
||||||
|
One Shot
|
||||||
|
</label>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="query-button">
|
||||||
|
{#if queryRunning}
|
||||||
|
<button class="danger" on:click={() => queryRunning = stopQuery(querySettings)}>Stop Query</button>
|
||||||
|
{:else}
|
||||||
|
<button class="colored" on:click={() => queryRunning = startQuery(querySettings)}>Start Query</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div class="query-button">
|
||||||
|
{#if queryRunning}
|
||||||
|
<button transition:slide class="danger" on:click={() => queryRunning = stopQuery(querySettings)}>Stop Query</button>
|
||||||
|
{:else}
|
||||||
|
<button transition:scale class="colored"
|
||||||
|
on:click={() => queryRunning = startQuery({...querySettings, mode: queryMode.ONE_SHOT})}>
|
||||||
|
Start One-Shot
|
||||||
|
</button>
|
||||||
|
<div class="separator"></div>
|
||||||
|
<button transition:slide class="colored"
|
||||||
|
on:click={() => queryRunning = startQuery({...querySettings, mode: queryMode.REAL_TIME})}>
|
||||||
|
Start Real-Time
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="data-view-container">
|
<div class="data-view-container">
|
||||||
<div class="data-view-bar">
|
<div class="data-view-bar">
|
||||||
<div class="border-between">
|
<div class="border-between">
|
||||||
<button class={showQuerySettings ? "query-settings-hider" : "query-settings-shower"}
|
|
||||||
on:click={() => showQuerySettings = !showQuerySettings}></button>
|
|
||||||
<button class={jsonDisplay.expandAll ? 'selected' : ''}
|
<button class={jsonDisplay.expandAll ? 'selected' : ''}
|
||||||
on:click={() => jsonDisplay.expandAll = !jsonDisplay.expandAll}>
|
on:click={() => jsonDisplay.expandAll = !jsonDisplay.expandAll}>
|
||||||
Expand All Objects
|
Expand All Objects
|
||||||
|
@ -198,12 +206,6 @@
|
||||||
{#if $state.itemCount >= 0}
|
{#if $state.itemCount >= 0}
|
||||||
<div class="data-view-results">
|
<div class="data-view-results">
|
||||||
{$state.itemCount} Messages
|
{$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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="border-between zoom-buttons">
|
<div class="border-between zoom-buttons">
|
||||||
|
@ -212,39 +214,26 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if $state.queryState !== queryState.IDLE}
|
{#if $state.items?.length > 0}
|
||||||
<div class="data-view" style={`font-size: ${fontSizes[jsonDisplay.fontSize]}%`}>
|
<div class="data-view" style={`font-size: ${fontSizes[jsonDisplay.fontSize]}%`}>
|
||||||
<JsonView json={jsonDisplay.showMetadata ? $state.items : $state.items.map(item => item.value)}
|
<JsonView json={jsonDisplay.showMetadata ? $state.items : $state.items.map(item => item.value)}
|
||||||
depth={jsonDisplay.expandAll ? Infinity : 1}/>
|
depth={jsonDisplay.expandAll ? Infinity : 1}/>
|
||||||
</div>
|
</div>
|
||||||
{:else }
|
{:else }
|
||||||
<div class="data-view no-query-data" style={`font-size: ${fontSizes[jsonDisplay.fontSize]}%`}>
|
<div class="data-view no-query-data">
|
||||||
<JsonView json={{value: {"Hey": "There's no data here yet!", "When you start a query": "your results will appear here!"}}}
|
<h2>No query data</h2>
|
||||||
depth={jsonDisplay.expandAll ? Infinity : -1}/>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
hr {
|
|
||||||
width: 60%;
|
|
||||||
border-style: solid;
|
|
||||||
margin-bottom: 1.5em;
|
|
||||||
border-color: rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
hr {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.data-view {
|
.data-view {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
}
|
}
|
||||||
.data-view-container {
|
.data-view-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
|
||||||
}
|
}
|
||||||
.data-view-bar {
|
.data-view-bar {
|
||||||
background-color: #d7dbf3;
|
background-color: #d7dbf3;
|
||||||
|
@ -292,6 +281,14 @@
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: space-between;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -306,6 +303,16 @@
|
||||||
background-color: #575757;
|
background-color: #575757;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.query-settings {
|
||||||
|
margin-right: 1rem;
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 1em;
|
||||||
|
height: 84.5vh;
|
||||||
|
}
|
||||||
|
|
||||||
.query-type {
|
.query-type {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -322,11 +329,6 @@
|
||||||
border-style: none;
|
border-style: none;
|
||||||
/* New button CSS */
|
/* New button CSS */
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.start-buttons, .stop-button {
|
|
||||||
display: flex;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
.separator {
|
||||||
|
@ -367,6 +369,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.no-query-data {
|
.no-query-data {
|
||||||
opacity: 65%;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-query-data h2 {
|
||||||
|
font-size: 32px;
|
||||||
|
color: rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -159,9 +159,6 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.data-view {
|
|
||||||
min-height: 40%;
|
|
||||||
}
|
|
||||||
.cluster-listing {
|
.cluster-listing {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -186,6 +183,13 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -206,6 +210,14 @@
|
||||||
min-width: 20vw;
|
min-width: 20vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.query-settings {
|
||||||
|
margin-right: 1rem;
|
||||||
|
padding: 1em;
|
||||||
|
overflow: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.settings-sub-option {
|
.settings-sub-option {
|
||||||
margin-top: 0.2em;
|
margin-top: 0.2em;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
34
src/utils.js
34
src/utils.js
|
@ -29,30 +29,20 @@ const jsonRequest = type => async (path, object) => fetch(backendUrl + path, {
|
||||||
export const postJson = (path, object) => jsonRequest('POST')(path, object)
|
export const postJson = (path, object) => jsonRequest('POST')(path, object)
|
||||||
export const putJson = (path, object) => jsonRequest('PUT')(path, object)
|
export const putJson = (path, object) => jsonRequest('PUT')(path, object)
|
||||||
|
|
||||||
const mockCluster = {
|
|
||||||
clientId: 'TestClient',
|
|
||||||
brokers: ['testbroker.com:5000'],
|
|
||||||
ssl: true,
|
|
||||||
sasl: {
|
|
||||||
mechanism: 'SCRAM-SHA-512',
|
|
||||||
username: 'testuser',
|
|
||||||
password: 'XXXXXXXXXXXXXXXXXXXXXX'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const mockApi = {
|
const mockApi = {
|
||||||
'/clusters': {
|
'/clusters': {
|
||||||
'TestCluster1': mockCluster,
|
'TestCluster': {
|
||||||
'TestCluster2': mockCluster,
|
clientId: 'TestClient',
|
||||||
'TestCluster3': mockCluster,
|
brokers: ['testbroker.com:5000'],
|
||||||
'TestCluster4': mockCluster,
|
ssl: true,
|
||||||
'TestCluster5': mockCluster,
|
sasl: {
|
||||||
'TestCluster6': mockCluster,
|
mechanism: 'SCRAM-SHA-512',
|
||||||
'TestCluster7': mockCluster,
|
username: 'testuser',
|
||||||
'TestCluster8': mockCluster,
|
password: 'XXXXXXXXXXXXXXXXXXXXXX'
|
||||||
'TestCluster9': mockCluster,
|
}
|
||||||
'TestCluster10': mockCluster,
|
}
|
||||||
},
|
},
|
||||||
'/topics/TestCluster1': ['Purchases'],
|
'/topics/TestCluster': ['NewReleases']
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,5 +76,3 @@ export const apiFetch = async (path, options = undefined) => {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setStateError = e => state.update(s => ({ ...s, error: e.toString() }))
|
|
||||||
|
|
Loading…
Reference in New Issue