diff --git a/res/include/md/subscribe.md b/res/include/md/subscribe.md index 93fccbe..6189166 100644 --- a/res/include/md/subscribe.md +++ b/res/include/md/subscribe.md @@ -39,6 +39,10 @@ account. {{if .Authenticated}}(Your e-mail address: {{.User.Email}}){{end}} Order plan 'I'm doing my part!' +{{else if eq $plan "t5"}} + +Order plan 'Resolve' + {{else if eq $plan "t2"}} Order plan 'Persistence' diff --git a/res/include/script/admin.js b/res/include/script/admin.js index 1c7e23b..6c3d2af 100644 --- a/res/include/script/admin.js +++ b/res/include/script/admin.js @@ -1,102 +1,10 @@ -// Draw usage graph +let graphViews = drawGraph(document.getElementById("views_chart"), "Views", "number"); +let graphBandwidth = drawGraph(document.getElementById("bandwidth_chart"), "Bandwidth", "bytes"); -Chart.defaults.global.defaultFontColor = "#b3b3b3"; -Chart.defaults.global.defaultFontSize = 15; -Chart.defaults.global.defaultFontFamily = "Ubuntu"; -Chart.defaults.global.maintainAspectRatio = false; -Chart.defaults.global.elements.point.radius = 0; -Chart.defaults.global.tooltips.mode = "index"; -Chart.defaults.global.tooltips.axis = "x"; -Chart.defaults.global.tooltips.intersect = false; -Chart.defaults.global.animation.duration = 1000; -Chart.defaults.global.animation.easing = "linear"; - -var graph = new Chart( - document.getElementById('bandwidth_chart'), - { - type: 'line', - data: { - datasets: [ - { - label: "Bandwidth", - backgroundColor: "rgba(64, 255, 64, .01)", - borderColor: "rgba(96, 255, 96, 1)", - borderWidth: 1.5, - lineTension: 0.2, - fill: true, - yAxisID: "y_bandwidth" - }, { - label: "Views", - backgroundColor: "rgba(64, 64, 255, .01)", - borderColor: "rgba(96, 96, 255, 1)", - borderWidth: 1.5, - lineTension: 0.2, - fill: true, - yAxisID: "y_views" - } - ] - }, - options: { - scales: { - yAxes: [ - { - type: "linear", - display: true, - position: "left", - id: "y_bandwidth", - scaleLabel: { - display: true, - labelString: "Bandwidth" - }, - ticks: { - callback: function(value, index, values) { - return formatDataVolume(value, 3); - }, - beginAtZero: true - }, - gridLines: { - color: "rgba(100, 255, 100, .05)" - } - }, { - type: "linear", - display: true, - position: "right", - id: "y_views", - scaleLabel: { - display: true, - labelString: "Views" - }, - ticks: { - callback: function(value, index, values) { - return formatNumber(value, 3); - }, - beginAtZero: true - }, - gridLines: { - color: "rgba(128, 128, 255, .05)" - } - } - ], - xAxes: [ - { - ticks: { - maxRotation: 16 - }, - gridLines: { - display: false - } - } - ] - } - } - } -); - -let graphTimeout = null; function loadGraph(minutes, interval, live){ if (graphTimeout !== null) { clearTimeout(graphTimeout) } if (live) { - graphTimeout = setTimeout(() => {loadGraph(minutes, interval, true)}, 2000) + graphTimeout = setTimeout(() => {updateGraphs(minutes, interval, true)}, 10000) } let today = new Date() @@ -112,26 +20,41 @@ function loadGraph(minutes, interval, live){ if (!resp.ok) { return Promise.reject("Error: "+resp.status);} return resp.json(); }).then(resp => { - if (resp.success) { - window.graph.data.labels = resp.labels; - window.graph.data.datasets[0].data = resp.downloads; - window.graph.data.datasets[1].data = resp.views; - window.graph.update(); + resp.views.timestamps.forEach((val, idx) => { + let date = new Date(val); + let dateStr = ("00"+(date.getMonth()+1)).slice(-2); + dateStr += "-"+("00"+date.getDate()).slice(-2); + dateStr += " "+("00"+date.getHours()).slice(-2); + dateStr += ":"+("00"+date.getMinutes()).slice(-2); + resp.views.timestamps[idx] = " "+dateStr+" "; // Poor man's padding + }); + graphViews.data.labels = resp.views.timestamps; + graphViews.data.datasets[0].data = resp.views.amounts; + graphBandwidth.data.labels = resp.views.timestamps; + graphBandwidth.data.datasets[0].data = resp.bandwidth.amounts; + graphViews.update() + graphBandwidth.update(); - document.getElementById("time_start").innerText = resp.labels[0]; - document.getElementById("time_end").innerText = resp.labels.slice(-1)[0]; - let total = 0 - resp.downloads.forEach(e => { total += e; }); - document.getElementById("total_bandwidth").innerText = formatDataVolume(total, 3); - total = 0 - resp.views.forEach(e => { total += e; }); - document.getElementById("total_views").innerText = formatThousands(total); - } + document.getElementById("time_start").innerText = resp.views.timestamps[0]; + document.getElementById("time_end").innerText = resp.views.timestamps.slice(-1)[0]; + let total = 0 + resp.bandwidth.amounts.forEach(e => { total += e; }); + document.getElementById("total_bandwidth").innerText = formatDataVolume(total, 3); + total = 0 + resp.views.amounts.forEach(e => { total += e; }); + document.getElementById("total_views").innerText = formatThousands(total); }).catch(e => { alert("Error requesting time series: "+e); }) } +let graphTimeout = null; +function updateGraphs(minutes, interval, live) { + + loadGraph(graphViews, "views", minutes, interval); + loadGraph(graphBandwidth, "bandwidth", minutes, interval); +} + loadGraph(1440, 10, true); // Load performance statistics diff --git a/res/include/script/dependencies/Modal.js b/res/include/script/dependencies/Modal.js index 95f15fb..0a32483 100644 --- a/res/include/script/dependencies/Modal.js +++ b/res/include/script/dependencies/Modal.js @@ -55,6 +55,10 @@ Modal.prototype.setBody = function(element) { this.body.append(element) } +Modal.prototype.cloneTemplate = function(templateID) { + this.setBody(document.getElementById(templateID).content.cloneNode(true)) +} + Modal.prototype.open = function() { if (this.visible) { return } this.visible = true diff --git a/res/include/script/dependencies/drawGraph.js b/res/include/script/dependencies/drawGraph.js new file mode 100644 index 0000000..fafe5a9 --- /dev/null +++ b/res/include/script/dependencies/drawGraph.js @@ -0,0 +1,65 @@ +Chart.defaults.global.defaultFontColor = "#b3b3b3"; +Chart.defaults.global.defaultFontSize = 15; +Chart.defaults.global.defaultFontFamily = "system-ui, sans-serif"; +Chart.defaults.global.maintainAspectRatio = false; +Chart.defaults.global.elements.point.radius = 0; +Chart.defaults.global.tooltips.mode = "index"; +Chart.defaults.global.tooltips.axis = "x"; +Chart.defaults.global.tooltips.intersect = false; +Chart.defaults.global.animation.duration = 500; +Chart.defaults.global.animation.easing = "linear"; + +function drawGraph(element, label, dataType) { + return new Chart( + element, + { + type: 'line', + data: { + datasets: [ + { + label: label, + backgroundColor: highlightColor, + borderWidth: 0, + lineTension: 0, + fill: true, + yAxisID: "ax_1" + } + ] + }, + options: { + legend: { display: false }, + scales: { + yAxes: [ + { + type: "linear", + display: true, + position: "left", + id: "ax_1", + ticks: { + callback: function(value, index, values) { + if (dataType == "bytes") { + return formatDataVolume(value, 3); + } + return formatNumber(value, 3); + }, + beginAtZero: true + }, + gridLines: { display: false }, + } + ], + xAxes: [ + { + ticks: { + sampleSize: 1, + padding: 4, + minRotation: 0, + maxRotation: 0 + }, + gridLines: { display: false } + } + ] + } + } + } + ); +} diff --git a/res/include/script/file_viewer/DetailsWindow.js b/res/include/script/file_viewer/DetailsWindow.js index c38ed0d..d41c05f 100644 --- a/res/include/script/file_viewer/DetailsWindow.js +++ b/res/include/script/file_viewer/DetailsWindow.js @@ -2,7 +2,9 @@ function DetailsWindow(viewer) { this.viewer = viewer this.visible = false this.file = null - this.graph = 0 + this.graphsInitialized = false + this.graphViews = 0 + this.graphDownloads = 0 this.modal = new Modal( document.getElementById("file_viewer"), () => { this.toggle() }, @@ -27,8 +29,9 @@ DetailsWindow.prototype.toggle = function() { this.btnDetails.classList.add("button_highlight") this.visible = true - if (this.graph === 0) { - this.renderGraph() + if (!this.graphsInitialized) { + this.renderGraphs() + this.graphsInitialized = true } this.updateGraph(this.file) } @@ -54,12 +57,22 @@ DetailsWindow.prototype.setFile = function(file) { } } +DetailsWindow.prototype.renderGraphs = function() { + console.log("rendering graphs") + this.graphDownloads = drawGraph( + document.getElementById("downloads_chart"), "Downloads", "number", + ); + this.graphViews = drawGraph( + document.getElementById("views_chart"), "Views", "number", + ); +} + DetailsWindow.prototype.updateGraph = function(file) { console.log("updating graph") let today = new Date() let start = new Date() - start.setDate(start.getDate()-30) + start.setDate(start.getDate()-90) fetch( file.timeseries_href+ @@ -70,101 +83,21 @@ DetailsWindow.prototype.updateGraph = function(file) { if (!resp.ok) {return null} return resp.json() }).then(resp => { - this.graph.data.labels = resp.labels - this.graph.data.datasets[0].data = resp.downloads - this.graph.data.datasets[1].data = resp.views - this.graph.update() + resp.views.timestamps.forEach((val, idx) => { + let date = new Date(val); + let dateStr = ("00"+(date.getMonth()+1)).slice(-2); + dateStr += "-"+("00"+date.getDate()).slice(-2); + dateStr += " "+("00"+date.getHours()).slice(-2)+"h"; + resp.views.timestamps[idx] = " "+dateStr+" "; // Poor man's padding + }); + resp.bandwidth.amounts.forEach((val, idx) => { + resp.bandwidth.amounts[idx] = Math.round(val/file.size); + }); + this.graphDownloads.data.labels = resp.views.timestamps + this.graphViews.data.labels = resp.views.timestamps + this.graphDownloads.data.datasets[0].data = resp.bandwidth.amounts + this.graphViews.data.datasets[0].data = resp.views.amounts + this.graphDownloads.update() + this.graphViews.update() }) } - -DetailsWindow.prototype.renderGraph = function() { - console.log("rendering graph") - Chart.defaults.global.defaultFontColor = "#b3b3b3" - Chart.defaults.global.defaultFontSize = 15 - Chart.defaults.global.defaultFontFamily = "Ubuntu" - Chart.defaults.global.maintainAspectRatio = false; - Chart.defaults.global.elements.point.radius = 0 - Chart.defaults.global.tooltips.mode = "index" - Chart.defaults.global.tooltips.axis = "x" - Chart.defaults.global.tooltips.intersect = false - this.graph = new Chart( - document.getElementById('bandwidth_chart'), - { - type: 'line', - data: { - datasets: [ - { - label: "Downloads", - backgroundColor: "rgba(64, 255, 64, .01)", - borderColor: "rgba(96, 255, 96, 1)", - borderWidth: 1.5, - lineTension: 0.2, - fill: true, - yAxisID: "y_bandwidth" - }, { - label: "Views", - backgroundColor: "rgba(64, 64, 255, .01)", - borderColor: "rgba(96, 96, 255, 1)", - borderWidth: 1.5, - lineTension: 0.2, - fill: true, - yAxisID: "y_views" - } - ] - }, - options: { - scales: { - yAxes: [ - { - type: "linear", - display: true, - position: "left", - id: "y_bandwidth", - scaleLabel: { - display: true, - labelString: "Downloads" - }, - ticks: { - callback: function(value, index, values) { - return formatNumber(value, 3); - }, - beginAtZero: true - }, - gridLines: { - color: "rgba(100, 255, 100, .05)" - } - }, { - type: "linear", - display: true, - position: "right", - id: "y_views", - scaleLabel: { - display: true, - labelString: "Views" - }, - ticks: { - callback: function(value, index, values) { - return formatNumber(value, 3); - }, - beginAtZero: true - }, - gridLines: { - color: "rgba(128, 128, 255, .05)" - } - } - ], - xAxes: [ - { - ticks: { - maxRotation: 16 - }, - gridLines: { - display: false - } - } - ] - } - } - } - ) -} diff --git a/res/include/script/user_home.js b/res/include/script/user_home.js new file mode 100644 index 0000000..e986b8a --- /dev/null +++ b/res/include/script/user_home.js @@ -0,0 +1,61 @@ +function loadGraph(graph, stat, minutes, interval){ + let today = new Date() + let start = new Date() + start.setMinutes(start.getMinutes()-minutes) + + fetch( + apiEndpoint+"/user/time_series/" + stat + + "?start="+start.toISOString() + + "&end="+today.toISOString() + + "&interval="+interval + ).then(resp => { + if (!resp.ok) { return Promise.reject("Error: "+resp.status);} + return resp.json(); + }).then(resp => { + resp.timestamps.forEach((val, idx) => { + let date = new Date(val); + let dateStr = ("00"+(date.getMonth()+1)).slice(-2); + dateStr += "-"+("00"+date.getDate()).slice(-2); + dateStr += " "+("00"+date.getHours()).slice(-2); + dateStr += ":"+("00"+date.getMinutes()).slice(-2); + resp.timestamps[idx] = " "+dateStr+" "; // Poor man's padding + }); + graph.data.labels = resp.timestamps; + graph.data.datasets[0].data = resp.amounts; + graph.update(); + + document.getElementById("time_start").innerText = resp.timestamps[0]; + document.getElementById("time_end").innerText = resp.timestamps.slice(-1)[0]; + let total = 0 + resp.amounts.forEach(e => { total += e; }); + + if (stat == "views") { + document.getElementById("total_views").innerText = formatThousands(total); + } else if (stat == "downloads") { + document.getElementById("total_downloads").innerText = formatThousands(total); + } else if (stat == "bandwidth") { + document.getElementById("total_bandwidth").innerText = formatDataVolume(total, 3); + } + }).catch(e => { + console.error("Error requesting time series: "+e); + }) +} + +let graphViews = drawGraph(document.getElementById("views_chart"), "Views", "number"); +let graphDownloads = drawGraph(document.getElementById("downloads_chart"), "Downloads", "number"); +let graphBandwidth = drawGraph(document.getElementById("bandwidth_chart"), "Bandwidth", "bytes"); +let graphTimeout = null; + +function updateGraphs(minutes, interval, live) { + if (graphTimeout !== null) { clearTimeout(graphTimeout) } + if (live) { + graphTimeout = setTimeout(() => {updateGraphs(minutes, interval, true)}, 10000) + } + + loadGraph(graphViews, "views", minutes, interval); + loadGraph(graphDownloads, "downloads", minutes, interval); + loadGraph(graphBandwidth, "bandwidth", minutes, interval); +} + +// Default +updateGraphs(10080, 60, false); diff --git a/res/include/style/file_manager.css b/res/include/style/file_manager.css index 985f41a..d97af23 100644 --- a/res/include/style/file_manager.css +++ b/res/include/style/file_manager.css @@ -104,6 +104,7 @@ is collapsed */ margin: 0; box-sizing: border-box; color: var(--text_color); + text-decoration: none; } .node:hover:not(.node_selected) { background-color: var(--input_color_dark); diff --git a/res/include/style/layout.css b/res/include/style/layout.css index e398ebe..61958dd 100644 --- a/res/include/style/layout.css +++ b/res/include/style/layout.css @@ -96,7 +96,7 @@ body{ .page_navigation { position: fixed; backface-visibility: hidden; - z-index: 100; + z-index: 99; width: 250px; height: 100%; left: 0; @@ -125,7 +125,7 @@ body{ overflow-x: hidden; z-index: 50; transition: left 0.5s; - padding: 50px 0 50px 0; + padding: 70px 0 100px 0; } @media (max-width: 800px) { .page_navigation { @@ -183,6 +183,7 @@ body{ text-overflow: ellipsis; transition: background-color 0.5s; border-radius: 5px; + text-decoration: none; } .page_navigation a:hover { background-color: #3f3f3f; @@ -285,10 +286,11 @@ hr{ a { color: #74ad38; - color: var(--highlight_color); - text-decoration: none; + color: var(--highlight_color_dark); +} +a:hover { + color: var(--highlight_color); } -a:hover {text-decoration: underline;} .form{ margin-left: auto; diff --git a/res/include/style/modal.css b/res/include/style/modal.css index 39c6417..9ba2b93 100644 --- a/res/include/style/modal.css +++ b/res/include/style/modal.css @@ -1,5 +1,5 @@ .modal_background { - position: absolute; + position: fixed; top: 0; right: 0; bottom: 0; diff --git a/res/static/img/benefit_5.png b/res/static/img/benefit_5.png new file mode 100644 index 0000000..1fe9f8e Binary files /dev/null and b/res/static/img/benefit_5.png differ diff --git a/res/template/account/user_home.html b/res/template/account/user_home.html index 567a4e5..904936f 100644 --- a/res/template/account/user_home.html +++ b/res/template/account/user_home.html @@ -18,52 +18,77 @@
  • Username: {{.User.Username}}
  • E-mail address: {{.User.Email}}
  • - Supporter status: - {{if eq .User.Subscription ""}} - Not a pixeldrain supporter
    + {{if eq .User.Subscription.Name ""}} + Supporter status: Not a pixeldrain supporter
    Learn how to support pixeldrain. {{else}} - {{if eq .User.Subscription "patreon_1"}} - Level 1 Patreon supporter. Benefits: - - {{else if eq .User.Subscription "patreon_2"}} - Level 2 Patreon supporter. Benefits: - - {{else if eq .User.Subscription "patreon_3"}} - Level 3 Patreon supporter. Benefits: - - {{else if eq .User.Subscription "patreon_4"}} - Level 4 Patreon supporter. Benefits: - - {{end}} + Supporter level {{.User.Subscription.Name}} + {{end}}
  • Change account settings -

    Navigation

    - Upload files - My files - My lists + +

    Statistics

    +

    + Here you can see how often your files are viewed, downloaded + and how much bandwidth they consume. The buttons at the top + can be pressed to adjust the timeframe. If you choose 'Live' + or 'Day' the statistics will be updated periodically. No + need to refresh the page. +

    + +
    + + + + + + + + +
    +

    Views

    +
    + +
    +

    Downloads

    +
    + +
    +

    Bandwidth

    +
    + +
    +
    + Total usage from to
    + views, + downloads and + bandwidth
    {{template "page_bottom" .}} {{template "analytics"}} + + + {{end}} diff --git a/res/template/admin.html b/res/template/admin.html index 238b3cb..5da3728 100644 --- a/res/template/admin.html +++ b/res/template/admin.html @@ -24,9 +24,13 @@ -
    +
    +
    +
    + +
    Total usage from to
    bandwidth and views @@ -63,7 +67,9 @@ {{else}} diff --git a/res/template/file_viewer.html b/res/template/file_viewer.html index 2dfe3fd..23c1203 100644 --- a/res/template/file_viewer.html +++ b/res/template/file_viewer.html @@ -156,12 +156,16 @@
    -

    Downloads and views

    -
    - +

    Downloads

    +
    + +
    +

    Views

    +
    +

    - Chart rendered by the amazing Chart.js. + Charts rendered by the amazing Chart.js.

    About

    @@ -210,8 +214,9 @@ 'use strict'; let apiEndpoint = '{{.APIEndpoint}}'; let captchaKey = '{{.Other.CaptchaKey}}'; - + let highlightColor = '#{{.Style.HighlightColor.RGB}}'; {{template `util.js`}} + {{template `drawGraph.js`}} {{template `Modal.js`}} {{template `Toolbar.js`}} {{template `EditWindow.js`}} diff --git a/res/template/home.html b/res/template/home.html index 3cbfd7b..d79a431 100644 --- a/res/template/home.html +++ b/res/template/home.html @@ -8,7 +8,7 @@ .header_image{ width: 100%; max-width: 800px; - margin: 20px auto 50px auto; + margin: 0 auto 50px auto; } .instruction_highlight { @@ -75,6 +75,7 @@ margin-bottom: 6px; } .features > div > .feature { + display: block; text-align: center; padding: 6px; border-bottom: 1px solid var(--layer_3_color_border); @@ -86,6 +87,8 @@ margin: 8px; text-align: center; } + + {{template `modal.css`}} @@ -195,6 +198,11 @@ about page.

    Features

    +

    + By purchasing a subscription you support pixeldrain on its + mission to make content sharing easier, safer and faster for + everyone. +

    @@ -203,7 +211,9 @@
    No account required
    -
    Files expire 30 days after last view
    + + Files expire 30 days after last view +
    Max file size 10 GB
    Access your files anywhere with a pixeldrain account
    Group multiple files together in a single link with lists
    @@ -213,11 +223,16 @@
    €2 per month + tax
    -
    No ads when viewing and downloading files
    -
    Support pixeldrain's development
    +
    No ads when viewing files
    + + Files expire 2 months after last view + +
    - {{if eq .User.Subscription "patreon_1"}} + {{if eq .User.Subscription.ID "patreon_1"}} You have this plan. Thank you for supporting pixeldrain! {{else}} @@ -226,17 +241,46 @@ {{end}}
    +
    +

    Resolve

    +
    €4 per month + tax
    + + +
    No ads when viewing files
    +
    + Files expire 3 months after last view + + +
    No ads on files you uploaded
    + +
    + {{if eq .User.Subscription.ID "patreon_5"}} + You have this plan. Thank you for supporting pixeldrain! + {{else}} + + Get Started + + {{end}} +
    +

    Persistence

    €8 per month + tax
    -
    No ads when viewing and downloading files
    +
    No ads when viewing files
    + + Files expire 4 months after last view + +
    No ads on files you uploaded
    -
    Files expire two months after last view
    - {{if eq .User.Subscription "patreon_2"}} + {{if eq .User.Subscription.ID "patreon_2"}} You have this plan. Thank you for supporting pixeldrain! {{else}} @@ -250,12 +294,17 @@
    €16 per month + tax
    -
    No ads when viewing and downloading files
    +
    No ads when viewing files
    +
    + Files expire 8 months after last view + +
    No ads on files you uploaded
    -
    Files expire six months after last view
    - {{if eq .User.Subscription "patreon_3"}} + {{if eq .User.Subscription.ID "patreon_3"}} You have this plan. Thank you for supporting pixeldrain! {{else}} @@ -269,12 +318,17 @@
    €32 per month + tax
    -
    No ads when viewing and downloading files
    +
    No ads when viewing files
    +
    + Files you upload never expire + +
    No ads on files you uploaded
    -
    Files you upload never expire
    - {{if eq .User.Subscription "patreon_4"}} + {{if eq .User.Subscription.ID "patreon_4"}} You have this plan. Thank you for supporting pixeldrain! {{else}} @@ -286,14 +340,77 @@
    + + + {{template "page_bottom" .}} {{template "analytics"}} diff --git a/webcontroller/file_viewer.go b/webcontroller/file_viewer.go index 9b000a0..db8f321 100644 --- a/webcontroller/file_viewer.go +++ b/webcontroller/file_viewer.go @@ -93,7 +93,7 @@ func (wc *WebController) serveFileViewer(w http.ResponseWriter, r *http.Request, } showAds := true - if (templateData.Authenticated && templateData.User.DisableAdDisplay) || finfo[0].ShowAds == false { + if (templateData.Authenticated && templateData.User.Subscription.DisableAdDisplay) || finfo[0].ShowAds == false { showAds = false } @@ -188,7 +188,8 @@ func (wc *WebController) serveListViewer(w http.ResponseWriter, r *http.Request, } showAds := true - if (templateData.Authenticated && templateData.User.DisableAdDisplay) || list.Files[0].ShowAds == false { + if (templateData.Authenticated && templateData.User.Subscription.DisableAdDisplay) || + list.Files[0].ShowAds == false { showAds = false }