Browse Source

Added journal support

Óscar García Amor 4 years ago
parent
commit
be7d1182c5

+ 2 - 1
setup.py

@@ -42,7 +42,8 @@ setup(
     },
     install_requires = [
         "bottle>=0.12.10",
-        "dbus-python>=1.2.4"
+        "dbus-python>=1.2.4",
+        "systemd-python"
     ],
     classifiers=[
         "Development Status :: 3 - Alpha",

+ 30 - 1
sysdweb/server.py

@@ -9,7 +9,7 @@
 from bottle import abort, response, route, run, static_file, template, TEMPLATE_PATH
 from socket import gethostname
 from sysdweb.config import checkConfig
-from sysdweb.systemd import systemdBus
+from sysdweb.systemd import systemdBus, Journal
 
 import json
 import os
@@ -43,6 +43,8 @@ def get_service_action(service, action):
                 return {action: str(sdbus.get_unit_active_state(unit))}
             else:
                 return {action: 'not-found'}
+        elif action == 'journal':
+            return get_service_journal(service, 100)
         else:
             response.status = 400
             return {'msg': 'Sorry, but cannot perform \'{}\' action.'.format(action)}
@@ -50,6 +52,23 @@ def get_service_action(service, action):
         response.status = 400
         return {'msg': 'Sorry, but \'{}\' is not defined in config.'.format(service)}
 
+@route('/api/v1/<service>/journal/<lines>')
+def get_service_journal(service, lines):
+    if service in config.sections():
+        if get_service_action(service, 'status')['status'] == 'not-found':
+            return {'journal': 'not-found'}
+        try:
+            lines = int(lines)
+        except Exception as e:
+            response.status = 500
+            return {'msg': '{}'.format(e)}
+        unit = config.get(service, 'unit')
+        journal = Journal(unit)
+        return {'journal': journal.get_tail(lines)}
+    else:
+        response.status = 400
+        return {'msg': 'Sorry, but \'{}\' is not defined in config.'.format(service)}
+
 @route('/')
 def get_main():
     services = []
@@ -74,6 +93,16 @@ def get_main():
             'service': service})
     return template('index', hostname=gethostname(), services=services)
 
+@route('/journal/<service>')
+def get_service_journal_page(service):
+    if service in config.sections():
+        if get_service_action(service, 'status')['status'] == 'not-found':
+            abort(400,'Sorry, but service \'{}\' unit not found in system.'.format(config.get(service, 'title')))
+        journal_lines = get_service_journal(service, 100)
+        return template('journal', hostname=gethostname(), service=config.get(service, 'title'), journal=journal_lines['journal'])
+    else:
+        abort(400, 'Sorry, but \'{}\' is not defined in config.'.format(service))
+
 # Serve static content
 @route('/favicon.ico')
 def get_favicon():

+ 13 - 0
sysdweb/systemd.py

@@ -7,6 +7,7 @@
 # Distributed under terms of the GNU GPLv3 license.
 
 from dbus import SystemBus, SessionBus, Interface, exceptions
+from systemd import journal
 
 DBUS_INTERFACE = 'org.freedesktop.DBus.Properties'
 SYSTEMD_BUSNAME = 'org.freedesktop.systemd1'
@@ -66,3 +67,15 @@ class systemdBus(object):
             return True
         except exceptions.DBusException:
             return False
+
+class Journal(object):
+    def __init__(self, unit):
+        self.reader = journal.Reader()
+        self.reader.add_match(_SYSTEMD_UNIT=unit)
+
+    def get_tail(self, lines):
+        self.reader.seek_tail()
+        self.reader.get_previous(lines)
+        journal_lines = ['{__REALTIME_TIMESTAMP} {MESSAGE}'.format(**value) for value in self.reader]
+        self.reader.close()
+        return journal_lines

+ 11 - 2
sysdweb/templates/static/css/sysdweb.css

