Explorar el Código

Add rudimentary charting of metrics. To be expanded later.

George Baugh hace 1 año
padre
commit
b24a0af8b0

+ 1 - 0
.gitignore

@@ -14,6 +14,7 @@ pod2htmd.tmp
 list.min.json
 highlight.min.js
 obsidian.min.css
+www/scripts/chart.js
 www/.well_known
 config/auth.db
 config/has_users

+ 4 - 3
Installer.mk

@@ -50,9 +50,10 @@ prereq-node:
 
 .PHONY: prereq-frontend
 prereq-frontend:
-	mkdir -p www/scripts; pushd www/scripts && curl -L --remote-name-all                                 \
-		"https://raw.githubusercontent.com/chalda-pnuzig/emojis.json/master/dist/list.min.json"     \
-		"https://raw.githubusercontent.com/highlightjs/cdn-release/main/build/highlight.min.js"; popd
+	mkdir -p www/scripts; pushd www/scripts && curl -L --remote-name-all                        \
+		"https://raw.githubusercontent.com/chalda-pnuzig/emojis.json/master/dist/list.min.json" \
+		"https://raw.githubusercontent.com/highlightjs/cdn-release/main/build/highlight.min.js" \
+		"https://cdn.jsdelivr.net/npm/chart.js"; popd
 	mkdir -p www/styles; cd www/styles && curl -L --remote-name-all \
 		"https://raw.githubusercontent.com/highlightjs/cdn-release/main/build/styles/obsidian.min.css"
 

+ 27 - 6
lib/Trog/Routes/HTML.pm

