From d20b20ec2f7e00aae8dfe530cc8f0fe9159dd490 Mon Sep 17 00:00:00 2001 From: Breck Yunits Date: Mon, 28 Oct 2024 06:57:15 -1000 Subject: [PATCH] --- ScrollHub.js | 24 +++++++++++---- dashboard.js | 73 ++++++++++++++++++++++++++++++++-------------- requests.scroll | 20 +++++++++++++ scrollHubEditor.js | 2 +- 4 files changed, 90 insertions(+), 29 deletions(-) create mode 100644 requests.scroll diff --git a/ScrollHub.js b/ScrollHub.js index 5c7da01b..4e6b7da0 100644 --- a/ScrollHub.js +++ b/ScrollHub.js @@ -85,6 +85,7 @@ class ScrollHub { this.sseClients = new Set() this.globalLogFile = path.join(__dirname, "log.txt") this.storyLogFile = path.join(__dirname, "writes.txt") + this.requestsLogPath = path.join(__dirname, "requests.csv") this.dashboard = new Dashboard(this.globalLogFile) } @@ -209,17 +210,25 @@ class ScrollHub { }) } + isSummarizing = false + async buildRequestsSummary() { + if (this.isSummarizing) return + this.isSummarizing = true + const dashboard = new Dashboard(this.globalLogFile) + await dashboard.processLogFile() + await fsp.writeFile(this.requestsLogPath, dashboard.csv, "utf8") + this.isSummarizing = false + } + initAnalytics() { + const checkWritePermissions = this.checkWritePermissions.bind(this) if (!fs.existsSync(this.storyLogFile)) fs.writeFileSync(this.storyLogFile, "", "utf8") const { app, folderCache } = this app.use(this.logRequest.bind(this)) - app.get("/dashboard.csv", async (req, res) => { - const { dashboard } = this - await dashboard.processLogFile() - const { csv } = dashboard - res.setHeader("Content-Type", "text/plain") - res.send(csv) + app.post("/summarizeRequests.htm", checkWritePermissions, async (req, res) => { + this.buildRequestsSummary() + res.send("Building Requests Summary") }) app.get("/hostname.htm", (req, res) => res.send(req.hostname)) @@ -1179,6 +1188,9 @@ container 1000px index.html ${this.hostname} style font-size: 150%; +Traffic Data + requests.html + JSON | CSV | TSV link folders.json JSON link folders.csv CSV diff --git a/dashboard.js b/dashboard.js index b67fa819..fde8e323 100644 --- a/dashboard.js +++ b/dashboard.js @@ -65,10 +65,15 @@ class Dashboard { const regex = /^(read|write) ([^ ]+) ([^ ]+) (\d+) ([^ ]+) (.+)$/ const match = entry.match(regex) if (match) { + const url = match[3] + // Extract filename from URL + const filename = url.split("/").pop().split("?")[0] + return { method: match[1] === "read" ? "GET" : "POST", folder: match[2], - url: match[3], + url: url, + filename: filename, timestamp: new Date(parseInt(match[4])), ip: match[5], userAgent: match[6] @@ -78,37 +83,61 @@ class Dashboard { } updateStats(entry) { - if (entry) { - const date = entry.timestamp.toISOString().split("T")[0] - const key = `${date}|${entry.folder}` - - if (!this.stats[key]) { - this.stats[key] = { - date, - folder: entry.folder, - reads: 0, - writes: 0, - uniqueReaders: new Set(), - uniqueWriters: new Set() - } + if (!entry) return + + const date = entry.timestamp.toISOString().split("T")[0] + const key = `${date}|${entry.folder}` + + if (!this.stats[key]) + this.stats[key] = { + date, + folder: entry.folder, + reads: 0, + writes: 0, + uniqueReaders: new Set(), + uniqueWriters: new Set(), + pageViews: new Map() // Track views per page } - if (entry.method === "GET") { - this.stats[key].reads++ - this.stats[key].uniqueReaders.add(entry.ip) - } else if (entry.method === "POST") { - this.stats[key].writes++ - this.stats[key].uniqueWriters.add(entry.ip) + const row = this.stats[key] + + if (entry.method === "GET") { + row.reads++ + row.uniqueReaders.add(entry.ip) + + // Update page views + if (entry.filename.endsWith(".html") || entry.filename.endsWith(".htm")) { + const currentViews = row.pageViews.get(entry.filename) || 0 + row.pageViews.set(entry.filename, currentViews + 1) } + } else if (entry.method === "POST") { + row.writes++ + row.uniqueWriters.add(entry.ip) } } + getTopPages(pageViews, count = 5) { + return Array.from(pageViews.entries()) + .sort((a, b) => b[1] - a[1]) + .slice(0, count) + .map(([filename]) => filename) + } + finalizeStats() { Object.values(this.stats).forEach(stat => { stat.readers = stat.uniqueReaders.size stat.writers = stat.uniqueWriters.size + + // Get top 5 pages and store them as rank1 through rank5 + const topPages = this.getTopPages(stat.pageViews) + for (let i = 0; i < 5; i++) { + stat[`rank${i + 1}`] = topPages[i] || "" // Empty string if no page exists for this rank + } + + // Clean up temporary data structures delete stat.uniqueReaders delete stat.uniqueWriters + delete stat.pageViews }) } @@ -145,9 +174,9 @@ class Dashboard { } get csv() { - const csvHeader = "Date,Folder,Reads,Readers,Writes,Writers\n" + const csvHeader = "Date,Folder,Reads,Readers,Writes,Writers,Rank1,Rank2,Rank3,Rank4,Rank5\n" const csvRows = Object.values(this.stats) - .map(({ date, folder, reads, readers, writes, writers }) => `${date},${folder},${reads},${readers},${writes},${writers}`) + .map(({ date, folder, reads, readers, writes, writers, rank1, rank2, rank3, rank4, rank5 }) => `${date},${folder},${reads},${readers},${writes},${writers},${rank1},${rank2},${rank3},${rank4},${rank5}`) .join("\n") return csvHeader + csvRows } diff --git a/requests.scroll b/requests.scroll new file mode 100644 index 00000000..d1872a27 --- /dev/null +++ b/requests.scroll @@ -0,0 +1,20 @@ +title Traffic Data + +header.scroll +printTitle + +container + +Real time view + globe.html + +button Refresh + link summarizeRequests.htm + post + // Anything + +requests.csv + printTable + +tableSearch +footer.scroll diff --git a/scrollHubEditor.js b/scrollHubEditor.js index 8f98af64..5d644bdc 100644 --- a/scrollHubEditor.js +++ b/scrollHubEditor.js @@ -260,7 +260,7 @@ class EditorApp { updateFooterLinks() { const { folderName, folderNameText } = this document.getElementById("gitClone").innerHTML = - `traffic · history · duplicate · rename · delete` + `live traffic · history · duplicate · rename · delete` } async renameFolder() {