diff --git a/.prettierrc b/.prettierrc
index 61cd7641..24ad1d9e 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -4,6 +4,10 @@
"trailingComma": "all",
"arrowParens": "always",
"singleQuote": true,
+ "semi": true,
+ "bracketSpacing": true,
+ "bracketSameLine": false,
+ "printWidth": 80,
"plugins": [
"prettier-plugin-tailwindcss",
"@prettier/plugin-ruby",
@@ -11,5 +15,13 @@
"@4az/prettier-plugin-html-erb"
],
"xmlQuoteAttributes": "double",
- "xmlWhitespaceSensitivity": "ignore"
+ "xmlWhitespaceSensitivity": "ignore",
+ "overrides": [
+ {
+ "files": ["*.ts", "*.tsx"],
+ "options": {
+ "parser": "typescript"
+ }
+ }
+ ]
}
diff --git a/app/javascript/bundles/Main/components/ListItem.tsx b/app/javascript/bundles/Main/components/ListItem.tsx
index 98ec684b..43217b40 100644
--- a/app/javascript/bundles/Main/components/ListItem.tsx
+++ b/app/javascript/bundles/Main/components/ListItem.tsx
@@ -29,11 +29,11 @@ export default function ListItem({
subtext,
domainIcon,
}: PropTypes) {
+ const groupHoverClassName = 'group-hover:text-slate-200';
const iconClassName = ['ml-2'];
const textClassName = [
COMMON_LIST_ELEM_CLASSES,
- 'relative flex items-center justify-between',
- 'border-t-0',
+ 'group flex items-center justify-between',
isLast && 'rounded-b-lg',
style === 'item' && selected && 'bg-slate-700 text-slate-100',
style === 'info' && 'text-slate-500 italic',
@@ -66,12 +66,21 @@ export default function ListItem({
{value}
{subtext && (
-
+
{subtext}
)}
{domainIcon && (
-

+

)}
diff --git a/app/javascript/bundles/Main/components/PostHoverPreviewWrapper.tsx b/app/javascript/bundles/Main/components/PostHoverPreviewWrapper.tsx
index c321dc2a..06500d66 100644
--- a/app/javascript/bundles/Main/components/PostHoverPreviewWrapper.tsx
+++ b/app/javascript/bundles/Main/components/PostHoverPreviewWrapper.tsx
@@ -44,9 +44,9 @@ export const PostHoverPreviewWrapper: React.FC<
className={anchorClassNamesForVisualStyle(visualStyle, true)}
>
{postDomainIcon && (
-
)}
diff --git a/app/javascript/bundles/Main/components/SortableTable.tsx b/app/javascript/bundles/Main/components/SortableTable.tsx
index 9a38f8d9..0c93feaf 100644
--- a/app/javascript/bundles/Main/components/SortableTable.tsx
+++ b/app/javascript/bundles/Main/components/SortableTable.tsx
@@ -44,7 +44,7 @@ export const SortableTable: React.FC = ({
};
const sortedData = React.useMemo(() => {
- const headerIndex = headers.findIndex(h => h.key === sortKey);
+ const headerIndex = headers.findIndex((h) => h.key === sortKey);
if (headerIndex === -1) return data;
return [...data].sort((a, b) => {
@@ -92,7 +92,13 @@ export const SortableTable: React.FC = ({
const getSortIndicator = (headerKey: string) => {
if (sortKey !== headerKey) {
- return ▼;
+ return (
+
+ ▼
+
+ );
}
return (
@@ -112,7 +118,9 @@ export const SortableTable: React.FC = ({
style={{
...headerStyle,
textAlign: header.align,
- ...(index === headers.length - 1 ? { borderRight: 'none' } : {}),
+ ...(index === headers.length - 1
+ ? { borderRight: 'none' }
+ : {}),
}}
onClick={() => handleSort(header.key)}
onMouseEnter={(e) => {
@@ -130,7 +138,7 @@ export const SortableTable: React.FC = ({
{/* Data Rows */}
{sortedData.map((row) => (
-
+
{row.cells.map((cell, cellIndex) => (
= ({
fontSize: '0.875rem',
fontWeight: cellIndex === 0 ? 500 : 400,
color: cellIndex === 0 ? '#0f172a' : '#64748b',
- ...(cellIndex === row.cells.length - 1 ? { borderRight: 'none' } : {}),
+ ...(cellIndex === row.cells.length - 1
+ ? { borderRight: 'none' }
+ : {}),
}}
- className="group-hover:bg-slate-50 transition-colors duration-150"
+ className="transition-colors duration-150 group-hover:bg-slate-50"
>
{cell.value}
diff --git a/app/javascript/bundles/Main/components/StatsCard.tsx b/app/javascript/bundles/Main/components/StatsCard.tsx
index 85cb99d7..04f761fb 100644
--- a/app/javascript/bundles/Main/components/StatsCard.tsx
+++ b/app/javascript/bundles/Main/components/StatsCard.tsx
@@ -28,7 +28,8 @@ export const StatsCard: React.FC
= ({
{requestCount} requests
- {' '}in last {timeWindow}
+ {' '}
+ in last {timeWindow}
diff --git a/app/javascript/bundles/Main/components/StatsPage.tsx b/app/javascript/bundles/Main/components/StatsPage.tsx
index ca3fb138..6196fa05 100644
--- a/app/javascript/bundles/Main/components/StatsPage.tsx
+++ b/app/javascript/bundles/Main/components/StatsPage.tsx
@@ -42,117 +42,137 @@ export const StatsPage: React.FC
= ({
byDomainCounts,
availableTimeWindows,
}) => {
- const contentTypeData: TableData[] = contentTypeCounts.map(item => ({
+ const contentTypeData: TableData[] = contentTypeCounts.map((item) => ({
id: item.content_type,
cells: [
{ value: item.content_type, sortKey: item.content_type },
{ value: item.countFormatted, sortKey: item.count },
- { value: item.bytesFormatted, sortKey: item.bytes }
- ]
+ { value: item.bytesFormatted, sortKey: item.bytes },
+ ],
}));
- const domainData: TableData[] = byDomainCounts.map(item => ({
+ const domainData: TableData[] = byDomainCounts.map((item) => ({
id: item.domain,
cells: [
{ value: item.domain, sortKey: item.domain },
{ value: item.countFormatted, sortKey: item.count },
- { value: item.bytesFormatted, sortKey: item.bytes }
- ]
+ { value: item.bytesFormatted, sortKey: item.bytes },
+ ],
}));
return (
-
+
{/* Header Section */}
-
+
{/* Top Bar */}
-
+
{/* Stats Summary */}
-
-
-
Summary for {timeWindowFormatted}
+
+
+
+ Summary for {timeWindowFormatted}
+
-
+
{/* Total Requests */}
-
+
-
Total Requests
-
{lastWindowCount.toLocaleString()}
+
+ Total Requests
+
+
+ {lastWindowCount.toLocaleString()}
+
+
+
+
-
-
-
{/* Requests per Second */}
-
+
-
Requests/sec
-
{requestsPerSecond}
+
+ Requests/sec
+
+
+ {requestsPerSecond}
+
+
+
+
-
-
-
{/* Total Data */}
-
+
-
Total Data
-
{totalBytesFormatted}
+
+ Total Data
+
+
+ {totalBytesFormatted}
+
+
+
+
-
-
-
{/* Data per Second */}
-
+
-
Data/sec
-
{bytesPerSecondFormatted}
+
+ Data/sec
+
+
+ {bytesPerSecondFormatted}
+
+
+
+
-
-
-
{/* Time Window Selector */}
-
+
-
+
{availableTimeWindows.map((timeWindowOption, index) => (
{timeWindowOption.active ? (
-
+
{timeWindowOption.label}
) : (
{timeWindowOption.label}
@@ -165,14 +185,16 @@ export const StatsPage: React.FC = ({
{/* Tables Grid - 2 columns */}
-
+
-
By Content Type
+
+ By Content Type
+
= ({
-
By Domain
+
By Domain
;
-type TrieNodeType = TrieNode;
-
export default function UserSearchBar({ isServerRendered }: PropTypes) {
isServerRendered = !!isServerRendered;
const [pendingRequest, setPendingRequest] = useState(
@@ -119,7 +114,6 @@ export default function UserSearchBar({ isServerRendered }: PropTypes) {
}
} catch (err) {
if (!err.message.includes('aborted')) {
- log.error('error loading user trie: ', err);
setState((s) => ({
...s,
errorMessage: `error loading users: ` + err.message,
@@ -148,7 +142,6 @@ export default function UserSearchBar({ isServerRendered }: PropTypes) {
const searchForUserDebounced = useCallback(
debounce(async (userName) => {
- log.info('sending search for ', userName);
setState((s) => ({ ...s, typingSettled: true }));
searchForUser(userName);
}, 250),
@@ -158,7 +151,6 @@ export default function UserSearchBar({ isServerRendered }: PropTypes) {
function invokeIdx(idx) {
const user = state.userList[idx];
if (user) {
- log.info('selecting user: ', user);
setState((s) => ({ ...s, userName: user.name }));
inputRef.current.value = user.name;
window.location.href = user.show_path;
@@ -185,9 +177,7 @@ export default function UserSearchBar({ isServerRendered }: PropTypes) {
function UserSearchBarItems() {
return (
-
+
{visibility.error ? (
-
+ {anyShown && (
+
+
+
+ )}
);
}
diff --git a/app/javascript/bundles/Main/lib/Trie.ts b/app/javascript/bundles/Main/lib/Trie.ts
deleted file mode 100644
index 707c4ad0..00000000
--- a/app/javascript/bundles/Main/lib/Trie.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-interface SerializedTrie
{
- // terminal node?
- t: 1 | 0;
- // value of the node
- v: T;
- // optional children
- c?: { [s: string]: SerializedTrie };
-}
-
-export class TrieNode {
- public terminal: boolean;
- public value: T;
- public children: Map>;
- public serialized: SerializedTrie;
-
- constructor(ser: SerializedTrie) {
- this.terminal = ser.t == 1;
- this.value = ser.v;
- this.children = new Map();
- this.serialized = ser;
-
- if (ser.c != null) {
- for (const [key, value] of Object.entries(ser.c)) {
- this.children.set(key, new TrieNode(value));
- }
- }
- }
-}
-
-export default class Trie {
- public root: TrieNode;
- constructor(ser: SerializedTrie) {
- this.root = new TrieNode(ser);
- }
-
- public nodeForPrefix(key: string): {
- chain: string[];
- node: TrieNode | null;
- } {
- let chain = [];
- let node = this.root;
- let remaining = key;
- while (node && remaining.length > 0) {
- let exactChild = null;
- console.log('remaining: ', remaining);
-
- for (const [childKey, child] of node.children.entries()) {
- if (remaining.startsWith(childKey)) {
- console.log('exact match for: ', childKey);
- exactChild = child;
- chain.push(childKey);
- remaining = remaining.slice(childKey.length);
- break;
- }
- }
-
- // if an exact match was found, continue iterating
- if (exactChild) {
- node = exactChild;
- continue;
- }
-
- console.log('looking for partial match for ', remaining);
- for (const [childKey, child] of node.children.entries()) {
- const startsWith = childKey.startsWith(remaining);
- console.log(
- 'test ',
- childKey,
- ' against ',
- remaining,
- ': ',
- startsWith,
- ' ',
- child.serialized,
- );
- if (startsWith) {
- console.log('partial match for: ', remaining, ': ', child.serialized);
- chain.push(childKey);
- return { chain, node: child };
- }
- }
-
- console.log('did not find partial, bailing!');
- return { chain, node: null };
- }
-
- // // return remaining.length === 0 && node && node.terminal ? node : null;
- console.log('returning child ', node, ' for remaining ', remaining);
- return { chain, node };
- }
-}
diff --git a/app/javascript/server/buildUsersTrie.js b/app/javascript/server/buildUsersTrie.js
deleted file mode 100644
index 08b36b93..00000000
--- a/app/javascript/server/buildUsersTrie.js
+++ /dev/null
@@ -1,140 +0,0 @@
-function buildUsersTrie(users) {
- const rootNode = new trie();
- users.forEach(([id, name]) => {
- rootNode.insert(name.toLowerCase(), [id, name]);
- });
- return JSON.stringify(rootNode.serialize());
-}
-class trie_node {
- constructor() {
- this.terminal = false;
- this.children = new Map();
- }
- serialize() {
- const { terminal, value, children } = this;
- let mapped = {};
- let numChildren = 0;
- Object.keys(Object.fromEntries(children)).forEach((childKey) => {
- numChildren += 1;
- mapped[childKey] = children.get(childKey).serialize();
- });
- return {
- t: this.terminal ? 1 : 0,
- v: value,
- c: numChildren > 0 ? mapped : undefined,
- };
- }
-}
-class trie {
- constructor() {
- this.root = new trie_node();
- this.elements = 0;
- }
- serialize() {
- return this.root.serialize();
- }
- get length() {
- return this.elements;
- }
- get(key) {
- const node = this.getNode(key);
- if (node) {
- return node.value;
- }
- return null;
- }
- contains(key) {
- const node = this.getNode(key);
- return !!node;
- }
- insert(key, value) {
- let node = this.root;
- let remaining = key;
- while (remaining.length > 0) {
- let child = null;
- for (const childKey of node.children.keys()) {
- const prefix = this.commonPrefix(remaining, childKey);
- if (!prefix.length) {
- continue;
- }
- if (prefix.length === childKey.length) {
- // enter child node
- child = node.children.get(childKey);
- remaining = remaining.slice(childKey.length);
- break;
- }
- else {
- // split the child
- child = new trie_node();
- child.children.set(childKey.slice(prefix.length), node.children.get(childKey));
- node.children.delete(childKey);
- node.children.set(prefix, child);
- remaining = remaining.slice(prefix.length);
- break;
- }
- }
- if (!child && remaining.length) {
- child = new trie_node();
- node.children.set(remaining, child);
- remaining = "";
- }
- node = child;
- }
- if (!node.terminal) {
- node.terminal = true;
- this.elements += 1;
- }
- node.value = value;
- }
- remove(key) {
- const node = this.getNode(key);
- if (node) {
- node.terminal = false;
- this.elements -= 1;
- }
- }
- map(prefix, func) {
- const mapped = [];
- const node = this.getNode(prefix);
- const stack = [];
- if (node) {
- stack.push([prefix, node]);
- }
- while (stack.length) {
- const [key, node] = stack.pop();
- if (node.terminal) {
- mapped.push(func(key, node.value));
- }
- for (const c of node.children.keys()) {
- stack.push([key + c, node.children.get(c)]);
- }
- }
- return mapped;
- }
- getNode(key) {
- let node = this.root;
- let remaining = key;
- while (node && remaining.length > 0) {
- let child = null;
- for (let i = 1; i <= remaining.length; i += 1) {
- child = node.children.get(remaining.slice(0, i));
- if (child) {
- remaining = remaining.slice(i);
- break;
- }
- }
- node = child;
- }
- return remaining.length === 0 && node && node.terminal ? node : null;
- }
- commonPrefix(a, b) {
- const shortest = Math.min(a.length, b.length);
- let i = 0;
- for (; i < shortest; i += 1) {
- if (a[i] !== b[i]) {
- break;
- }
- }
- return a.slice(0, i);
- }
-}
diff --git a/app/javascript/server/buildUsersTrie.ts b/app/javascript/server/buildUsersTrie.ts
deleted file mode 100644
index f39b1474..00000000
--- a/app/javascript/server/buildUsersTrie.ts
+++ /dev/null
@@ -1,163 +0,0 @@
-type UserRow = [number, string];
-
-function buildUsersTrie(users: UserRow[]): string {
- const rootNode = new trie<[number, string]>();
- users.forEach(([id, name]) => {
- rootNode.insert(name.toLowerCase(), [id, name]);
- });
- return JSON.stringify(rootNode.serialize());
-}
-
-class trie_node {
- public terminal: boolean;
- public value: T;
- public children: Map>;
-
- constructor() {
- this.terminal = false;
- this.children = new Map();
- }
-
- public serialize(): Object {
- const { terminal, value, children } = this;
- let mapped = {};
- let numChildren = 0;
- Object.keys(Object.fromEntries(children)).forEach((childKey) => {
- numChildren += 1;
- mapped[childKey] = children.get(childKey).serialize();
- });
- return {
- t: this.terminal ? 1 : 0,
- v: value,
- c: numChildren > 0 ? mapped : undefined,
- };
- }
-}
-
-class trie {
- public root: trie_node;
- public elements: number;
-
- constructor() {
- this.root = new trie_node();
- this.elements = 0;
- }
-
- public serialize(): Object {
- return this.root.serialize();
- }
-
- public get length(): number {
- return this.elements;
- }
-
- public get(key: string): T | null {
- const node = this.getNode(key);
- if (node) {
- return node.value;
- }
- return null;
- }
-
- public contains(key: string): boolean {
- const node = this.getNode(key);
- return !!node;
- }
-
- public insert(key: string, value: T): void {
- let node = this.root;
- let remaining = key;
- while (remaining.length > 0) {
- let child: trie_node = null;
- for (const childKey of node.children.keys()) {
- const prefix = this.commonPrefix(remaining, childKey);
- if (!prefix.length) {
- continue;
- }
- if (prefix.length === childKey.length) {
- // enter child node
- child = node.children.get(childKey);
- remaining = remaining.slice(childKey.length);
- break;
- } else {
- // split the child
- child = new trie_node();
- child.children.set(
- childKey.slice(prefix.length),
- node.children.get(childKey)
- );
- node.children.delete(childKey);
- node.children.set(prefix, child);
- remaining = remaining.slice(prefix.length);
- break;
- }
- }
- if (!child && remaining.length) {
- child = new trie_node();
- node.children.set(remaining, child);
- remaining = "";
- }
- node = child;
- }
- if (!node.terminal) {
- node.terminal = true;
- this.elements += 1;
- }
- node.value = value;
- }
-
- public remove(key: string): void {
- const node = this.getNode(key);
- if (node) {
- node.terminal = false;
- this.elements -= 1;
- }
- }
-
- public map(prefix: string, func: (key: string, value: T) => U): U[] {
- const mapped = [];
- const node = this.getNode(prefix);
- const stack: [string, trie_node][] = [];
- if (node) {
- stack.push([prefix, node]);
- }
- while (stack.length) {
- const [key, node] = stack.pop();
- if (node.terminal) {
- mapped.push(func(key, node.value));
- }
- for (const c of node.children.keys()) {
- stack.push([key + c, node.children.get(c)]);
- }
- }
- return mapped;
- }
-
- private getNode(key: string): trie_node | null {
- let node = this.root;
- let remaining = key;
- while (node && remaining.length > 0) {
- let child = null;
- for (let i = 1; i <= remaining.length; i += 1) {
- child = node.children.get(remaining.slice(0, i));
- if (child) {
- remaining = remaining.slice(i);
- break;
- }
- }
- node = child;
- }
- return remaining.length === 0 && node && node.terminal ? node : null;
- }
-
- private commonPrefix(a: string, b: string): string {
- const shortest = Math.min(a.length, b.length);
- let i = 0;
- for (; i < shortest; i += 1) {
- if (a[i] !== b[i]) {
- break;
- }
- }
- return a.slice(0, i);
- }
-}