From 0c4965c403b0088cab1bc476d53db0f0684b5b7a Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 31 May 2023 02:10:01 -0500 Subject: [PATCH 1/5] feat: basic transaction search UI - extremely simple static web server - UI uses preact, htm - fuse.js for search no localStorage etc. --- packages/tx-ui1/package.json | 33 +++++++++ packages/tx-ui1/server/server.js | 38 ++++++++++ packages/tx-ui1/ui/index.html | 23 ++++++ packages/tx-ui1/ui/report.css | 49 +++++++++++++ packages/tx-ui1/ui/txUi.js | 120 +++++++++++++++++++++++++++++++ 5 files changed, 263 insertions(+) create mode 100644 packages/tx-ui1/package.json create mode 100644 packages/tx-ui1/server/server.js create mode 100644 packages/tx-ui1/ui/index.html create mode 100644 packages/tx-ui1/ui/report.css create mode 100644 packages/tx-ui1/ui/txUi.js diff --git a/packages/tx-ui1/package.json b/packages/tx-ui1/package.json new file mode 100644 index 0000000..182f2a4 --- /dev/null +++ b/packages/tx-ui1/package.json @@ -0,0 +1,33 @@ +{ + "scripts": { + "start": "node server/server.js" + }, + "dependencies": {}, + "type": "module", + "devDependencies": { + "@agoric/eslint-config": "^0.3.3", + "@agoric/eslint-plugin": "^0.2.3", + "@endo/eslint-config": "^0.5.1", + "@types/better-sqlite3": "^7.6.1", + "@types/node": "^14.14.7", + "@typescript-eslint/parser": "^4.21.0", + "eslint": "^7.24.0", + "eslint-config-airbnb-base": "^14.2.1", + "eslint-config-prettier": "^8.1.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jsdoc": "^32.3.0", + "eslint-plugin-prettier": "^3.3.1", + "fuse.js": "^6.6.2", + "typescript": "^4.8.4" + }, + "eslintConfig": { + "extends": [ + "@agoric" + ] + }, + "prettier": { + "trailingComma": "all", + "arrowParens": "avoid", + "singleQuote": true + } +} diff --git a/packages/tx-ui1/server/server.js b/packages/tx-ui1/server/server.js new file mode 100644 index 0000000..263e7f5 --- /dev/null +++ b/packages/tx-ui1/server/server.js @@ -0,0 +1,38 @@ +// @ts-check + +const pages = { + // package-relative + '/': { path: './ui/index.html', type: 'text/html' }, + '/txUi.js': { path: './ui/txUi.js', type: 'application/javascript' }, + '/report.css': { path: './ui/report.css', type: 'text/css' }, + '/txs': { path: './data/split_detail.json', type: 'application/json' }, +}; + +/** + * @param {object} io + * @param {typeof import('http')} io.http + * @param {typeof import('fs')} io.fs TODO: narrow to least authority + */ +const main = async ({ http, fs }) => { + const srv = http.createServer((request, response) => { + const { url } = request; + console.log('@@request for', url); + const page = pages[url]; + if (!page) { + response.writeHead(404, { 'content-type': 'text/plain' }); + response.end(); + return; + } + fs.promises.readFile(page.path, 'utf8').then(body => { + response.writeHead(200, { 'content-type': page.type }); + response.write(body); + response.end(); + }); + }); + srv.listen(8822); +}; + +(async () => + main({ http: await import('http'), fs: await import('fs') }))().catch(err => + console.error(err), +); diff --git a/packages/tx-ui1/ui/index.html b/packages/tx-ui1/ui/index.html new file mode 100644 index 0000000..a25c9bd --- /dev/null +++ b/packages/tx-ui1/ui/index.html @@ -0,0 +1,23 @@ + + Transaction Search + + + + + + + diff --git a/packages/tx-ui1/ui/report.css b/packages/tx-ui1/ui/report.css new file mode 100644 index 0000000..43a1e83 --- /dev/null +++ b/packages/tx-ui1/ui/report.css @@ -0,0 +1,49 @@ +.report { + border-collapse: collapse; + font-family: sans-serif; +} +th, +td { + border: 1px solid black; + padding: 4px; +} +.report tr:nth-child(odd) { + background-color: #fff; +} + +.report tr:nth-child(even) { + background-color: #eee; +} + +.report td.amount { + text-align: right; +} +.report td.amount:before { + content: '$'; +} + +.report td.status { + text-align: center; +} +.report td.status span { + visibility: hidden; +} +.report td.uncleared:before { + content: '?'; +} +.report td.cleared:before { + content: '✓'; +} +.report td.recurring:before { + content: '♲'; + color: green; +} +.report td.recurring_suggested:before { + content: '♲?'; + color: grey; +} + +.report td.uncleared { + font-style: italic; + color: red; +} diff --git a/packages/tx-ui1/ui/txUi.js b/packages/tx-ui1/ui/txUi.js new file mode 100644 index 0000000..1345dbb --- /dev/null +++ b/packages/tx-ui1/ui/txUi.js @@ -0,0 +1,120 @@ +// @ts-check +import { + html, + Component, + useState, +} from 'https://unpkg.com/htm/preact/standalone.module.js'; +import Fuse from 'https://cdn.jsdelivr.net/npm/fuse.js@6.6.2/dist/fuse.esm.js'; + +console.log('@@txUi module'); + +/** + * @param {object} io + * @param { typeof document } io.document + * @param { typeof fetch } io.fetch + * @param { typeof localStorage } io.localStorage + * @param { typeof import('fuse.js').default } io.Fuse + */ +export const main = ({ fetch, document, localStorage, Fuse }) => { + console.log('@@main'); + document.addEventListener('DOMContentLoaded', ev => { + console.log('content ready@@'); + + fetch('/txs').then(resp => { + if (resp.status !== 200) { + throw resp; + } + resp.text().then(txt => { + console.log('got text@@', txt.slice(0, 20)); + const lines = txt.split('\n').filter(line => line > ''); + console.log(lines[0]); + }); + }); + }); +}; + +const opts = { + keys: [ + 'date', + 'num', + 'description', + { name: 'memo', getFn: tx => tx.splits.map(s => s.memo).join(' ') }, + { name: 'value', getFn: tx => tx.splits.map(s => `${s.value}`).join(' ') }, + ], + threshold: 0.3, + minMatchCharLength: 3, +}; + +export const TxRegister = () => { + const [items, setItems] = useState([]); + const [fuse, setFuse] = useState(new Fuse([], opts)); + const [pattern, setPattern] = useState(''); + const [matches, setMatches] = useState([]); + + const load = async () => { + const resp = await fetch('/txs'); + if (resp.status !== 200) { + throw resp; + } + const txt = await resp.text(); + console.log('got text@@', txt.slice(0, 20)); + const lines = txt.split('\n').filter(line => line > ''); + console.log(lines[0]); + const txs = lines.map(line => JSON.parse(line)); + setItems(txs); + setFuse(new Fuse(txs, opts)); + }; + + const search = () => { + console.log('@@search', pattern); + const hits = fuse.search(pattern); + setMatches(hits); + }; + + return html` +
e.preventDefault()}> +
+ Transaction Search + setPattern(e.target.value)} /> + +
+ +
Transactions loaded: ${items.length}
+
+
+ + + + + + + + + + + + ${matches.map(match => { + const tx = match.item; + return html` + + + + + + ${tx.splits.map(s => { + return html` + + + + + + + + `; + })} + `; + })} + +
DateNumDescriptionCodeAccountAmount
${tx.date}${tx.num}${tx.description}
${s.memo}${s.code}${s.account}${s.value}
+ `; +}; From b960454611289d1e73a18ed150cd811131338b15 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Sun, 16 Jul 2023 17:36:50 -0500 Subject: [PATCH 2/5] WIP: tx search --- packages/tx-ui1/ui/txUi.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/tx-ui1/ui/txUi.js b/packages/tx-ui1/ui/txUi.js index 1345dbb..d05df94 100644 --- a/packages/tx-ui1/ui/txUi.js +++ b/packages/tx-ui1/ui/txUi.js @@ -1,7 +1,7 @@ // @ts-check import { html, - Component, + useEffect, useState, } from 'https://unpkg.com/htm/preact/standalone.module.js'; import Fuse from 'https://cdn.jsdelivr.net/npm/fuse.js@6.6.2/dist/fuse.esm.js'; @@ -51,18 +51,30 @@ export const TxRegister = () => { const [pattern, setPattern] = useState(''); const [matches, setMatches] = useState([]); + console.warn('AMBIENT: window.localStorage'); + navigator.storage.estimate().then(est => console.log(est)); + const { localStorage } = window; + + useEffect(() => { + const found = localStorage.getItem('/txs'); + if (!found) return; + const lines = found.split('\n').filter(line => line > ''); + setItems(lines); + }); + const load = async () => { const resp = await fetch('/txs'); if (resp.status !== 200) { throw resp; } const txt = await resp.text(); - console.log('got text@@', txt.slice(0, 20)); + console.log('got text@@', txt.length, txt.slice(0, 20)); const lines = txt.split('\n').filter(line => line > ''); console.log(lines[0]); const txs = lines.map(line => JSON.parse(line)); setItems(txs); setFuse(new Fuse(txs, opts)); + localStorage.setItem('/txs', txt); }; const search = () => { From 8b882b2b663377911a30001036df5f1176d2162a Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Oct 2023 16:38:44 -0500 Subject: [PATCH 3/5] chore: use conventional port for transaction search to match https://github.com/eugene-khyst/letsencrypt-docker-compose --- packages/tx-ui1/server/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tx-ui1/server/server.js b/packages/tx-ui1/server/server.js index 263e7f5..c2ea57f 100644 --- a/packages/tx-ui1/server/server.js +++ b/packages/tx-ui1/server/server.js @@ -29,7 +29,7 @@ const main = async ({ http, fs }) => { response.end(); }); }); - srv.listen(8822); + srv.listen(8080); }; (async () => From 8fa6e8bfa972ff95711b73163d112fda09163cbc Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Oct 2023 16:52:09 -0500 Subject: [PATCH 4/5] chore(tx-ui1): copy Dockerfile from letsencrypt-docker-compose $ git log10 examples/nodejs-backend/Dockerfile 2023-03-07 21:48 d38618a Version 2 (#36) --- packages/tx-ui1/Dockerfile | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 packages/tx-ui1/Dockerfile diff --git a/packages/tx-ui1/Dockerfile b/packages/tx-ui1/Dockerfile new file mode 100644 index 0000000..9d65a96 --- /dev/null +++ b/packages/tx-ui1/Dockerfile @@ -0,0 +1,8 @@ +FROM node:18-alpine +WORKDIR /usr/src/app +COPY package*.json ./ +RUN npm ci --only=production +COPY . . +EXPOSE 8080 +ENV NODE_ENV=production +CMD ["node", "server.js"] \ No newline at end of file From 8664ced82b2cd93b8ab441459c3ac6b78b777f90 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Oct 2023 16:54:06 -0500 Subject: [PATCH 5/5] build(tx-ui1): tweak Dockerfile - npm -> yarn - server.js is down a level --- packages/tx-ui1/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tx-ui1/Dockerfile b/packages/tx-ui1/Dockerfile index 9d65a96..89e0521 100644 --- a/packages/tx-ui1/Dockerfile +++ b/packages/tx-ui1/Dockerfile @@ -1,8 +1,8 @@ FROM node:18-alpine WORKDIR /usr/src/app COPY package*.json ./ -RUN npm ci --only=production +RUN yarn install --production --frozen-lockfile COPY . . EXPOSE 8080 ENV NODE_ENV=production -CMD ["node", "server.js"] \ No newline at end of file +CMD ["node", "server/server.js"] \ No newline at end of file