diff --git a/webadmin/src/index.html b/webadmin/src/index.html index 0baf2fc77..dc6a626a2 100644 --- a/webadmin/src/index.html +++ b/webadmin/src/index.html @@ -220,7 +220,8 @@
  • Graphs
  • Settings
  • -
  • Terminal & Logs
  • +
  • Logs
  • +
  • Terminal
  • Support
  • Logout
  • diff --git a/webadmin/src/js/index.js b/webadmin/src/js/index.js index 3ce015c8f..4374efc60 100644 --- a/webadmin/src/js/index.js +++ b/webadmin/src/js/index.js @@ -61,6 +61,9 @@ app.config(['$routeProvider', function ($routeProvider) { }).when('/email', { controller: 'EmailController', templateUrl: 'views/email.html' + }).when('/logs', { + controller: 'LogsController', + templateUrl: 'views/logs.html' }).when('/settings', { controller: 'SettingsController', templateUrl: 'views/settings.html' diff --git a/webadmin/src/theme.scss b/webadmin/src/theme.scss index 232c9ffaa..010914c02 100644 --- a/webadmin/src/theme.scss +++ b/webadmin/src/theme.scss @@ -1124,12 +1124,18 @@ footer { .logs-controls { margin-top: 25px; + margin-bottom: 10px; .ng-isolate-scope { - display: inline-block; float: left; } + h3 { + display: inline-block; + margin-top: 6px; + margin-bottom: 0; + } + select { display: inline-block; width: 250px; @@ -1147,6 +1153,30 @@ footer { } } +.logs-container { + flex-grow: 1; + margin-left: calc(8.33% + 15px); + margin-right: calc(8.33% + 15px); + margin-bottom: 20px; + background-color: black; + color: white; + overflow: auto; + padding: 5px; + font-family: monospace; + + .log-line { + line-height: 1.2; + + &:hover { + background-color: #333333; + } + + .time { + color: #00FFFF; + } + } +} + .logs-and-term-container { flex-grow: 1; margin-left: calc(8.33% + 15px); diff --git a/webadmin/src/views/logs.html b/webadmin/src/views/logs.html new file mode 100644 index 000000000..be3393800 --- /dev/null +++ b/webadmin/src/views/logs.html @@ -0,0 +1,12 @@ +
    +
    +

    Logs

    + + + + Download Logs +
    +
    + +
    + diff --git a/webadmin/src/views/logs.js b/webadmin/src/views/logs.js new file mode 100644 index 000000000..164d5aaa9 --- /dev/null +++ b/webadmin/src/views/logs.js @@ -0,0 +1,120 @@ +'use strict'; + +/* global moment */ +/* global Terminal */ + +angular.module('Application').controller('LogsController', ['$scope', '$location', '$route', '$routeParams', 'Client', function ($scope, $location, $route, $routeParams, Client) { + Client.onReady(function () { if (!Client.getUserInfo().admin) $location.path('/'); }); + + $scope.config = Client.getConfig(); + $scope.user = Client.getUserInfo(); + + $scope.logs = []; + $scope.selected = ''; + $scope.activeEventSource = null; + $scope.lines = 10; + $scope.selectedAppInfo = null; + + function ab2str(buf) { + return String.fromCharCode.apply(null, new Uint16Array(buf)); + } + + $scope.populateLogTypes = function () { + $scope.logs.push({ name: 'System (All)', type: 'platform', value: 'all', url: Client.makeURL('/api/v1/cloudron/logs?units=all') }); + $scope.logs.push({ name: 'Box', type: 'platform', value: 'box', url: Client.makeURL('/api/v1/cloudron/logs?units=box') }); + $scope.logs.push({ name: 'Mail', type: 'platform', value: 'mail', url: Client.makeURL('/api/v1/cloudron/logs?units=mail') }); + + Client.getInstalledApps().sort(function (app1, app2) { return app1.fqdn.localeCompare(app2.fqdn); }).forEach(function (app) { + $scope.logs.push({ + type: 'app', + value: app.id, + name: app.fqdn + ' (' + app.manifest.title + ')', + url: Client.makeURL('/api/v1/apps/' + app.id + '/logs'), + addons: app.manifest.addons + }); + }); + + // activate pre-selected log from query + $scope.selected = $scope.logs.find(function (e) { return e.value === $routeParams.id; }); + + if (!$scope.selected) { + $scope.selected = $scope.logs[0]; + } + }; + + function reset() { + // close the old event source so we wont receive any new logs + if ($scope.activeEventSource) { + $scope.activeEventSource.close(); + $scope.activeEventSource = null; + } + + var logViewer = $('.logs-container'); + logViewer.empty(); + + $scope.selectedAppInfo = null; + } + + $scope.showLogs = function () { + reset(); + + if (!$scope.selected) return; + + var func = $scope.selected.type === 'platform' ? Client.getPlatformLogs : Client.getAppLogs; + func($scope.selected.value, true, $scope.lines, function handleLogs(error, result) { + if (error) return console.error(error); + + $scope.activeEventSource = result; + result.onmessage = function handleMessage(message) { + var data; + + try { + data = JSON.parse(message.data); + } catch (e) { + return console.error(e); + } + + // check if we want to auto scroll (this is before the appending, as that skews the check) + var tmp = $('.logs-container'); + var autoScroll = tmp[0].scrollTop > (tmp[0].scrollTopMax - 24); + + var logLine = $('
    '); + var timeString = moment.utc(data.realtimeTimestamp/1000).format('MMM DD HH:mm:ss'); + logLine.html('' + timeString + ' ' + window.ansiToHTML(typeof data.message === 'string' ? data.message : ab2str(data.message))); + tmp.append(logLine); + + if (autoScroll) tmp[0].lastChild.scrollIntoView({ behavior: 'instant', block: 'end' }); + }; + }); + }; + + $scope.$watch('selected', function (newVal) { + if (!newVal) return; + + $route.updateParams({ id: newVal.value }); + $scope.showLogs(); + }); + + Client.onReady($scope.populateLogTypes); + + Client.onApps(function () { + if ($scope.$$destroyed) return; + if ($scope.selected.type !== 'app') return; + + var appId = $scope.selected.value; + + Client.getApp(appId, function (error, result) { + if (error) return console.error(error); + + $scope.selectedAppInfo = result; + }); + }); + + $scope.$on('$destroy', function () { + if ($scope.activeEventSource) { + $scope.activeEventSource.onmessage = function () {}; + $scope.activeEventSource.close(); + $scope.activeEventSource = null; + } + }); +}]);