1
0
mirror of https://github.com/mail-in-a-box/mailinabox.git synced 2026-03-04 15:54:48 +01:00

Initial commit of a log capture and reporting feature

This adds a new section to the admin panel called "Activity", that
supplies charts, graphs and details about messages entering and leaving
the host.

A new daemon captures details of system mail activity by monitoring
the /var/log/mail.log file, summarizing it into a sqllite database
that's kept in user-data.
This commit is contained in:
downtownallday
2021-01-11 18:02:07 -05:00
parent 73a2b72243
commit 2a0e50c8d4
108 changed files with 9027 additions and 6 deletions

View File

@@ -0,0 +1,92 @@
class Me {
/* construct with return value from GET /me */
constructor(me) {
Object.assign(this, me);
}
is_authenticated() {
return this.api_key || this.user_id;
}
get_email() {
return this.user_email || this.user_id;
}
};
/*
* axios interceptors for authentication
*/
function init_axios_interceptors() {
// requests: attach non-session based auth (admin panel)
axios.interceptors.request.use(request => {
var api_credentials = null;
// code from templates/index.html for "recall saved user
// credentials" (but, without the split(':'))
if (typeof sessionStorage != 'undefined' && sessionStorage.getItem("miab-cp-credentials"))
api_credentials = sessionStorage.getItem("miab-cp-credentials");
else if (typeof localStorage != 'undefined' && localStorage.getItem("miab-cp-credentials"))
api_credentials = localStorage.getItem("miab-cp-credentials");
// end
if (api_credentials) {
request.headers.authorization = 'Basic ' + window.btoa(api_credentials);
}
return request;
});
// reponses: redirect on authorization failure
axios.interceptors.response.use(
response => {
if (response.data &&
response.data.status === 'invalid' &&
response.config.headers.authorization)
{
var url = response.config.url;
if (response.config.baseURL) {
var sep = ( response.config.baseURL.substr(-1) != '/' ?
'/' : '' );
url = response.config.baseURL + sep + url;
}
if (url == '/admin/me')
{
// non-session/admin login
throw new AuthenticationError(
null,
'not authenticated',
response
);
}
}
return response;
},
error => {
const auth_required_msg = 'Authentication required - you have been logged out of the server';
if (! error.response) {
throw error;
}
if (error.response.status == 403 &&
error.response.data == 'login_required')
{
// session login
throw new AuthenticationError(error, auth_required_msg);
}
else if ((error.response.status == 403 ||
error.response.status == 401) &&
error.response.data &&
error.response.data.status == 'error')
{
// admin
throw new AuthenticationError(error, auth_required_msg);
}
throw error;
}
);
}

View File

@@ -0,0 +1,18 @@
class ValueError extends Error {
constructor(msg) {
super(msg);
}
};
class AssertionError extends Error {
}
class AuthenticationError extends Error {
constructor(caused_by_error, msg, response) {
super(msg);
this.caused_by = caused_by_error;
this.response = response;
if (!response && caused_by_error)
this.response = caused_by_error.response;
}
};

View File

@@ -0,0 +1,11 @@
<div class="text-center">
<h2 class="mb-0">{{ header_text }}</h2>
<small class="text-white-50">from the <a href="https://github.com/downtownallday/mailinabox-ldap/" target="_blank" class="text-white-50">MiaB-LDAP project</a></small>
<div class="d-flex align-items-center justify-content-between" style="margin-top:-1.5rem">
<div class="ml-1" style="min-width:1em">
<spinner v-show="loading_counter > 0"></spinner>
</div>
<slot name="links"><div>&nbsp;</div></slot>
</div>
</div>

View File

@@ -0,0 +1,19 @@
Vue.component('spinner', {
template: '<span class="spinner-border spinner-border-sm"></span>'
});
Vue.component('page-header', function(resolve, reject) {
axios.get('ui-common/page-header.html').then((response) => { resolve({
props: {
header_text: { type: String, required: true },
loading_counter: { type: Number, required: true }
},
template: response.data
})}).catch((e) => {
reject(e);
});
});

View File

@@ -0,0 +1,8 @@
<div>
<div class="bg-dark text-white p-1">
<slot name="header"></slot>
</div>
<div class="bg-light text-dark p-1">
<slot></slot>
</div>
</div>

View File

