Skip to content

Commit

Permalink
webapp/preferences: allow user to choose UTC or local time
Browse files Browse the repository at this point in the history
Closes #161.
  • Loading branch information
jasonish committed Jan 2, 2025
1 parent aebedab commit be59219
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 16 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
Elasticsearch were being used. This increases compatiblity with
OpenSearch as its used by ClearNDR (formerly SELKS).
- [eve2pcap] Use SID as filename when available.
- [webapp] Allow user to choose local time or UTC time:
https://github.com/jasonish/evebox/issues/161

## 0.19.0 - 2024-12-13

Expand Down
4 changes: 3 additions & 1 deletion webapp/src/EventView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { createStore } from "solid-js/store";
import { BiInfoCircle } from "./icons";
import { SearchLink } from "./common/SearchLink";
import * as api from "./api";
import { FormattedTimestamp } from "./components";

const PCAP_BUTTON_STYLE =
"--bs-btn-padding-y: .1rem; --bs-btn-padding-x: .2rem; --bs-btn-font-size: .7rem;";
Expand Down Expand Up @@ -258,10 +259,11 @@ export function EventView() {
commonDetails.push([
"Timestamp",
<SearchLink field={"timestamp"} value={source.timestamp}>
{formatTimestamp(source.timestamp)}
<FormattedTimestamp timestamp={source.timestamp} withMillis={true} />
</SearchLink>,
]);
}

if (source.host) {
commonDetails.push(["Sensor", source.host]);
}
Expand Down
69 changes: 61 additions & 8 deletions webapp/src/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,27 @@ import {
setTheme,
setViewSize,
} from "./settings";
import { getClientPreferences, saveClientPreferences } from "./preferences";

