diff --git a/gulpfile.js b/gulpfile.js index b0ac85809..2db35d717 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -50,7 +50,7 @@ if (argv.help || argv.h) { process.exit(1); } -gulp.task('js', ['js-index', 'js-setup', 'js-setupdns', 'js-restore', 'js-update'], function () {}); +gulp.task('js', ['js-index', 'js-logs', 'js-setup', 'js-setupdns', 'js-restore', 'js-update'], function () {}); var oauth = { clientId: argv.clientId || 'cid-webadmin', @@ -90,6 +90,22 @@ gulp.task('js-index', function () { .pipe(gulp.dest('webadmin/dist/js')); }); +gulp.task('js-logs', function () { + // needs special treatment for error handling + var uglifyer = uglify(); + uglifyer.on('error', function (error) { + console.error(error); + }); + + gulp.src(['webadmin/src/js/logs.js', 'webadmin/src/js/client.js']) + .pipe(ejs({ oauth: oauth }, {}, { ext: '.js' })) + .pipe(sourcemaps.init()) + .pipe(concat('logs.js', { newLine: ';' })) + .pipe(uglifyer) + .pipe(sourcemaps.write()) + .pipe(gulp.dest('webadmin/dist/js')); +}); + gulp.task('js-setup', function () { // needs special treatment for error handling var uglifyer = uglify(); @@ -209,6 +225,7 @@ gulp.task('watch', ['default'], function () { gulp.watch(['webadmin/src/js/setup.js', 'webadmin/src/js/client.js'], ['js-setup']); gulp.watch(['webadmin/src/js/setupdns.js', 'webadmin/src/js/client.js'], ['js-setupdns']); gulp.watch(['webadmin/src/js/restore.js', 'webadmin/src/js/client.js'], ['js-restore']); + gulp.watch(['webadmin/src/js/logs.js', 'webadmin/src/js/client.js'], ['js-logs']); gulp.watch(['webadmin/src/js/index.js', 'webadmin/src/js/client.js', 'webadmin/src/js/appstore.js', 'webadmin/src/js/main.js', 'webadmin/src/views/*.js'], ['js-index']); gulp.watch(['webadmin/src/3rdparty/**/*'], ['3rdparty']); }); diff --git a/webadmin/src/js/logs.js b/webadmin/src/js/logs.js new file mode 100644 index 000000000..d0fc9b882 --- /dev/null +++ b/webadmin/src/js/logs.js @@ -0,0 +1,128 @@ +'use strict'; + +/* global moment */ + +// create main application module +var app = angular.module('Application', ['angular-md5', 'ui-notification']); + +app.controller('LogsController', ['$scope', '$timeout', '$location', 'Client', function ($scope, $timeout, $location, Client) { + var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {}); + + $scope.initialized = false; + $scope.installedApps = Client.getInstalledApps(); + $scope.client = Client; + $scope.logs = []; + $scope.selected = ''; + $scope.activeEventSource = null; + $scope.lines = 10; + $scope.selectedAppInfo = null; + + // Add built-in log types for now + $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') }); + + $scope.error = function (error) { + console.error(error); + window.location.href = '/error.html'; + }; + + function ab2str(buf) { + return String.fromCharCode.apply(null, new Uint16Array(buf)); + } + + $scope.clear = function () { + var logViewer = $('.logs-container'); + logViewer.empty(); + }; + + function showLogs() { + 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' }); + }; + }); + } + + Client.onApps(function () { + 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; + }); + }); + + Client.getStatus(function (error, status) { + if (error) return $scope.error(error); + + if (!status.activated) { + console.log('Not activated yet, redirecting', status); + window.location.href = '/'; + return; + } + + Client.refreshConfig(function (error) { + if (error) return $scope.error(error); + + // check version and force reload if needed + if (!localStorage.version) { + localStorage.version = Client.getConfig().version; + } else if (localStorage.version !== Client.getConfig().version) { + localStorage.version = Client.getConfig().version; + window.location.reload(true); + } + + Client.refreshInstalledApps(function (error) { + if (error) return $scope.error(error); + + Client.getInstalledApps().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 otherwise choose the first one + $scope.selected = $scope.logs.find(function (e) { return e.value === search.id; }); + if (!$scope.selected) $scope.selected = $scope.logs[0]; + + // now mark the Client to be ready + Client.setReady(); + + $scope.initialized = true; + + showLogs(); + }); + }); + }); +}]); diff --git a/webadmin/src/logs.html b/webadmin/src/logs.html new file mode 100644 index 000000000..f4112cecd --- /dev/null +++ b/webadmin/src/logs.html @@ -0,0 +1,67 @@ + + + + + + + + Cloudron Logs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+

{{ selected.name }} Logs

+ + + +
+ +
+ +
+ + + diff --git a/webadmin/src/theme.scss b/webadmin/src/theme.scss index 589076f61..d8b667123 100644 --- a/webadmin/src/theme.scss +++ b/webadmin/src/theme.scss @@ -1122,47 +1122,45 @@ footer { // Logs // ---------------------------- -.logs-controls { - margin-top: 25px; - margin-bottom: 10px; +.logs { + background: black; - .ng-isolate-scope { - float: left; - } + .logs-controls { + margin: 5px; - h3 { - display: inline-block; - margin-top: 6px; - margin-bottom: 0; - } - - select { - display: inline-block; - width: 250px; - margin-left: 20px; - } -} - -.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; + .ng-isolate-scope { + float: left; } - .time { - color: #00FFFF; + h3 { + margin: 5px 0; + color: white; + } + + select { + display: inline-block; + width: 250px; + } + } + + .logs-container { + flex-grow: 1; + margin-bottom: 5px; + color: white; + overflow: auto; + padding: 5px; + font-family: monospace; + + .log-line { + line-height: 1.2; + + &:hover { + background-color: #333333; + } + + .time { + color: #00FFFF; + } } } } diff --git a/webadmin/src/views/logs.html b/webadmin/src/views/logs.html deleted file mode 100644 index be3393800..000000000 --- a/webadmin/src/views/logs.html +++ /dev/null @@ -1,12 +0,0 @@ -
-
-

Logs

- - - - Download Logs -
-
- -
- diff --git a/webadmin/src/views/logs.js b/webadmin/src/views/logs.js deleted file mode 100644 index a2c0bd3fc..000000000 --- a/webadmin/src/views/logs.js +++ /dev/null @@ -1,119 +0,0 @@ -'use strict'; - -/* global moment */ - -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().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; - } - }); -}]);