Download history to client

This commit is contained in:
Magnus Åhall 2026-06-06 14:29:22 +02:00
parent 65a0225d74
commit 9506b89453
5 changed files with 249 additions and 75 deletions

View file

@ -516,87 +516,115 @@ dialog.op {
n2-pagehistory {
.layout {
.back,
.node-name {
display: grid;
grid-template-columns: min-content 1fr;
grid-gap: 8px;
align-items: center;
margin-bottom: 16px;
}
.el-back-image,
.el-back-text {
cursor: pointer;
}
.group-label {
font-weight: bold;
background-color: #444;
color: #fff;
padding: 8px 32px;
display: inline-block;
margin-left: 32px;
transform: translateY(14px);
border-radius: 6px;
}
.el-node-name {
margin-left: 8px;
}
.group {
border: 1px solid #ccc;
padding: 32px;
margin-bottom: 32px;
border-radius: 8px;
background-color: #f8f8f8;
.el-nodes {
grid-column: 1 / -1;
box-shadow:
rgba(0, 0, 0, 0.4) 0px 2px 4px,
rgba(0, 0, 0, 0.3) 0px 7px 13px -3px,
rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
}
display: grid;
grid-template-columns: min-content minmax(min-content, max-content) min-content 1fr;
.el-back-image,
.el-back-text {
cursor: pointer;
}
background-color: var(--line-color);
gap: 1px;
border: 1px solid var(--line-color);
.el-node-name {
margin-left: 8px;
}
&>div>div {
padding: 8px 12px;
background-color: #fff;
white-space: nowrap;
.el-nodes {
grid-column: 1 / -1;
&.index {
text-align: right;
}
display: grid;
grid-template-columns: min-content minmax(min-content, max-content) min-content 1fr;
&.updated {
white-space: initial;
}
background-color: var(--line-color);
gap: 1px;
border: 1px solid var(--line-color);
.date {
white-space: nowrap;
font-weight: bold;
}
.time {
white-space: nowrap;
color: #555;
}
&.name {
white-space: initial;
/*overflow-wrap: anywhere;*/
word-break: break-all;
color: var(--color1);
}
}
.history-node {
display: contents;
}
}
.pagination {
grid-column: 1 / -1;
margin-top: 16px;
display: grid;
grid-template-columns: repeat(3, min-content);
grid-gap: 32px;
&>div>div {
padding: 8px 12px;
background-color: #fff;
white-space: nowrap;
user-select: none;
.el-prev {
font-weight: bold;
cursor: pointer;
&.index {
text-align: right;
}
.el-next {
font-weight: bold;
cursor: pointer;
&.updated {
white-space: initial;
}
.date {
white-space: nowrap;
font-weight: bold;
}
.time {
white-space: nowrap;
color: #555;
}
&.name {
white-space: initial;
/*overflow-wrap: anywhere;*/
word-break: break-all;
color: var(--color1);
}
}
.history-node {
display: contents;
}
}
.el-pagination {
grid-column: 1 / -1;
margin-top: 16px;
display: grid;
grid-template-columns: repeat(3, min-content);
grid-gap: 16px;
align-items: center;
white-space: nowrap;
user-select: none;
.el-prev,
.el-next {
font-weight: bold;
cursor: pointer;
border: 1px solid #aaa;
background-color: #eee;
padding: 8px 16px;
border-radius: 4px;
}
}
}

View file

@ -456,7 +456,24 @@ class NodeHistoryStore extends SimpleNodeStore {
request.onerror = (event) => reject(event.target.error)
})
}//}}}
retrievePage(uuid, perPage, page) {
hasNode(uuid, updated) {
return new Promise((resolve, reject) => {
const req = this.db
.transaction(['nodes', this.storeName], 'readonly')
.objectStore(this.storeName)
.getKey([uuid, updated])
req.onsuccess = (event) => {
resolve(event.target.result !== undefined)
}
req.onerror = (event) => {
console.log(event.target.error)
reject(event.target.error)
}
})
}
retrievePage(uuid, perPage, page) {// {{{
return new Promise((resolve, _reject) => {
const cursor = this.db
.transaction(['nodes', this.storeName], 'readonly')
@ -497,10 +514,10 @@ class NodeHistoryStore extends SimpleNodeStore {
}
}
})
}
}// }}}
}
export function uuidv7() {
export function uuidv7() {// {{{
// random bytes
const value = new Uint8Array(16)
crypto.getRandomValues(value)
@ -524,6 +541,6 @@ export function uuidv7() {
.map((b) => b.toString(16).padStart(2, "0"))
.join("")
return `${str.slice(0, 8)}-${str.slice(8, 12)}-${str.slice(12, 16)}-${str.slice(16, 20)}-${str.slice(20)}`
}
}// }}}
// vim: foldmethod=marker

View file

@ -1,7 +1,8 @@
import { CustomHTMLElement } from './lib/custom_html_element.mjs'
import { Node } from './page_node.mjs'
export class N2PageHistory extends CustomHTMLElement {
static PAGESIZE = 25
static PAGESIZE = 15
static {
this.tmpl = document.createElement('template')
@ -9,22 +10,30 @@ export class N2PageHistory extends CustomHTMLElement {
<style>
n2-pagehistory {
margin-top: 32px;
.layout {
}
}
</style>
<div class="layout">
<div class="back">
<img data-el="back-image" src="/images/${_VERSION}/icon_back.svg" class="colorize">
<div data-el="back-text">Back to node</div>
</div>
<div class="node-name">
<img src="/images/${_VERSION}/icon_history.svg" class="colorize">
<h1 data-el="node-name"></h1>
</div>
<div class="group-label">Actions</div>
<div class="group">
<button data-el="download-history">Fetch all history from server</button>
</div>
<div class="group-label">History</div>
<div class="group">
<div data-el="nodes"></div>
<div class="pagination">
<div data-el="pagination">
<div data-el="prev">&lt;</div>
<div data-el="page"></div>
<div data-el="next">&gt;</div>
@ -44,6 +53,7 @@ export class N2PageHistory extends CustomHTMLElement {
this.elBackText.addEventListener('click', () => _mbus.dispatch('SHOW_PAGE', { page: 'node' }))
this.elPrev.addEventListener('click', () => this.prevPage())
this.elNext.addEventListener('click', () => this.nextPage())
this.elDownloadHistory.addEventListener('click', () => this.downloadHistory())
_mbus.subscribe('NODE_UI_OPEN', async (event) => {
await this.useNode(event.detail.data)
@ -104,6 +114,11 @@ export class N2PageHistory extends CustomHTMLElement {
this.elNodeName.innerText = this.node.get('Name')
this.elPage.innerText = `${this.page} / ${this.pages}`
if (this.nodesTotal <= N2PageHistory.PAGESIZE)
this.elPagination.style.display = 'none'
else
this.elPagination.style.display = ''
let nodes = await nodeStore.nodesHistory.retrievePage(this.node.UUID, N2PageHistory.PAGESIZE, this.page)
let i = 0
let divs = nodes.map(n => {
@ -124,5 +139,50 @@ export class N2PageHistory extends CustomHTMLElement {
})
this.elNodes.replaceChildren(...divs)
}
async downloadHistory() {
try {
const nodes = []
let offset = 0
let hasMore = true
while (hasMore) {
const history = await this.downloadHistoryPage(offset)
hasMore = history.HasMore
for (const nodeData of history.Nodes) {
nodes.push(new Node(nodeData))
}
offset = nodes.length
}
let num = 0
for (const node of nodes) {
const ok = await nodeStore.nodesHistory.hasNode(node.UUID, node.get('Updated'))
if (ok) num++
await nodeStore.nodesHistory.add(node)
}
console.log(num)
} catch (e) {
console.error(e)
alert(e)
}
}
async downloadHistoryPage(offset) {
const res = await fetch(`/node/history/retrieve/${this.node.UUID}/${offset}`, {
headers: {
"Authorization": 'Bearer ' + localStorage.getItem('token'),
}
})
const json = await res.json()
if (!json.OK) {
alert(json.Error)
return
}
return json
}
}
customElements.define('n2-pagehistory', N2PageHistory)