export function Settings() {
return (
<>
<Top />
<Container class={"mt-2"}>
<Row>
<Col></Col>
<Col md={8} sm={12} lg={6}>
<div class="row">
<div class="col"></div>
<div class="col-sm-12 col-md-8 col-lg-6">
<Alert variant={"info"}>
Note: Settings are stored client side and will not be reflected on
other computers or in other browsers.
</Alert>
</div>
<div class="col"></div>
</div>

<div class="row">
<div class="col"></div>
<div class="col-sm-12 col-md-8 col-lg-6">
<div class={"row form-group"}>
<label class="col-md-4 col-form-label">Theme</label>
<div class="col-md-8">
Expand All @@ -42,9 +49,13 @@ export function Settings() {
</select>
</div>
</div>
</div>
<div class="col"></div>
</div>

<br />

<div class="row mt-2">
<div class="col"></div>
<div class="col-sm-12 col-md-8 col-lg-6">
<div class={"row form-group"}>
<label class="col-md-4 col-form-label">View Size</label>
<div class="col-md-8">
Expand Down Expand Up @@ -73,9 +84,51 @@ export function Settings() {
</select>
</div>
</div>
</Col>
<Col></Col>
</Row>
</div>
<div class="col"></div>
</div>

<div class="row mt-2">
<div class="col"></div>
<div class="col-sm-12 col-md-8 col-lg-6">
<div class={"row form-group"}>
<label class="col-md-4 col-form-label">Timestamp Format</label>
<div class="col-md-8">
<select
class="form-select"
onchange={(e) => {
let prefs = getClientPreferences();
switch (e.currentTarget.value) {
case "utc":
prefs.timestamp_format = "utc";
break;
default:
prefs.timestamp_format = undefined;
break;
}
saveClientPreferences(prefs);
}}
>
<option
value="local"
selected={
getClientPreferences().timestamp_format === "local"
}
>
Local
</option>
<option
value="utc"
selected={getClientPreferences().timestamp_format === "utc"}
>
UTC
</option>
</select>
</div>
</div>
</div>
<div class="col"></div>
</div>
</Container>
</>
);
Expand Down
46 changes: 39 additions & 7 deletions webapp/src/components.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// SPDX-FileCopyrightText: (C) 2023 Jason Ish <jason@codemonkey.net>
// SPDX-License-Identifier: MIT

import { For, Show } from "solid-js";
import { For, Show, createMemo } from "solid-js";
import { SearchLink } from "./common/SearchLink";
import { parse_timestamp } from "./datetime";
import { EventSource } from "./types";
import { formatAddress } from "./formatters";
import { BiDashCircle, BiFilter, BiPlusCircle } from "./icons";
import { PREFS } from "./preferences";
import dayjs from "dayjs";

// Creates a table where the first column is a count, and the second
// column is value.
Expand Down Expand Up @@ -221,17 +223,47 @@ export function FilterStrip(props: { filters: any; setFilters: any }) {
);
}

export function FormattedTimestamp(props: {
timestamp: string;
withMillis?: boolean;
}) {
const timestamp = createMemo(() => {
return parse_timestamp(props.timestamp);
});

const formatted = createMemo(() => {
let formatString = "YYYY-MM-DD HH:mm:ss";
if (props.withMillis === true) {
formatString += ".SSS";
}

if (PREFS().timestamp_format === "utc") {
return timestamp().utc().format(formatString) + "Z";
} else {
return timestamp().format(formatString);
}
});

return <>{formatted}</>;
}

export function TimestampCell(props: {
timestamp: string;
addFilter?: (what: string, op: string, value: string) => void;
}) {
let timestamp = parse_timestamp(props.timestamp);
let formatted = timestamp.format("YYYY-MM-DD HH:mm:ss");
const timestamp = createMemo(() => {
return parse_timestamp(props.timestamp);
});

const formatted = createMemo(() => {
return <FormattedTimestamp timestamp={props.timestamp} />;
});

return (
<div title={props.timestamp}>
{timestamp.format("YYYY-MM-DD HH:mm:ss")}
{formatted()}
<br />
<span class={"small"}>{timestamp.fromNow()}</span>{" "}
<span class={"small"}>{timestamp().fromNow()}</span>{" "}
<Show when={props.addFilter}>
<span class="dropdown" onclick={(e) => e.stopPropagation()}>
<span data-bs-toggle="dropdown">
Expand All @@ -245,7 +277,7 @@ export function TimestampCell(props: {
props.addFilter!("@from", "", props.timestamp);
}}
>
Filter for from {formatted}
Filter for from {formatted()}
</a>
</li>
<li>
Expand All @@ -255,7 +287,7 @@ export function TimestampCell(props: {
props.addFilter!("@to", "", props.timestamp);
}}
>
Filter for to {formatted}
Filter for to {formatted()}
</a>
</li>
</ul>
Expand Down
2 changes: 2 additions & 0 deletions webapp/src/datetime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

import relativeTime from "dayjs/plugin/relativeTime";
import duration, { DurationUnitType } from "dayjs/plugin/duration";
import utc from "dayjs/plugin/utc";
import dayjs from "dayjs";

dayjs.extend(relativeTime);
dayjs.extend(duration);
dayjs.extend(utc);

export function parse_timestamp(timestamp: string): dayjs.Dayjs {
return dayjs(timestamp);
Expand Down
39 changes: 39 additions & 0 deletions webapp/src/preferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: (C) 2025 Jason Ish <jason@codemonkey.net>
// SPDX-License-Identifier: MIT

import { createSignal } from "solid-js";

export const [PREFS, SET_PREFS] = createSignal<ClientPreferences>(
getClientPreferences()
);

export interface ClientPreferences {
timestamp_format?: "utc" | "local";
}

export function createDefaultClientPreferences(): ClientPreferences {
return {
timestamp_format: "local",
};
}

export function getClientPreferences(): ClientPreferences {
let prefs: any = localStorage.getItem("clientPreferences");
if (!prefs) {
console.log("Did not find localStorage clientPreferences, return defaults");
return createDefaultClientPreferences();
}
try {
prefs = JSON.parse(prefs) as ClientPreferences;
// Merge in defaults.
prefs = { ...createDefaultClientPreferences(), ...prefs };
return prefs;
} catch (e) {
return createDefaultClientPreferences();
}
}

export function saveClientPreferences(prefs: ClientPreferences) {
localStorage.setItem("clientPreferences", JSON.stringify(prefs));
SET_PREFS(prefs);
}

0 comments on commit be59219

Please sign in to comment.