mirror of
https://github.com/garraflavatra/rolens.git
synced 2025-01-18 21:17:59 +00:00
Loose JSON parsing. Many UI improvements.
This commit is contained in:
parent
7ac1587c95
commit
78540622ee
@ -3,7 +3,7 @@
|
|||||||
import Icon from './icon.svelte';
|
import Icon from './icon.svelte';
|
||||||
import FormInput from './forminput.svelte';
|
import FormInput from './forminput.svelte';
|
||||||
import contextMenu from '$lib/stores/contextmenu';
|
import contextMenu from '$lib/stores/contextmenu';
|
||||||
import { resolveKeypath, setValue } from '$lib/keypaths';
|
import { resolveKeypath, setValue } from '$lib/objects';
|
||||||
|
|
||||||
export let items = [];
|
export let items = [];
|
||||||
export let columns = [];
|
export let columns = [];
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import Modal from './modal.svelte';
|
import Modal from './modal.svelte';
|
||||||
import ObjectTree from './objecttree.svelte';
|
import ObjectTree from './objecttree.svelte';
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
|
import { deepClone } from '$lib/objects';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
@ -11,7 +12,7 @@
|
|||||||
let _data;
|
let _data;
|
||||||
|
|
||||||
$: if (data) {
|
$: if (data) {
|
||||||
_data = JSON.parse(JSON.stringify(data));
|
_data = deepClone(data);
|
||||||
for (const key of Object.keys(_data)) {
|
for (const key of Object.keys(_data)) {
|
||||||
if (typeof _data[key] === 'undefined') {
|
if (typeof _data[key] === 'undefined') {
|
||||||
delete _data[key];
|
delete _data[key];
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { isInt } from '$lib/math';
|
import { isInt } from '$lib/math';
|
||||||
import { canBeObjectId, int32, int64, uint64 } from '$lib/mongo';
|
import { canBeObjectId, int32, int64, uint64 } from '$lib/mongo';
|
||||||
|
import { jsonLooseParse } from '$lib/strings';
|
||||||
|
|
||||||
export default function input(node, { autofocus, type, onValid, onInvalid, mandatory } = {
|
export default function input(node, { autofocus, type, onValid, onInvalid, mandatory } = {
|
||||||
autofocus: false,
|
autofocus: false,
|
||||||
@ -26,7 +27,7 @@ export default function input(node, { autofocus, type, onValid, onInvalid, manda
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case 'json':
|
case 'json':
|
||||||
try {
|
try {
|
||||||
JSON.parse(node.value);
|
jsonLooseParse(node.value);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
29
frontend/src/lib/mongo/atomic-update-operators.json
Normal file
29
frontend/src/lib/mongo/atomic-update-operators.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"Fields": {
|
||||||
|
"$currentDate": "Current Date",
|
||||||
|
"$inc": "Increment",
|
||||||
|
"$min": "Min",
|
||||||
|
"$max": "Max",
|
||||||
|
"$mul": "Multiply",
|
||||||
|
"$rename": "Rename",
|
||||||
|
"$set": "Set",
|
||||||
|
"$setOnInsert": "Set on Insert",
|
||||||
|
"$unset": "Unset"
|
||||||
|
},
|
||||||
|
"Array": {
|
||||||
|
"$addToSet": "Add to Set",
|
||||||
|
"$pop": "Pop",
|
||||||
|
"$pull": "Pull",
|
||||||
|
"$push": "Push",
|
||||||
|
"$pullAll": "Push All"
|
||||||
|
},
|
||||||
|
"Modifiers": {
|
||||||
|
"$each": "Each",
|
||||||
|
"$position": "Position",
|
||||||
|
"$slice": "Slice",
|
||||||
|
"$sort": "Sort"
|
||||||
|
},
|
||||||
|
"Bitwise": {
|
||||||
|
"$bit": "Bit"
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
import { ObjectId } from 'bson';
|
import { ObjectId } from 'bson';
|
||||||
import aggregationStages from './aggregation-stages.json';
|
import aggregationStages from './aggregation-stages.json';
|
||||||
|
import atomicUpdateOperators from './atomic-update-operators.json';
|
||||||
import locales from './locales.json';
|
import locales from './locales.json';
|
||||||
|
|
||||||
export { aggregationStages, locales };
|
export { aggregationStages, atomicUpdateOperators, locales };
|
||||||
|
|
||||||
// Calculate the min and max values of (un)signed integers with n bits
|
// Calculate the min and max values of (un)signed integers with n bits
|
||||||
export const intMin = bits => Math.pow(2, bits - 1) * -1;
|
export const intMin = bits => Math.pow(2, bits - 1) * -1;
|
||||||
|
@ -56,3 +56,8 @@ export function setValue(object, path, value) {
|
|||||||
|
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deepClone(obj) {
|
||||||
|
// Room for improvement below
|
||||||
|
return JSON.parse(JSON.stringify(obj));
|
||||||
|
}
|
@ -2,3 +2,23 @@ export function capitalise(string = '') {
|
|||||||
const capitalised = string.charAt(0).toUpperCase() + string.slice(1);
|
const capitalised = string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
return capitalised;
|
return capitalised;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function jsonLooseParse(json) {
|
||||||
|
const obj = new Function(`return (${json})`)();
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertLooseJson(json) {
|
||||||
|
const j = JSON.stringify(jsonLooseParse(json));
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function looseJsonIsValid(json) {
|
||||||
|
try {
|
||||||
|
jsonLooseParse(json);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import MongoCollation from '$components/mongo-collation.svelte';
|
import MongoCollation from '$components/mongo-collation.svelte';
|
||||||
import input from '$lib/actions/input';
|
import input from '$lib/actions/input';
|
||||||
import { aggregationStageDocumentationURL, aggregationStages } from '$lib/mongo';
|
import { aggregationStageDocumentationURL, aggregationStages } from '$lib/mongo';
|
||||||
|
import { jsonLooseParse, looseJsonIsValid } from '$lib/strings';
|
||||||
import { Aggregate } from '$wails/go/app/App';
|
import { Aggregate } from '$wails/go/app/App';
|
||||||
import { BrowserOpenURL } from '$wails/runtime/runtime';
|
import { BrowserOpenURL } from '$wails/runtime/runtime';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
@ -14,15 +15,7 @@
|
|||||||
const options = {};
|
const options = {};
|
||||||
let stages = [];
|
let stages = [];
|
||||||
let settingsModalOpen = false;
|
let settingsModalOpen = false;
|
||||||
$: invalid = !stages.length || stages.some(stage => {
|
$: invalid = !stages.length || stages.some(stage => !stage.data || !looseJsonIsValid(stage.data));
|
||||||
try {
|
|
||||||
JSON.parse(stage.data);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function addStage() {
|
function addStage() {
|
||||||
stages = [ ...stages, { type: '$match' } ];
|
stages = [ ...stages, { type: '$match' } ];
|
||||||
@ -38,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
const pipeline = stages.map(stage => ({ [stage.type]: JSON.parse(stage.data) }));
|
const pipeline = stages.map(stage => ({ [stage.type]: jsonLooseParse(stage.data) }));
|
||||||
await Aggregate(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(pipeline), JSON.stringify(options));
|
await Aggregate(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(pipeline), JSON.stringify(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,11 +77,10 @@
|
|||||||
<Icon name="cog" /> Settings
|
<Icon name="cog" /> Settings
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<Modal title="Advanced settings" bind:show={settingsModalOpen}>
|
<Modal title="Advanced aggregation settings" bind:show={settingsModalOpen}>
|
||||||
<div class="settinggrid">
|
<div class="settinggrid">
|
||||||
<label for="allowDiskUse">Allow disk use</label>
|
<label for="allowDiskUse">Allow disk use</label>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import FormInput from '$components/forminput.svelte';
|
import FormInput from '$components/forminput.svelte';
|
||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import { inputTypes } from '$lib/mongo';
|
import { inputTypes } from '$lib/mongo';
|
||||||
import { resolveKeypath, setValue } from '$lib/keypaths';
|
import { resolveKeypath, setValue } from '$lib/objects';
|
||||||
import Hint from '$components/hint.svelte';
|
import Hint from '$components/hint.svelte';
|
||||||
|
|
||||||
export let item = {};
|
export let item = {};
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import CodeExample from '$components/code-example.svelte';
|
// import CodeExample from '$components/code-example.svelte';
|
||||||
import Grid from '$components/grid.svelte';
|
import Grid from '$components/grid.svelte';
|
||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import ObjectGrid from '$components/objectgrid.svelte';
|
import ObjectGrid from '$components/objectgrid.svelte';
|
||||||
import input from '$lib/actions/input';
|
import input from '$lib/actions/input';
|
||||||
|
import { deepClone } from '$lib/objects';
|
||||||
import queries from '$lib/stores/queries';
|
import queries from '$lib/stores/queries';
|
||||||
import applicationSettings from '$lib/stores/settings';
|
import applicationSettings from '$lib/stores/settings';
|
||||||
import views from '$lib/stores/views';
|
import views from '$lib/stores/views';
|
||||||
@ -45,7 +46,7 @@
|
|||||||
if (newResult) {
|
if (newResult) {
|
||||||
newResult.results = newResult.results?.map(s => EJSON.parse(s, { relaxed: false }));
|
newResult.results = newResult.results?.map(s => EJSON.parse(s, { relaxed: false }));
|
||||||
result = newResult;
|
result = newResult;
|
||||||
submittedForm = JSON.parse(JSON.stringify(form));
|
submittedForm = deepClone(form);
|
||||||
}
|
}
|
||||||
resetFocus();
|
resetFocus();
|
||||||
}
|
}
|
||||||
@ -157,12 +158,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row three">
|
<div class="form-row three">
|
||||||
<CodeExample {code} />
|
<!-- <CodeExample {code} /> -->
|
||||||
<button class="btn" type="button" on:click={loadQuery} title="Load query…">
|
<button class="btn" type="button" on:click={loadQuery}>
|
||||||
<Icon name="upload" />
|
<Icon name="upload" /> Load query…
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" type="button" on:click={saveQuery} title="Save query as…">
|
<button class="btn" type="button" on:click={saveQuery}>
|
||||||
<Icon name="save" />
|
<Icon name="save" /> Save as…
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" class="btn" title="Run query">
|
<button type="submit" class="btn" title="Run query">
|
||||||
<Icon name="play" /> Run
|
<Icon name="play" /> Run
|
||||||
@ -274,7 +275,8 @@
|
|||||||
}
|
}
|
||||||
.form-row.three {
|
.form-row.three {
|
||||||
margin-bottom: 0rem;
|
margin-bottom: 0rem;
|
||||||
grid-template: 1fr / 1fr repeat(3, auto);
|
grid-template: 1fr / repeat(3, auto);
|
||||||
|
justify-content: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result {
|
.result {
|
||||||
|
@ -29,6 +29,10 @@
|
|||||||
collection.key = collectionKey;
|
collection.key = collectionKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: if (hostKey || dbKey || collectionKey) {
|
||||||
|
tab = 'find';
|
||||||
|
}
|
||||||
|
|
||||||
EventsOn('OpenCollectionTab', name => (tab = name || tab));
|
EventsOn('OpenCollectionTab', name => (tab = name || tab));
|
||||||
|
|
||||||
async function catchQuery(event) {
|
async function catchQuery(event) {
|
||||||
|
@ -43,6 +43,16 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="indexes">
|
<div class="indexes">
|
||||||
|
<div class="grid">
|
||||||
|
<ObjectGrid
|
||||||
|
key="name"
|
||||||
|
data={indexes}
|
||||||
|
getRootMenu={(_, idx) => [ { label: 'Drop this index', fn: () => drop(idx.name) } ]}
|
||||||
|
bind:activePath
|
||||||
|
on:trigger={e => openJson(e.detail.itemKey)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn" on:click={getIndexes}>
|
<button class="btn" on:click={getIndexes}>
|
||||||
<Icon name="reload" /> Reload
|
<Icon name="reload" /> Reload
|
||||||
@ -54,16 +64,6 @@
|
|||||||
<Icon name="x" /> Drop selected
|
<Icon name="x" /> Drop selected
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid">
|
|
||||||
<ObjectGrid
|
|
||||||
key="name"
|
|
||||||
data={indexes}
|
|
||||||
getRootMenu={(_, idx) => [ { label: 'Drop this index', fn: () => drop(idx.name) } ]}
|
|
||||||
bind:activePath
|
|
||||||
on:trigger={e => openJson(e.detail.itemKey)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ObjectViewer bind:data={objectViewerData} />
|
<ObjectViewer bind:data={objectViewerData} />
|
||||||
@ -73,7 +73,7 @@
|
|||||||
.indexes {
|
.indexes {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
grid-template: auto 1fr / 1fr;
|
grid-template: 1fr auto / 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.indexes .grid {
|
.indexes .grid {
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
import { randomString } from '$lib/math';
|
import { randomString } from '$lib/math';
|
||||||
import { inputTypes } from '$lib/mongo';
|
import { inputTypes } from '$lib/mongo';
|
||||||
import views from '$lib/stores/views';
|
import views from '$lib/stores/views';
|
||||||
import { capitalise } from '$lib/strings';
|
import { capitalise, convertLooseJson, jsonLooseParse } from '$lib/strings';
|
||||||
import { InsertItems } from '$wails/go/app/App';
|
import { InsertItems } from '$wails/go/app/App';
|
||||||
import { EJSON } from 'bson';
|
import { EJSON } from 'bson';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
@ -32,7 +32,7 @@
|
|||||||
$: {
|
$: {
|
||||||
if (collection.viewKey === 'list') {
|
if (collection.viewKey === 'list') {
|
||||||
try {
|
try {
|
||||||
newItems = EJSON.parse(json, { relaxed: false });
|
newItems = EJSON.deserialize(jsonLooseParse(json), { relaxed: false });
|
||||||
}
|
}
|
||||||
catch { /* ok */ }
|
catch { /* ok */ }
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function insert() {
|
async function insert() {
|
||||||
insertedIds = await InsertItems(collection.hostKey, collection.dbKey, collection.key, json);
|
insertedIds = await InsertItems(collection.hostKey, collection.dbKey, collection.key, convertLooseJson(json));
|
||||||
if ((collection.viewKey === 'list') && insertedIds) {
|
if ((collection.viewKey === 'list') && insertedIds) {
|
||||||
newItems = [];
|
newItems = [];
|
||||||
}
|
}
|
||||||
@ -95,15 +95,8 @@
|
|||||||
<form on:submit|preventDefault={insert}>
|
<form on:submit|preventDefault={insert}>
|
||||||
<div class="items">
|
<div class="items">
|
||||||
{#if collection.viewKey === 'list'}
|
{#if collection.viewKey === 'list'}
|
||||||
<label class="field">
|
<label class="field json">
|
||||||
<textarea
|
<textarea placeholder="[]" class="code" bind:value={json} use:input={{ type: 'json', autofocus: true }}></textarea>
|
||||||
cols="30"
|
|
||||||
rows="10"
|
|
||||||
placeholder="[]"
|
|
||||||
class="code"
|
|
||||||
bind:value={json}
|
|
||||||
use:input={{ type: 'json', autofocus: true }}
|
|
||||||
></textarea>
|
|
||||||
</label>
|
</label>
|
||||||
{:else if viewType === 'form'}
|
{:else if viewType === 'form'}
|
||||||
<div class="form">
|
<div class="form">
|
||||||
@ -208,4 +201,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.field.json {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.field.json textarea {
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import CodeExample from '$components/code-example.svelte';
|
// import CodeExample from '$components/code-example.svelte';
|
||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import input from '$lib/actions/input';
|
import input from '$lib/actions/input';
|
||||||
import { RemoveItems } from '$wails/go/app/App';
|
import { RemoveItems } from '$wails/go/app/App';
|
||||||
@ -17,15 +17,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form on:submit|preventDefault={removeItems}>
|
<form on:submit|preventDefault={removeItems}>
|
||||||
<div class="options">
|
<!-- <CodeExample {code} /> -->
|
||||||
<CodeExample {code} />
|
|
||||||
<label class="field">
|
|
||||||
<span class="label">Many</span>
|
|
||||||
<span class="checkbox">
|
|
||||||
<input type="checkbox" bind:checked={many} />
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<textarea
|
<textarea
|
||||||
@ -38,35 +30,38 @@
|
|||||||
></textarea>
|
></textarea>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="flex">
|
<div class="actions">
|
||||||
<div>
|
<button type="submit" class="btn danger">
|
||||||
|
<Icon name="-" /> Remove
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<label class="field many">
|
||||||
|
<span class="label">Many</span>
|
||||||
|
<span class="checkbox">
|
||||||
|
<input type="checkbox" bind:checked={many} />
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
{#key result}
|
{#key result}
|
||||||
{#if typeof result === 'number'}
|
{#if typeof result === 'number'}
|
||||||
<span class="flash-green">Removed {result} item{result === 1 ? '' : 's'}</span>
|
<span class="flash-green">Removed {result} item{result === 1 ? '' : 's'}</span>
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn danger">
|
|
||||||
<Icon name="-" /> Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
form {
|
form {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr auto;
|
grid-template-rows: 1fr auto;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options {
|
.many {
|
||||||
display: grid;
|
display: inline-flex;
|
||||||
gap: 0.5rem;
|
|
||||||
grid-template: 1fr / 1fr auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex {
|
textarea {
|
||||||
display: flex;
|
resize: none;
|
||||||
justify-content: space-between;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<script>
|
<script>
|
||||||
import CodeExample from '$components/code-example.svelte';
|
// import CodeExample from '$components/code-example.svelte';
|
||||||
import ObjectGrid from '$components/objectgrid.svelte';
|
import ObjectGrid from '$components/objectgrid.svelte';
|
||||||
|
|
||||||
export let collection;
|
export let collection;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<CodeExample code="db.stats()" />
|
<!-- <CodeExample code="db.stats()" /> -->
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<ObjectGrid data={collection.stats} />
|
<ObjectGrid data={collection.stats} />
|
||||||
@ -17,7 +17,7 @@
|
|||||||
.stats {
|
.stats {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
grid-template: auto 1fr / 1fr;
|
grid-template: auto / 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats .grid {
|
.stats .grid {
|
||||||
|
@ -1,45 +1,31 @@
|
|||||||
<script>
|
<script>
|
||||||
import CodeExample from '$components/code-example.svelte';
|
// import CodeExample from '$components/code-example.svelte';
|
||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import input from '$lib/actions/input';
|
import input from '$lib/actions/input';
|
||||||
|
import { atomicUpdateOperators } from '$lib/mongo';
|
||||||
|
import { deepClone } from '$lib/objects';
|
||||||
|
import { convertLooseJson, jsonLooseParse } from '$lib/strings';
|
||||||
import { UpdateItems } from '$wails/go/app/App';
|
import { UpdateItems } from '$wails/go/app/App';
|
||||||
|
|
||||||
export let collection = {};
|
export let collection = {};
|
||||||
|
|
||||||
const atomicUpdateOperators = {
|
|
||||||
'Fields': {
|
|
||||||
$currentDate: 'Current Date',
|
|
||||||
$inc: 'Increment',
|
|
||||||
$min: 'Min',
|
|
||||||
$max: 'Max',
|
|
||||||
$mul: 'Multiply',
|
|
||||||
$rename: 'Rename',
|
|
||||||
$set: 'Set',
|
|
||||||
$setOnInsert: 'Set on Insert',
|
|
||||||
$unset: 'Unset',
|
|
||||||
},
|
|
||||||
'Array': {
|
|
||||||
$addToSet: 'Add to Set',
|
|
||||||
$pop: 'Pop',
|
|
||||||
$pull: 'Pull',
|
|
||||||
$push: 'Push',
|
|
||||||
$pullAll: 'Push All',
|
|
||||||
},
|
|
||||||
'Modifiers': {
|
|
||||||
$each: 'Each',
|
|
||||||
$position: 'Position',
|
|
||||||
$slice: 'Slice',
|
|
||||||
$sort: 'Sort',
|
|
||||||
},
|
|
||||||
'Bitwise': {
|
|
||||||
$bit: 'Bit',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const allOperators = Object.values(atomicUpdateOperators).map(Object.keys).flat();
|
const allOperators = Object.values(atomicUpdateOperators).map(Object.keys).flat();
|
||||||
|
|
||||||
const form = { query: '{}', parameters: [ { type: '$set' } ] };
|
const form = { query: '{}', parameters: [ { type: '$set' } ] };
|
||||||
let updatedCount;
|
let updatedCount;
|
||||||
$: code = buildCode(form);
|
$: code = buildCode(form);
|
||||||
|
$: invalid = !form.query || form.parameters?.some(param => {
|
||||||
|
if (!param.value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
jsonLooseParse(param.value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function buildCode(form) {
|
function buildCode(form) {
|
||||||
let operation = '{ ' + form.parameters.filter(p => p.type).map(p => `${p.type}: ${p.value || '{}'}`).join(', ') + ' }';
|
let operation = '{ ' + form.parameters.filter(p => p.type).map(p => `${p.type}: ${p.value || '{}'}`).join(', ') + ' }';
|
||||||
@ -58,7 +44,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function submitQuery() {
|
async function submitQuery() {
|
||||||
updatedCount = await UpdateItems(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(form));
|
const f = deepClone(form);
|
||||||
|
f.query = convertLooseJson(f.query);
|
||||||
|
f.parameters = f.parameters.map(param => ({ ...param, value: convertLooseJson(param.value) }));
|
||||||
|
updatedCount = await UpdateItems(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeParam(index) {
|
function removeParam(index) {
|
||||||
@ -92,7 +81,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="update" on:submit|preventDefault={submitQuery}>
|
<form class="update" on:submit|preventDefault={submitQuery}>
|
||||||
<CodeExample language="json" {code} />
|
<!-- <CodeExample language="json" {code} /> -->
|
||||||
|
|
||||||
<div class="options">
|
<div class="options">
|
||||||
<label class="field">
|
<label class="field">
|
||||||
@ -117,7 +106,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
|
|
||||||
<button class="btn" type="submit">
|
<button class="btn" type="submit" disabled={invalid}>
|
||||||
<Icon name="check" /> Update
|
<Icon name="check" /> Update
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -135,7 +124,7 @@
|
|||||||
{#each Object.entries(atomicUpdateOperators) as [groupName, options]}
|
{#each Object.entries(atomicUpdateOperators) as [groupName, options]}
|
||||||
<optgroup label={groupName}>
|
<optgroup label={groupName}>
|
||||||
{#each Object.entries(options) as [key, label]}
|
{#each Object.entries(options) as [key, label]}
|
||||||
<option value={key} disabled={form.parameters.some(p => p.type === key)}>
|
<option value={key} disabled={form.parameters.some(p => p.type === key) && (key !== param.type)}>
|
||||||
{label}
|
{label}
|
||||||
</option>
|
</option>
|
||||||
{/each}
|
{/each}
|
||||||
@ -145,14 +134,13 @@
|
|||||||
<input type="text" class="code" bind:value={param.value} placeholder={'{}'} use:input={{ type: 'json' }} />
|
<input type="text" class="code" bind:value={param.value} placeholder={'{}'} use:input={{ type: 'json' }} />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="field">
|
|
||||||
<button class="btn" disabled={form.parameters.length >= allOperators.length} on:click={() => addParameter()} type="button">
|
<button class="btn" disabled={form.parameters.length >= allOperators.length} on:click={() => addParameter()} type="button">
|
||||||
<Icon name="+" />
|
<Icon name="+" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="btn" disabled={form.parameters.length < 2} on:click={() => removeParam(index)} type="button">
|
<button class="btn" disabled={form.parameters.length < 2} on:click={() => removeParam(index)} type="button">
|
||||||
<Icon name="-" />
|
<Icon name="-" />
|
||||||
</button>
|
</button>
|
||||||
</label>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{/each}
|
{/each}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@ -162,7 +150,7 @@
|
|||||||
.update {
|
.update {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
grid-template: auto auto auto 1fr / 1fr;
|
grid-template: auto auto 1fr / 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options {
|
.options {
|
||||||
@ -181,7 +169,7 @@
|
|||||||
}
|
}
|
||||||
.parameter {
|
.parameter {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template: 1fr / 1fr auto;
|
grid-template: 1fr / 1fr auto auto;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
frontend/wailsjs/go/app/App.d.ts
vendored
2
frontend/wailsjs/go/app/App.d.ts
vendored
@ -41,6 +41,8 @@ export function OpenDirectory(arg1:string,arg2:string):Promise<string>;
|
|||||||
|
|
||||||
export function PerformExport(arg1:string):Promise<boolean>;
|
export function PerformExport(arg1:string):Promise<boolean>;
|
||||||
|
|
||||||
|
export function PurgeLogDirectory():Promise<void>;
|
||||||
|
|
||||||
export function RemoveHost(arg1:string):Promise<void>;
|
export function RemoveHost(arg1:string):Promise<void>;
|
||||||
|
|
||||||
export function RemoveItemById(arg1:string,arg2:string,arg3:string,arg4:string):Promise<boolean>;
|
export function RemoveItemById(arg1:string,arg2:string,arg3:string,arg4:string):Promise<boolean>;
|
||||||
|
@ -74,6 +74,10 @@ export function PerformExport(arg1) {
|
|||||||
return window['go']['app']['App']['PerformExport'](arg1);
|
return window['go']['app']['App']['PerformExport'](arg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function PurgeLogDirectory() {
|
||||||
|
return window['go']['app']['App']['PurgeLogDirectory']();
|
||||||
|
}
|
||||||
|
|
||||||
export function RemoveHost(arg1) {
|
export function RemoveHost(arg1) {
|
||||||
return window['go']['app']['App']['RemoveHost'](arg1);
|
return window['go']['app']['App']['RemoveHost'](arg1);
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,34 @@ func (a *App) Environment() EnvironmentInfo {
|
|||||||
return a.Env
|
return a.Env
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) PurgeLogDirectory() {
|
||||||
|
sure, _ := wailsRuntime.MessageDialog(a.ctx, wailsRuntime.MessageDialogOptions{
|
||||||
|
Title: "Are you sure you want to remove all logfiles?",
|
||||||
|
Buttons: []string{"Yes", "No"},
|
||||||
|
DefaultButton: "Yes",
|
||||||
|
CancelButton: "No",
|
||||||
|
Type: wailsRuntime.QuestionDialog,
|
||||||
|
})
|
||||||
|
|
||||||
|
if sure != "Yes" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.RemoveAll(a.Env.LogDirectory)
|
||||||
|
if err == nil {
|
||||||
|
wailsRuntime.MessageDialog(a.ctx, wailsRuntime.MessageDialogOptions{
|
||||||
|
Title: "Successfully purged log directory.",
|
||||||
|
Type: wailsRuntime.InfoDialog,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
wailsRuntime.MessageDialog(a.ctx, wailsRuntime.MessageDialogOptions{
|
||||||
|
Title: "Encountered an error while purging log directory.",
|
||||||
|
Message: err.Error(),
|
||||||
|
Type: wailsRuntime.WarningDialog,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func menuEventEmitter(a *App, eventName string, data ...interface{}) func(cd *menu.CallbackData) {
|
func menuEventEmitter(a *App, eventName string, data ...interface{}) func(cd *menu.CallbackData) {
|
||||||
return func(cd *menu.CallbackData) {
|
return func(cd *menu.CallbackData) {
|
||||||
wailsRuntime.EventsEmit(a.ctx, eventName, data...)
|
wailsRuntime.EventsEmit(a.ctx, eventName, data...)
|
||||||
@ -99,6 +127,9 @@ func (a *App) Menu() *menu.Menu {
|
|||||||
aboutMenu.AddText("About…", nil, menuEventEmitter(a, "OpenAboutModal"))
|
aboutMenu.AddText("About…", nil, menuEventEmitter(a, "OpenAboutModal"))
|
||||||
aboutMenu.AddText("Prefrences…", keys.CmdOrCtrl(","), menuEventEmitter(a, "OpenPrefrences"))
|
aboutMenu.AddText("Prefrences…", keys.CmdOrCtrl(","), menuEventEmitter(a, "OpenPrefrences"))
|
||||||
aboutMenu.AddSeparator()
|
aboutMenu.AddSeparator()
|
||||||
|
aboutMenu.AddText("Open log directory…", nil, func(cd *menu.CallbackData) { open_file.Reveal(a.Env.LogDirectory) })
|
||||||
|
aboutMenu.AddText("Purge logs", nil, func(cd *menu.CallbackData) { a.PurgeLogDirectory() })
|
||||||
|
aboutMenu.AddSeparator()
|
||||||
aboutMenu.AddText("Quit", keys.CmdOrCtrl("q"), func(cd *menu.CallbackData) { wailsRuntime.Quit(a.ctx) })
|
aboutMenu.AddText("Quit", keys.CmdOrCtrl("q"), func(cd *menu.CallbackData) { wailsRuntime.Quit(a.ctx) })
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
@ -123,8 +154,6 @@ func (a *App) Menu() *menu.Menu {
|
|||||||
|
|
||||||
helpMenu := appMenu.AddSubmenu("Help")
|
helpMenu := appMenu.AddSubmenu("Help")
|
||||||
helpMenu.AddText("User guide", nil, func(cd *menu.CallbackData) { wailsRuntime.BrowserOpenURL(a.ctx, "") })
|
helpMenu.AddText("User guide", nil, func(cd *menu.CallbackData) { wailsRuntime.BrowserOpenURL(a.ctx, "") })
|
||||||
helpMenu.AddSeparator()
|
|
||||||
helpMenu.AddText("Open log directory", nil, func(cd *menu.CallbackData) { open_file.Reveal(a.Env.LogDirectory) })
|
|
||||||
|
|
||||||
return appMenu
|
return appMenu
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@ func (a *App) TruncateCollection(hostKey, dbKey, collKey string) bool {
|
|||||||
Buttons: []string{"Yes", "No"},
|
Buttons: []string{"Yes", "No"},
|
||||||
DefaultButton: "Yes",
|
DefaultButton: "Yes",
|
||||||
CancelButton: "No",
|
CancelButton: "No",
|
||||||
|
Type: runtime.WarningDialog,
|
||||||
})
|
})
|
||||||
if sure != "Yes" {
|
if sure != "Yes" {
|
||||||
return false
|
return false
|
||||||
@ -97,6 +98,7 @@ func (a *App) DropCollection(hostKey, dbKey, collKey string) bool {
|
|||||||
Buttons: []string{"Yes", "No"},
|
Buttons: []string{"Yes", "No"},
|
||||||
DefaultButton: "Yes",
|
DefaultButton: "Yes",
|
||||||
CancelButton: "No",
|
CancelButton: "No",
|
||||||
|
Type: runtime.WarningDialog,
|
||||||
})
|
})
|
||||||
if sure != "Yes" {
|
if sure != "Yes" {
|
||||||
return false
|
return false
|
||||||
|
@ -20,6 +20,7 @@ func (a *App) RemoveItems(hostKey, dbKey, collKey, jsonData string, many bool) i
|
|||||||
Buttons: []string{"Yes", "No"},
|
Buttons: []string{"Yes", "No"},
|
||||||
DefaultButton: "Yes",
|
DefaultButton: "Yes",
|
||||||
CancelButton: "No",
|
CancelButton: "No",
|
||||||
|
Type: runtime.WarningDialog,
|
||||||
})
|
})
|
||||||
if sure != "Yes" {
|
if sure != "Yes" {
|
||||||
return 0
|
return 0
|
||||||
|
@ -44,11 +44,12 @@ func (a *App) UpdateItems(hostKey, dbKey, collKey string, formJson string) int64
|
|||||||
|
|
||||||
err = bson.UnmarshalExtJSON([]byte(form.Query), true, &query)
|
err = bson.UnmarshalExtJSON([]byte(form.Query), true, &query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogError(a.ctx, "Invalid update query:")
|
runtime.LogWarning(a.ctx, "Invalid update query:")
|
||||||
runtime.LogError(a.ctx, err.Error())
|
runtime.LogWarning(a.ctx, form.Query)
|
||||||
|
runtime.LogWarning(a.ctx, err.Error())
|
||||||
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
||||||
Type: runtime.ErrorDialog,
|
Type: runtime.ErrorDialog,
|
||||||
Title: "Invalid query",
|
Title: "Invalid update query",
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
})
|
})
|
||||||
return 0
|
return 0
|
||||||
@ -60,11 +61,12 @@ func (a *App) UpdateItems(hostKey, dbKey, collKey string, formJson string) int64
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
update[param.Type] = unmarshalled
|
update[param.Type] = unmarshalled
|
||||||
} else {
|
} else {
|
||||||
runtime.LogError(a.ctx, "Invalid update query:")
|
runtime.LogWarning(a.ctx, "Invalid update parameter value:")
|
||||||
runtime.LogError(a.ctx, err.Error())
|
runtime.LogWarning(a.ctx, param.Value)
|
||||||
|
runtime.LogWarning(a.ctx, err.Error())
|
||||||
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
||||||
Type: runtime.ErrorDialog,
|
Type: runtime.ErrorDialog,
|
||||||
Title: "Invalid query",
|
Title: "Invalid update query",
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
})
|
})
|
||||||
return 0
|
return 0
|
||||||
|
@ -34,6 +34,7 @@ func (a *App) DropDatabase(hostKey, dbKey string) bool {
|
|||||||
Buttons: []string{"Yes", "No"},
|
Buttons: []string{"Yes", "No"},
|
||||||
DefaultButton: "Yes",
|
DefaultButton: "Yes",
|
||||||
CancelButton: "No",
|
CancelButton: "No",
|
||||||
|
Type: runtime.WarningDialog,
|
||||||
})
|
})
|
||||||
if sure != "Yes" {
|
if sure != "Yes" {
|
||||||
return false
|
return false
|
||||||
|
@ -150,6 +150,7 @@ func (a *App) RemoveHost(key string) error {
|
|||||||
Buttons: []string{"Yes", "No"},
|
Buttons: []string{"Yes", "No"},
|
||||||
DefaultButton: "Yes",
|
DefaultButton: "Yes",
|
||||||
CancelButton: "No",
|
CancelButton: "No",
|
||||||
|
Type: runtime.WarningDialog,
|
||||||
})
|
})
|
||||||
if sure != "Yes" {
|
if sure != "Yes" {
|
||||||
return errors.New("operation aborted")
|
return errors.New("operation aborted")
|
||||||
|
@ -135,6 +135,7 @@ func (a *App) RemoveView(viewKey string) error {
|
|||||||
Buttons: []string{"Yes", "No"},
|
Buttons: []string{"Yes", "No"},
|
||||||
DefaultButton: "Yes",
|
DefaultButton: "Yes",
|
||||||
CancelButton: "No",
|
CancelButton: "No",
|
||||||
|
Type: runtime.WarningDialog,
|
||||||
})
|
})
|
||||||
if sure != "Yes" {
|
if sure != "Yes" {
|
||||||
return errors.New("operation aborted")
|
return errors.New("operation aborted")
|
||||||
|
Loading…
Reference in New Issue
Block a user