@@ -0,0 +1,10 @@
Vue.component('page-layout', function(resolve, reject) {
axios.get('ui-common/page-layout.html').then((response) => { resolve({
template: response.data,
})}).catch((e) => {
reject(e);
});
});

1
management/ui-common/theme/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules/

View File

@@ -0,0 +1,35 @@
#!/bin/bash
#
# install bootstrap sources
#
if [ ! -e "node_modules/bootstrap" ]; then
npm install bootstrap
if [ $? -ne 0 ]; then
echo "Installing bootstrap using npm failed. Is npm install on your system?"
exit 1
fi
fi
#
# install sass compiler
#
compiler="/usr/bin/sassc"
if [ ! -x "$compiler" ]; then
sudo apt-get install sassc || exit 1
fi
#
# compile our theme
#
b_dir="node_modules/bootstrap/scss"
$compiler -I "$b_dir" --sourcemap --style compressed theme.scss ../ui-bootstrap.css
if [ $? -eq 0 ]; then
echo "SUCCESS - files:"
ls -sh ../ui-bootstrap.*
fi

View File

@@ -0,0 +1,14 @@
{
"name": "theme",
"version": "1.0.0",
"description": "",
"dependencies": {
"bootstrap": "^4.5.3"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "GPL-3.0-or-later"
}

View File

@@ -0,0 +1,95 @@
/* variable overrides */
$white: #fff;
$gray-100: #f8f9fa;
$gray-200: #e9ecef;
$gray-300: #dee2e6;
$gray-400: #ced4da;
$gray-500: #adb5bd;
$gray-600: #6c757d;
$gray-700: #495057;
$gray-800: #343a40;
$gray-900: #212529;
$black: #000;
$blue: cadetblue;
$primary: #446599; //#96a5b2;
$secondary: $gray-600; //#d8dfe5; //#f5f5f5;
$success: #b7dfb8;
$info: #ffd6b0;
$warning: #f0e68c;
$danger: #a91409;
$light: $gray-100;
$dark: #303e45;
$body-bg: $gray-100; //$blue;
$body-color: $dark;
$card-color: $gray-900;
$card-spacer-x: 0.75rem;
$card-spacer-y: 0.25rem;
$table-color: $dark;
$table-cell-padding: 0.375rem; // .75rem;
$table-cell-padding-sm: 0.15rem; // .3rem;
$font-size-base: 0.85rem; // Assumes the browser default, typically `16px`
$input-btn-padding-y: .25rem; // .375rem !default;
$input-btn-padding-x: .5rem; // .75rem !default;
$input-btn-padding-y-sm: .12rem; // .25rem !default;
$input-btn-padding-x-sm: .25rem; // .5rem !default;
//$input-btn-padding-y-lg: .5rem !default;
//$input-btn-padding-x-lg: 1rem !default;
$alert-padding-y: .25rem;
$alert-padding-x: .75rem;
// $list-group-item-padding-y: .75rem !default;
// $list-group-item-padding-x: 1.25rem !default;
$nav-link-padding-y: 0.20rem;
/* bootstrap styles that we want */
@import "functions";
@import "variables";
@import "mixins";
@import "root";
@import "reboot";
@import "type";
@import "images";
@import "code";
@import "grid";
@import "tables";
@import "forms";
@import "buttons";
@import "transitions";
@import "dropdown";
@import "button-group";
@import "input-group";
/* @import "custom-forms"; */
@import "nav";
@import "navbar";
@import "card";
@import "breadcrumb";
@import "pagination";
@import "badge";
/* @import "jumbotron"; */
@import "alert";
@import "progress";
/* @import "media"; */
@import "list-group";
@import "close";
@import "toasts";
@import "modal";
@import "tooltip";
@import "popover";
/* @import "carousel"; */
@import "spinners";
@import "utilities";
/* @import "print"; */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,30 @@
:hover {
--hover-bg-color: #ccc;
}
/* fix visibility of calendar icon in bootstrap vue component */
.b-form-datepicker g {
fill: var(--dark);
}
/* make table header sticky at 0 */
.sticky-table-header-0 thead {
position: sticky;
background-color: #fff;
top: 0;
}
.cursor-pointer {
cursor:pointer;
}
.cursor-default {
cursor:default;
}
.clickme {
cursor:pointer;
}
.clickme:hover {
background-color: var(--hover-bg-color);
}