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 && ( - domain icon + domain icon )}
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 && ( - {postThumbnailAlt} )} 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 */} -
+
-

HTTP Request Analytics

- - - Back to Log Entries - +

+ HTTP Request Analytics +

+ + + Back to Log Entries +
{/* 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); - } -}