@@ -5,14 +5,23 @@
  * Distributed under terms of the GNU GPLv3 license.
  */
 
-.well {
-  background-color: #fff;
+a {
+  color: rgba(0, 0, 80, 1);
+}
+
+a:hover {
+  color: rgba(0, 0, 80, 0.8);
+  text-decoration: none;
 }
 
 .table {
   margin-bottom: 0
 }
 
+pre {
+  margin-bottom: 0
+}
+
 .table > tbody > tr > td {
   vertical-align: middle;
 }

+ 4 - 5
sysdweb/templates/static/js/sysdweb.js

@@ -5,10 +5,6 @@
  * Distributed under terms of the GNU GPLv3 license.
  */
 
-$(document).ready(function(){
-      $('[data-toggle="tooltip"]').tooltip();
-});
-
 function unit(service, action) {
   var url = '/api/v1/' + service + '/' + action;
 
@@ -28,7 +24,10 @@ function unit(service, action) {
 }
 
 $(document).ready(function(){
+  $('[data-toggle="tooltip"]').tooltip();
   setInterval(function() {
-    $('#services').load(document.URL + ' #services');
+    $('#services').load(document.URL + ' #services', function() {
+      $('[data-toggle="tooltip"]').tooltip();
+    });
   }, 20000);
 });

+ 14 - 5
sysdweb/templates/views/index.tpl

@@ -8,10 +8,10 @@
     <title>{{hostname or 'sysdweb'}} · sysdweb</title>
 
     <!-- Bootstrap -->
-    <link href="css/bootstrap.min.css" rel="stylesheet">
+    <link href="/css/bootstrap.min.css" rel="stylesheet">
 
     <!-- Custom style -->
-    <link href="css/sysdweb.css" rel="stylesheet">
+    <link href="/css/sysdweb.css" rel="stylesheet">
 
     <!-- Favicon -->
     <link rel="shortcut icon" href="/img/favicon.png">
@@ -36,7 +36,16 @@
           </tr>
           % for service in services:
           <tr>
-            <td class="{{service['class']}}">{{service['title']}}</td>
+            <td class="{{service['class']}}">
+            % if service['class'] != 'active':
+              <a href="/journal/{{service['service']}}"
+                data-toggle="tooltip" data-placement="right" title="Show journal">
+            % end
+                {{service['title']}}
+            % if service['class'] != 'active':
+                </a>
+            % end
+            </td>
             <td class="text-right {{service['class']}}">
               <button type="button" class="btn btn-default btn-sm"
               % if service['disabled_start']:
@@ -94,7 +103,7 @@
     <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
     <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
     <!-- Include all compiled plugins (below), or include individual files as needed -->
-    <script src="js/bootstrap.min.js"></script>
-    <script src="js/sysdweb.js"></script>
+    <script src="/js/bootstrap.min.js"></script>
+    <script src="/js/sysdweb.js"></script>
   </body>
 </html>

+ 41 - 0
sysdweb/templates/views/journal.tpl

@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
+    <title>{{service}} journal · {{hostname or 'sysdweb'}} · sysdweb</title>
+
+    <!-- Bootstrap -->
+    <link href="/css/bootstrap.min.css" rel="stylesheet">
+
+    <!-- Custom style -->
+    <link href="/css/sysdweb.css" rel="stylesheet">
+
+    <!-- Favicon -->
+    <link rel="shortcut icon" href="/img/favicon.png">
+
+    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
+    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
+    <!--[if lt IE 9]>
+      <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
+      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+    <![endif]-->
+  </head>
+  <body>
+    <div class="container-fluid">
+      <div class="page-header text-center">
+        <h1>{{service}} journal<br/>
+        <small>{{hostname or 'sysdweb'}}</small></h1>
+      </div>
+      <div>
+        <pre id="journal">
+% for line in journal:
+{{line}}
+% end
+        </pre>
+      </div>
+    </div>
+  </body>
+</html>