Skip to content

Commit

Permalink
Add host log panel
Browse files Browse the repository at this point in the history
  • Loading branch information
garraflavatra committed Jul 1, 2023
1 parent 0b9f233 commit f30827a
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 18 deletions.
2 changes: 2 additions & 0 deletions frontend/src/components/icon.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,7 @@
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0zM12 9v4M12 17h.01" />
{:else if name === 'loading'}
<path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" />
{:else if name === 'doc'}
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8" />
{/if}
</svg>
5 changes: 2 additions & 3 deletions frontend/src/components/modal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
<script>
import { Beep } from '$wails/go/ui/UI';
import { createEventDispatcher } from 'svelte';
import { fade, fly } from 'svelte/transition';
import Icon from './icon.svelte';
export let show = true;
Expand Down Expand Up @@ -43,8 +42,8 @@
<svelte:window on:keydown={keydown} />

{#if show}
<div class="modal outer" transition:fade on:pointerdown|self={Beep}>
<div class="inner" style:max-width={width || '80vw'} transition:fly={{ y: -100 }}>
<div class="modal outer" on:pointerdown|self={Beep}>
<div class="inner" style:max-width={width || '80vw'}>
{#if title}
<header>
<div class="title">{title}</div>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/components/objecteditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
export let text = '';
export let editor = undefined;
export let readonly = false;
const dispatch = createEventDispatcher();
let editorParent;
Expand All @@ -20,6 +21,7 @@
keymap.of([ indentWithTab, indentOnInput ]),
javascript(),
EditorState.tabSize.of(4),
EditorState.readOnly.of(readonly),
EditorView.updateListener.of(e => {
if (!e.docChanged) {
return;
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/objectviewer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import ObjectEditor from './objecteditor.svelte';
export let data;
export let readonly = false;
export let saveable = false;
export let successMessage = '';
Expand Down Expand Up @@ -37,7 +38,7 @@
{#if data}
<Modal bind:show={data} contentPadding={false}>
<div class="objectviewer">
<ObjectEditor bind:text on:updated={() => successMessage = ''} />
<ObjectEditor bind:text on:updated={() => successMessage = ''} {readonly} />
</div>

<svelte:fragment slot="footer">
Expand Down
21 changes: 13 additions & 8 deletions frontend/src/lib/stores/hosttree.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
DropDatabase,
DropIndex,
GetIndexes,
HostLogs,
Hosts,
OpenCollection,
OpenConnection,
Expand Down Expand Up @@ -263,17 +264,17 @@ async function refresh() {
};
}

host.newDatabase = async function() {
const name = await dialogs.enterText('Create a database', 'Enter the database name. Note: databases in MongoDB do not exist until they have a collection and an item. Your new database will not persist on the server; fill it to have it created.', '');
if (name) {
host.databases[name] = { key: name, new: true };
await host.open();
}
};

await refresh();
};

host.newDatabase = async function() {
const name = await dialogs.enterText('Create a database', 'Enter the database name. Note: databases in MongoDB do not exist until they have a collection and an item. Your new database will not persist on the server; fill it to have it created.', '');
if (name) {
host.databases[name] = { key: name, new: true };
await host.open();
}
};

host.edit = async function() {
const dialog = dialogs.new(HostDetailDialog, { hostKey });
return new Promise(resolve => {
Expand All @@ -283,6 +284,10 @@ async function refresh() {
});
};

host.getLogs = async function(filter = 'global') {
return await HostLogs(hostKey, filter);
};

host.remove = async function() {
await RemoveHost(hostKey);
await refresh();
Expand Down
16 changes: 11 additions & 5 deletions frontend/src/organisms/connection/host/index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import BlankState from '$components/blankstate.svelte';
import TabBar from '$components/tabbar.svelte';
import { EventsOn } from '$wails/runtime/runtime';
import Logs from './logs.svelte';
import Status from './status.svelte';
import SystemInfo from './systeminfo.svelte';
Expand All @@ -23,14 +25,18 @@
<div class="view" class:empty={!host}>
{#if host}
{#key host}
<TabBar tabs={[
{ key: 'status', icon: 'chart', title: 'Host status' },
{ key: 'systemInfo', icon: 'server', title: 'System info' },
]}
bind:selectedKey={tab} />
<TabBar
tabs={[
{ key: 'status', icon: 'chart', title: 'Host status' },
{ key: 'logs', icon: 'text', title: 'Logs' },
{ key: 'systemInfo', icon: 'server', title: 'System info' },
]}
bind:selectedKey={tab}
/>

<div class="container">
{#if tab === 'status'} <Status {host} />
{:else if tab === 'logs'} <Logs {host} />
{:else if tab === 'systemInfo'} <SystemInfo {host} />
{/if}
</div>
Expand Down
139 changes: 139 additions & 0 deletions frontend/src/organisms/connection/host/logs.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<script>
import Grid from '$components/grid.svelte';
import Icon from '$components/icon.svelte';
import ObjectViewer from '$components/objectviewer.svelte';
import input from '$lib/actions/input';
import { BrowserOpenURL } from '$wails/runtime/runtime';
export let host;
const autoReloadIntervals = [ 1, 2, 5, 10, 30, 60 ];
let filter = 'global';
let logs;
let total = 0;
let error = '';
let copySucceeded = false;
let autoReloadInterval = 0;
let objectViewerData;
$: filter && refresh();
$: busy = !logs && !error && 'Requesting logs…';
async function refresh() {
let _logs = [];
({ logs: _logs, total, error } = await host.getLogs(filter));
logs = [];
for (let index = 0; index < _logs.length; index++) {
const log = JSON.parse(_logs[index]);
log._index = index;
logs = [ ...logs, log ];
}
}
function openFilterDocs() {
BrowserOpenURL('https://www.mongodb.com/docs/manual/reference/command/getLog/#command-fields');
}
function openLogDetail(event) {
objectViewerData = logs[event.detail.index];
}
async function copy() {
const json = JSON.stringify(host.status, undefined, '\t');
await navigator.clipboard.writeText(json);
copySucceeded = true;
setTimeout(() => copySucceeded = false, 1500);
}
</script>

<div class="stats">
<div class="grid">
<Grid
items={logs || []}
columns={[
{ title: 'Date', key: 't.$date' },
{ title: 'Severity', key: 's' },
{ title: 'ID', key: 'id' },
{ title: 'Component', key: 'c' },
{ title: 'Context', key: 'ctx' },
{ title: 'Message', key: 'msg' },
]}
key="_index"
showHeaders
errorTitle={error ? 'Error fetching server status' : ''}
errorDescription={error}
on:trigger={openLogDetail}
{busy}
/>
</div>

<div class="controls">
<div>
<div class="field inline">
<button class="button" on:click={refresh}>
<Icon name="reload" spin={busy} /> Reload
</button>

<button class="button secondary" on:click={copy} disabled={!host.status}>
<Icon name={copySucceeded ? 'check' : 'clipboard'} />
Copy JSON
</button>
</div>

<label class="field inline">
<span class="label">Reload (sec)</span>
<input type="number" bind:value={autoReloadInterval} list="autoreloadintervals" use:input />
</label>

<label class="field inline">
<select bind:value={filter}>
<option value="global">Global</option>
<option value="startupWarnings">Startup warnings</option>
</select>
<button class="button secondary" on:click={openFilterDocs} title="Documentation">
<Icon name="?" />
</button>
</label>
</div>

{#if total}
<div class="total">
Total: {total}
</div>
{/if}
</div>
</div>

{#if objectViewerData}
<ObjectViewer bind:data={objectViewerData} readonly />
{/if}

<datalist id="autoreloadintervals">
{#each autoReloadIntervals as value}
<option {value} />
{/each}
</datalist>

<style>
.stats {
display: grid;
gap: 0.5rem;
grid-template: 1fr auto / 1fr;
}
.stats .grid {
overflow: auto;
min-height: 0;
min-width: 0;
border: 1px solid #ccc;
}
.controls {
display: flex;
align-items: center;
gap: 0.1rem;
}
.total {
margin-left: auto;
}
</style>
2 changes: 1 addition & 1 deletion frontend/src/styles/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ select:disabled {
.field > textarea,
.field > select {
flex: 1;
padding: 0.5rem;
padding: 0 0.5rem;
border: 1px solid #ccc;
background-color: #fff;
appearance: none;
Expand Down
2 changes: 2 additions & 0 deletions frontend/wailsjs/go/app/App.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions frontend/wailsjs/go/app/App.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions internal/app/host_logs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package app

import (
"github.com/wailsapp/wails/v2/pkg/runtime"
"go.mongodb.org/mongo-driver/bson"
)

type HostLogsResult struct {
Total int32 `json:"total"`
Logs []string `json:"logs"`
Error string `json:"error"`
}

func (a *App) HostLogs(hostKey, filter string) (result HostLogsResult) {
client, ctx, close, err := a.connectToHost(hostKey)
if err != nil {
result.Error = "Could not connect to host"
return
}
defer close()

var res bson.M
err = client.Database("admin").RunCommand(ctx, bson.M{"getLog": filter}).Decode(&res)
if err != nil {
runtime.LogWarningf(a.ctx, "Could not get %s logs for %s: %s", filter, hostKey, err.Error())
result.Error = err.Error()
}

result.Total = res["totalLinesWritten"].(int32)
result.Logs = make([]string, 0)

for _, v := range res["log"].(bson.A) {
result.Logs = append(result.Logs, v.(string))
}

return
}

0 comments on commit f30827a

Please sign in to comment.