@@ -73,8 +73,6 @@ our %routes = (
     #        callback => \&Trog::Routes::HTML::setup,
     #    },
 
-    # IMPORTANT: YOU MUST setup fail2ban rules for the following routes.
-    # TODO: Put a rule in fail2ban/ subdir, make say a generator for it based on the routes having fail2ban=1
     '/login' => {
         method   => 'GET',
         callback => \&Trog::Routes::HTML::login,
@@ -151,8 +149,11 @@ our %routes = (
         callback => \&Trog::Routes::HTML::processed,
         noindex  => 1,
     },
-
-    # END FAIL2BAN ROUTES
+	'/metrics' => {
+		method => 'GET',
+		auth   => 1,
+		callback => \&Trog::Routes::HTML::metrics,
+	},
 
     #TODO transform into posts?
     '/sitemap',
@@ -239,7 +240,7 @@ Most subsequent functions simply pass content to this function.
 
 =cut
 
-sub index ( $query, $content = '', $i_styles = [] ) {
+sub index ( $query, $content = '', $i_styles = [], $i_scripts = [] ) {
     $query->{theme_dir} = $Trog::Themes::td;
 
     my $to_render = $query->{template} // $landing_page;
@@ -308,6 +309,7 @@ sub index ( $query, $content = '', $i_styles = [] ) {
             categories   => \@series,
             stylesheets  => \@styles,
             print_styles => \@p_styles,
+			scripts      => $i_scripts,
             show_madeby  => $Theme::show_madeby ? 1 : 0,
             embed        => $query->{embed} ? 1 : 0,
             embed_video  => $query->{primary_post}{is_video},
@@ -1537,6 +1539,26 @@ sub processed ($query) {
     );
 }
 
+sub metrics ($query) {
+    return see_also('/login') unless $query->{user};
+    return Trog::Routes::HTML::forbidden($query) unless grep { $_ eq 'admin' } @{ $query->{user_acls} };
+
+    $query->{failure} //= -1;
+
+    return Trog::Routes::HTML::index(
+        {
+            title     => 'tCMS Metrics',
+            theme_dir => $Trog::Themes::td,
+            template  => 'metrics.tx',
+            is_admin  => 1,
+            %$query,
+        },
+        undef,
+        ['post.css'],
+		['chart.js'],
+    );
+}
+
 # basically a file rewrite rule for themes
 sub icon ($query) {
     my $path = $query->{route};
@@ -1559,7 +1581,6 @@ sub rss_style ($query) {
         data        => $query,
         code        => 200,
     );
-
 }
 
 sub _build_themed_styles ($styles) {

+ 17 - 0
lib/Trog/Routes/JSON.pm

@@ -9,10 +9,13 @@ use feature qw{signatures state};
 use Clone qw{clone};
 use JSON::MaybeXS();
 
+use Trog::Utils();
 use Trog::Config();
 use Trog::Auth();
 use Trog::Routes::HTML();
 
+use Trog::Log::Metrics();
+
 my $conf = Trog::Config::get();
 
 # TODO de-duplicate this, it's shared in html
@@ -42,6 +45,12 @@ our %routes = (
         noindex    => 1,
         robot_name => '/api/auth_change_request/*',
     },
+    '/api/requests_per' => {
+        method => 'GET',
+        auth   => 1,
+        parameters => [qw{period num_periods before code}],
+        callback => \&requests_per,
+    },
 );
 
 # Clone / redact for catalog
@@ -94,6 +103,14 @@ sub process_auth_change_request ($query) {
     );
 }
 
+sub requests_per($query) {
+    my $code = Trog::Utils::coerce_array($query->{code});
+    return _render(
+        200, undef, 
+        %{Trog::Log::Metrics::requests_per($query->{period}, $query->{num_periods}, $query->{before}, @$code )}
+    );
+}
+
 sub _render ( $code, $headers, %data ) {
     return Trog::Renderer->render(
         code        => 200,

+ 69 - 0
www/templates/html/components/metrics.tx

@@ -0,0 +1,69 @@
+<div id="backoffice">
+    <script>
+        var params = { period: 'hour', num_periods: 5 };
+        addEventListener("DOMContentLoaded", (event => {
+            // Fire off the XHR to fetch the JSON payload
+            doChartXHR(params);
+        }));
+
+        addEventListener("chartXHRFinished", (event => {
+            buildChart(event.payload);
+        }));
+
+        function doChartXHR(params) {
+            const req = new XMLHttpRequest();
+            req.addEventListener("load", respondToChartXHR);
+            req.open("GET", location.protocol+'//'+location.host+"/api/requests_per"+location.search);
+            req.send();
+        };
+
+        function respondToChartXHR() {
+            var payload = this.responseText;
+            console.log(payload);
+            const ev = new Event("chartXHRFinished");
+            ev.payload = JSON.parse(payload);
+            dispatchEvent(ev);
+        };
+
+        function buildChart(payload) {
+            const ctx = document.getElementById('request_chart');
+            new Chart(ctx, {
+                type: 'bar',
+                data: {
+                    labels: payload.labels,
+                    datasets: [{
+                        label: '# Requests',
+                        data: payload.data,
+                        borderWidth: 1
+                    }]
+                },
+                options: {
+                    scales: {
+                        y: {
+                            beginAtZero: true
+                        }
+                    }
+                }
+            });
+        };
+    </script>
+    <form>
+        Period:
+        <select name=period class="cooltext" >
+            <option value="second">second</option>
+            <option value="minute">minute</option>
+            <option value="hour" selected>hour</option>
+            <option value="day">day</option>
+            <option value="week">week</option>
+            <option value="month">month</option>
+            <option value="year">year</option>
+        </select>
+        Num Periods:
+        <input name="num_periods" class="cooltext" value=5 />
+        <input type="submit" value="Go" />
+    </form>
+    <div>
+      <canvas id="request_chart"></canvas>
+    </div>
+
+</div>

+ 1 - 0
www/templates/html/sysbar.tx

@@ -4,6 +4,7 @@
         <a href="/"            title="Back home"     class="topbar">Home</a>
         <a href="/config"      title="Configuration" class="topbar">Settings</a>
         <a href="/manual"      title="Manual"        class="topbar">Manual</a>
+        <a href="/metrics"     title="Metrics"       class="topbar">Metrics</a>
         <a href="/totp"        title="TOTP"          class="topbar">Enable/View TOTP 2fa</a>
         <a href="/password_reset" title="Reset Auth" class="topbar">Reset Password/TOTP</a>
         <a href="/logout"      title="Logout"        class="topbar">🚪</a>

+ 3 - 0
www/templates/toolong.tx

@@ -0,0 +1,3 @@
+419 Excessive URL length
+<br /><br />
+Please send URLs shorter than 2048 characters.