merge v0.23.0-rc changes
This commit is contained in:
@@ -66,12 +66,13 @@
|
||||
"execTime",
|
||||
"type",
|
||||
"auth",
|
||||
"authId",
|
||||
"status",
|
||||
"method",
|
||||
"url",
|
||||
"referer",
|
||||
"remoteIp",
|
||||
"userIp",
|
||||
"remoteIP",
|
||||
"userIP",
|
||||
"userAgent",
|
||||
"error",
|
||||
"details",
|
||||
@@ -135,19 +136,29 @@
|
||||
<tr>
|
||||
<td class="min-width txt-hint txt-bold">id</td>
|
||||
<td>
|
||||
<div class="label">
|
||||
<span class="txt">{log.id}</span>
|
||||
<div class="copy-icon-wrapper">
|
||||
<CopyIcon value={log.id} />
|
||||
<div class="txt">{log.id}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="min-width txt-hint txt-bold">level</td>
|
||||
<td><LogLevel level={log.level} /></td>
|
||||
<td>
|
||||
<LogLevel level={log.level} />
|
||||
<div class="copy-icon-wrapper">
|
||||
<CopyIcon value={log.level} />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="min-width txt-hint txt-bold">created</td>
|
||||
<td><LogDate date={log.created} /></td>
|
||||
<td>
|
||||
<LogDate date={log.created} />
|
||||
<div class="copy-icon-wrapper">
|
||||
<CopyIcon value={log.created} />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{#if !isRequest}
|
||||
<tr>
|
||||
@@ -155,6 +166,10 @@
|
||||
<td>
|
||||
{#if log.message}
|
||||
<span class="txt">{log.message}</span>
|
||||
|
||||
<div class="copy-icon-wrapper">
|
||||
<CopyIcon value={log.message} />
|
||||
</div>
|
||||
{:else}
|
||||
<span class="txt txt-hint">N/A</span>
|
||||
{/if}
|
||||
@@ -163,13 +178,14 @@
|
||||
{/if}
|
||||
{#each extractKeys(log.data) as key}
|
||||
{@const value = log.data[key]}
|
||||
{@const isJson = value !== null && typeof value == "object"}
|
||||
{@const isEmpty = CommonHelper.isEmpty(value)}
|
||||
{@const isJson = !isEmpty && value !== null && typeof value == "object"}
|
||||
<tr>
|
||||
<td class="min-width txt-hint txt-bold" class:v-align-top={isJson}>
|
||||
data.{key}
|
||||
</td>
|
||||
<td>
|
||||
{#if CommonHelper.isEmpty(value)}
|
||||
{#if isEmpty}
|
||||
<span class="txt txt-hint">N/A</span>
|
||||
{:else if isJson}
|
||||
<CodeBlock content={JSON.stringify(value, null, 2)} />
|
||||
@@ -184,6 +200,12 @@
|
||||
{value}{isRequest && key == "execTime" ? "ms" : ""}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if !isEmpty}
|
||||
<div class="copy-icon-wrapper">
|
||||
<CopyIcon {value} />
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
@@ -207,4 +229,17 @@
|
||||
.log-error-label {
|
||||
white-space: normal;
|
||||
}
|
||||
.copy-icon-wrapper {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 12px;
|
||||
opacity: 0;
|
||||
transition: opacity var(--baseAnimationSpeed);
|
||||
}
|
||||
tr:hover .copy-icon-wrapper {
|
||||
opacity: 1;
|
||||
}
|
||||
td:has(.copy-icon-wrapper) {
|
||||
padding-right: 30px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
Tooltip,
|
||||
} from "chart.js";
|
||||
import "chartjs-adapter-luxon";
|
||||
import zoomPlugin from "chartjs-plugin-zoom";
|
||||
|
||||
export let filter = "";
|
||||
export let zoom = {};
|
||||
export let presets = "";
|
||||
|
||||
let chartCanvas;
|
||||
@@ -23,6 +25,7 @@
|
||||
let chartData = [];
|
||||
let totalLogs = 0;
|
||||
let isLoading = false;
|
||||
let isZoomedOrPanned = false;
|
||||
|
||||
$: if (typeof filter !== "undefined" || typeof presets !== "undefined") {
|
||||
load();
|
||||
@@ -74,8 +77,13 @@
|
||||
totalLogs = 0;
|
||||
}
|
||||
|
||||
function resetZoom() {
|
||||
chartInst?.resetZoom();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
Chart.register(LineElement, PointElement, LineController, LinearScale, TimeScale, Filler, Tooltip);
|
||||
Chart.register(zoomPlugin);
|
||||
|
||||
chartInst = new Chart(chartCanvas, {
|
||||
type: "line",
|
||||
@@ -143,6 +151,41 @@
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
zoom: {
|
||||
enabled: true,
|
||||
zoom: {
|
||||
mode: "x",
|
||||
pinch: {
|
||||
enabled: true,
|
||||
},
|
||||
drag: {
|
||||
enabled: true,
|
||||
backgroundColor: "rgba(255, 99, 132, 0.2)",
|
||||
borderWidth: 0,
|
||||
threshold: 10,
|
||||
},
|
||||
limits: {
|
||||
x: { minRange: 100000000 },
|
||||
y: { minRange: 100000000 },
|
||||
},
|
||||
onZoomComplete: ({ chart }) => {
|
||||
isZoomedOrPanned = chart.isZoomedOrPanned();
|
||||
if (!isZoomedOrPanned) {
|
||||
if (zoom.min || zoom.max) {
|
||||
zoom = {}; // reset
|
||||
}
|
||||
} else {
|
||||
// trim minutes and seconds since the statistic is hourly based
|
||||
zoom.min =
|
||||
CommonHelper.formatToUTCDate(chart.scales.x.min, "yyyy-MM-dd HH") +
|
||||
":00:00.000Z";
|
||||
zoom.max =
|
||||
CommonHelper.formatToUTCDate(chart.scales.x.max, "yyyy-MM-dd HH") +
|
||||
":59:59.999Z";
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -156,10 +199,18 @@
|
||||
Found {totalLogs}
|
||||
{totalLogs == 1 ? "log" : "logs"}
|
||||
</div>
|
||||
|
||||
{#if isLoading}
|
||||
<div class="chart-loader loader" transition:scale={{ duration: 150 }} />
|
||||
{/if}
|
||||
<canvas bind:this={chartCanvas} class="chart-canvas" />
|
||||
|
||||
<canvas bind:this={chartCanvas} class="chart-canvas" on:dblclick={resetZoom} />
|
||||
|
||||
{#if isZoomedOrPanned}
|
||||
<button type="button" class="btn btn-secondary btn-sm btn-chart-zoom" on:click={resetZoom}>
|
||||
Reset zoom
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@@ -187,4 +238,9 @@
|
||||
font-size: var(--smFontSize);
|
||||
color: var(--txtHintColor);
|
||||
}
|
||||
.btn-chart-zoom {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { fly } from "svelte/transition";
|
||||
import Scroller from "@/components/base/Scroller.svelte";
|
||||
import SortHeader from "@/components/base/SortHeader.svelte";
|
||||
import LogDate from "@/components/logs/LogDate.svelte";
|
||||
import LogLevel from "@/components/logs/LogLevel.svelte";
|
||||
import ApiClient from "@/utils/ApiClient";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import SortHeader from "@/components/base/SortHeader.svelte";
|
||||
import Scroller from "@/components/base/Scroller.svelte";
|
||||
import LogLevel from "@/components/logs/LogLevel.svelte";
|
||||
import LogDate from "@/components/logs/LogDate.svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { fly } from "svelte/transition";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
|
||||
export let filter = "";
|
||||
export let presets = "";
|
||||
export let sort = "-rowid";
|
||||
export let zoom = {};
|
||||
export let sort = "-@rowid";
|
||||
|
||||
let logs = [];
|
||||
let currentPage = 1;
|
||||
@@ -23,7 +24,12 @@
|
||||
let yieldedId = 0;
|
||||
let bulkSelected = {};
|
||||
|
||||
$: if (typeof sort !== "undefined" || typeof filter !== "undefined" || typeof presets !== "undefined") {
|
||||
$: if (
|
||||
typeof sort !== "undefined" ||
|
||||
typeof filter !== "undefined" ||
|
||||
typeof presets !== "undefined" ||
|
||||
typeof zoom !== "undefined"
|
||||
) {
|
||||
clearList();
|
||||
load(1);
|
||||
}
|
||||
@@ -37,15 +43,17 @@
|
||||
export async function load(page = 1, breakTasks = true) {
|
||||
isLoading = true;
|
||||
|
||||
const normalizedFilter = [presets, CommonHelper.normalizeLogsFilter(filter)]
|
||||
.filter(Boolean)
|
||||
.join("&&");
|
||||
const normalizedFilter = [presets, CommonHelper.normalizeLogsFilter(filter)];
|
||||
|
||||
if (zoom.min && zoom.max) {
|
||||
normalizedFilter.push(`created >= "${zoom.min}" && created <= "${zoom.max}"`);
|
||||
}
|
||||
|
||||
return ApiClient.logs
|
||||
.getList(page, perPage, {
|
||||
sort: sort,
|
||||
skipTotal: 1,
|
||||
filter: normalizedFilter,
|
||||
filter: normalizedFilter.filter(Boolean).join("&&"),
|
||||
})
|
||||
.then(async (result) => {
|
||||
if (page <= 1) {
|
||||
@@ -174,7 +182,7 @@
|
||||
}
|
||||
|
||||
if (log.data.type == "request") {
|
||||
const requestKeys = ["status", "execTime", "auth", "userIp"];
|
||||
const requestKeys = ["status", "execTime", "auth", "authId", "userIP"];
|
||||
for (let key of requestKeys) {
|
||||
if (typeof log.data[key] != "undefined") {
|
||||
keys.push({ key });
|
||||
@@ -298,7 +306,7 @@
|
||||
{:else}
|
||||
{keyItem.key}: {CommonHelper.stringifyValue(
|
||||
log.data[keyItem.key],
|
||||
"-",
|
||||
"N/A",
|
||||
80,
|
||||
)}
|
||||
{/if}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import ApiClient from "@/utils/ApiClient";
|
||||
import { setErrors } from "@/stores/errors";
|
||||
import { addSuccessToast } from "@/stores/toasts";
|
||||
import tooltip from "@/actions/tooltip";
|
||||
import Field from "@/components/base/Field.svelte";
|
||||
import OverlayPanel from "@/components/base/OverlayPanel.svelte";
|
||||
import LogsLevelsInfo from "@/components/logs/LogsLevelsInfo.svelte";
|
||||
@@ -86,7 +85,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<OverlayPanel bind:this={panel} popup class="admin-panel" beforeHide={() => !isSaving} on:hide on:show>
|
||||
<OverlayPanel bind:this={panel} popup class="superuser-panel" beforeHide={() => !isSaving} on:hide on:show>
|
||||
<svelte:fragment slot="header">
|
||||
<h4>Logs settings</h4>
|
||||
</svelte:fragment>
|
||||
@@ -114,10 +113,15 @@
|
||||
</div>
|
||||
</Field>
|
||||
|
||||
<Field class="form-field form-field-toggle" name="logs.logIp" let:uniqueId>
|
||||
<input type="checkbox" id={uniqueId} bind:checked={formSettings.logs.logIp} />
|
||||
<Field class="form-field form-field-toggle" name="logs.logIP" let:uniqueId>
|
||||
<input type="checkbox" id={uniqueId} bind:checked={formSettings.logs.logIP} />
|
||||
<label for={uniqueId}>Enable IP logging</label>
|
||||
</Field>
|
||||
|
||||
<Field class="form-field form-field-toggle" name="logs.logAuthId" let:uniqueId>
|
||||
<input type="checkbox" id={uniqueId} bind:checked={formSettings.logs.logAuthId} />
|
||||
<label for={uniqueId}>Enable Auth Id logging</label>
|
||||
</Field>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
$pageTitle = "Logs";
|
||||
|
||||
const LOG_QUERY_KEY = "logId";
|
||||
const ADMIN_REQUESTS_QUERY_KEY = "adminRequests";
|
||||
const ADMIN_REQUESTS_STORAGE_KEY = "adminLogRequests";
|
||||
const ADMIN_REQUESTS_QUERY_KEY = "superuserRequests";
|
||||
const ADMIN_REQUESTS_STORAGE_KEY = "superuserLogRequests";
|
||||
|
||||
const initialQueryParams = new URLSearchParams($querystring);
|
||||
|
||||
@@ -25,20 +25,21 @@
|
||||
let logsSettingsPanel;
|
||||
let refreshKey = 1;
|
||||
let filter = initialQueryParams.get("filter") || "";
|
||||
let withAdminLogs =
|
||||
let zoom = {};
|
||||
let withSuperuserLogs =
|
||||
(initialQueryParams.get(ADMIN_REQUESTS_QUERY_KEY) ||
|
||||
window.localStorage?.getItem(ADMIN_REQUESTS_STORAGE_KEY)) << 0;
|
||||
let initialWithAdminLogs = withAdminLogs;
|
||||
let initialWithSuperuserLogs = withSuperuserLogs;
|
||||
|
||||
$: if (initialQueryParams.get(LOG_QUERY_KEY) && logViewPanel) {
|
||||
logViewPanel.show(initialQueryParams.get(LOG_QUERY_KEY));
|
||||
}
|
||||
|
||||
$: presets = !withAdminLogs ? 'data.auth!="admin"' : "";
|
||||
$: presets = !withSuperuserLogs ? 'data.auth!="_superusers"' : "";
|
||||
|
||||
$: if (initialWithAdminLogs != withAdminLogs) {
|
||||
initialWithAdminLogs = withAdminLogs;
|
||||
window.localStorage?.setItem(ADMIN_REQUESTS_STORAGE_KEY, withAdminLogs << 0);
|
||||
$: if (initialWithSuperuserLogs != withSuperuserLogs) {
|
||||
initialWithSuperuserLogs = withSuperuserLogs;
|
||||
window.localStorage?.setItem(ADMIN_REQUESTS_STORAGE_KEY, withSuperuserLogs << 0);
|
||||
updateQueryParams();
|
||||
}
|
||||
|
||||
@@ -53,7 +54,7 @@
|
||||
function updateQueryParams(extra = {}) {
|
||||
let queryParams = {};
|
||||
queryParams.filter = filter || null;
|
||||
queryParams[ADMIN_REQUESTS_QUERY_KEY] = withAdminLogs << 0 || null;
|
||||
queryParams[ADMIN_REQUESTS_QUERY_KEY] = withSuperuserLogs << 0 || null;
|
||||
CommonHelper.replaceHashQueryParams(Object.assign(queryParams, extra));
|
||||
}
|
||||
</script>
|
||||
@@ -81,8 +82,8 @@
|
||||
|
||||
<div class="inline-flex">
|
||||
<Field class="form-field form-field-toggle m-0" let:uniqueId>
|
||||
<input type="checkbox" id={uniqueId} bind:checked={withAdminLogs} />
|
||||
<label for={uniqueId}>Include requests by admins</label>
|
||||
<input type="checkbox" id={uniqueId} bind:checked={withSuperuserLogs} />
|
||||
<label for={uniqueId}>Include requests by superusers</label>
|
||||
</Field>
|
||||
</div>
|
||||
</header>
|
||||
@@ -96,12 +97,12 @@
|
||||
<LogsLevelsInfo class="block txt-sm txt-hint m-t-xs m-b-base" />
|
||||
|
||||
{#key refreshKey}
|
||||
<LogsChart {filter} {presets} />
|
||||
<LogsChart bind:zoom {filter} {presets} />
|
||||
{/key}
|
||||
</div>
|
||||
|
||||
{#key refreshKey}
|
||||
<LogsList bind:filter {presets} on:select={(e) => logViewPanel?.show(e?.detail)} />
|
||||
<LogsList bind:filter bind:zoom {presets} on:select={(e) => logViewPanel?.show(e?.detail)} />
|
||||
{/key}
|
||||
</PageWrapper>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user