Skip to content

Commit

Permalink
feat: started working on alerts feature
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikBjare committed Apr 27, 2022
1 parent d63d1bf commit b0d860f
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 11 deletions.
5 changes: 4 additions & 1 deletion src/components/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,12 @@ div(:class="{'fixed-top-padding': fixedTopMenu}")
b-dropdown-item(to="/trends")
icon(name="chart-line")
| Trends
b-dropdown-item(to="/Report")
b-dropdown-item(to="/report")
icon(name="chart-pie")
| Report
b-dropdown-item(to="/alerts")
icon(name="chart-bar")
| Alerts
b-dropdown-item(to="/query")
icon(name="code")
| Query
Expand Down
2 changes: 2 additions & 0 deletions src/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const Timeline = () => import('./views/Timeline.vue');
const Trends = () => import('./views/Trends.vue');
const Settings = () => import('./views/settings/Settings.vue');
const Stopwatch = () => import('./views/Stopwatch.vue');
const Alerts = () => import('./views/Alerts.vue');
const Search = () => import('./views/Search.vue');
const Report = () => import('./views/Report.vue');
const Dev = () => import('./views/Dev.vue');
Expand Down Expand Up @@ -56,6 +57,7 @@ const router = new VueRouter({
{ path: '/trends/:host', component: Trends, meta: { fullContainer: true } },
{ path: '/report', component: Report },
{ path: '/query', component: QueryExplorer },
{ path: '/alerts', component: Alerts },
{ path: '/settings', component: Settings },
{ path: '/stopwatch', component: Stopwatch },
{ path: '/search', component: Search },
Expand Down
1 change: 1 addition & 0 deletions src/store/modules/buckets.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const getters = {
},
getBucket: state => id => _.filter(state.buckets, b => b.id === id)[0],
bucketsByHostname: state => _.groupBy(state.buckets, 'hostname'),
getHostnames: state => _.map(state.buckets, 'hostname'),
};

// actions
Expand Down
19 changes: 19 additions & 0 deletions src/store/modules/categories.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const getters = {
);
},
get_category: state => category_arr => {
if (typeof category_arr === 'string' || category_arr instanceof String)
console.error('Passed category was string, expected array. Lookup will fail.');

const match = state.classes.find(c => _.isEqual(c.name, category_arr));
if (!match) {
if (!_.equals(category_arr, ['Uncategorized']))
Expand All @@ -48,6 +51,22 @@ const getters = {
get_category_by_id: state => id => {
return annotate(_.cloneDeep(state.classes.find(c => c.id == id)));
},
category_select: (state, getters) => insertMeta => {
// Useful for <select> elements enumerating categories
let cats = getters.all_categories;
cats = cats
.map(c => {
return { text: c.join(' > '), value: c };
})
.sort((a, b) => a.text > b.text);
if (insertMeta) {
cats = [
{ text: 'All', value: null },
{ text: 'Uncategorized', value: ['Uncategorized'] },
].concat(cats);
}
return cats;
},
};

// actions
Expand Down
187 changes: 187 additions & 0 deletions src/views/Alerts.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
<template lang="pug">
div
h3 Alerts

// TODO: Call this "goals" instead? (alerts is more general, but goals might fit the most common use better
// TODO: Support 'less than' goals
// TODO: Send notifications when goals met
// TODO: Query from day start, not 24h ago
b-alert(style="warning" show)
| This feature is still in early development.

b-alert(v-if="error" show variant="danger")
| {{error}}

b-card(v-for="alert in alerts")
div Name: {{ alert.name }}
div Category: {{ alert.category.join(" > ") }}
div Current: {{ alertTime(alert.category) | friendlyduration }} / {{alert.goal}} minutes
span(v-if="alertTime(alert.category) >= alert.goal")
icon(name="check" style="color: #0C0")
span(v-else)
icon(name="times" color="#555")

//div
// | {{alert_times}}
div
b-btn(@click="check") Check
small
b-form-checkbox(v-model="autorefresh", @change="toggleAutoRefresh", switch) Toggle autorefresh every 10s

small(v-if="last_updated")
| Last updated: {{ last_updated }}

hr

div
h4 New alert
| Name:
b-input(v-model="editing_alert.name")
| Category:
b-select(v-model="editing_alert.category")
option(v-for="category in categories" :value="category.value") {{ category.text }}
| Goal:
input(v-model="editing_alert.goal")
| minutes

div
b-btn(@click="addAlert")
icon(name="plus")
| Add alert
</template>

<style scoped lang="scss"></style>

<script>
import _ from 'lodash';
import moment from 'moment';
import { canonicalEvents } from '~/queries';
import { loadClassesForQuery } from '~/util/classes';
import 'vue-awesome/icons/plus';
import 'vue-awesome/icons/check';
import 'vue-awesome/icons/times';
export default {
name: 'Alerts',
data() {
return {
alerts: [{ name: 'Test', category: ['Work'], goal: 100 }],
editing_alert: {},
alert_times: {},
error: '',
hostnames: [],
hostname: 'erb-main2-arch',
last_updated: null,
autorefresh: false,
running_interval: null,
// Options
show_options: false,
use_regex: true,
filter_afk: true,
};
},
computed: {
categories: function () {
return this.$store.getters['categories/category_select'](true);
},
alertTime: function () {
return cat => {
let time = 0;
_.map(Object.entries(this.alert_times), ([c, t]) => {
if (c.startsWith(cat.join(','))) {
time += t;
}
});
return time;
};
},
},
mounted: async function () {
await this.$store.dispatch('buckets/ensureBuckets');
await this.$store.dispatch('categories/load');
this.hostnames = this.$store.getters['buckets/getHostnames'];
},
methods: {
addAlert: function () {
const new_alert = this.editing_alert;
this.alerts = this.alerts.concat(new_alert);
// TODO: Persist to settings/localstorage
},
toggleAutoRefresh: function () {
if (!this.autorefresh || this.running_interval) {
console.log('Stopping autorefresh');
clearInterval(this.running_interval);
this.autorefresh = false;
this.running_interval = null;
} else {
console.log('Starting autorefresh');
this.autorefresh = true;
this.running_interval = setInterval(this.check, 10000);
}
},
// Check current time of alert goals
check: async function () {
/*
const test_class = [['test', 'alert'], { type: 'regex', regex: 'aw-' }];
// TODO: Add alert classes
console.log(this.alerts);
const cats = _.map(this.alerts, a => {
const cat = this.$store.getters['categories/get_category'](a.category);
return [cat.name, cat.rule];
});
*/
const start = moment().subtract(1, 'day');
const stop = moment().add(1, 'day');
const classes = loadClassesForQuery();
let query = canonicalEvents({
bid_window: 'aw-watcher-window_' + this.hostname,
bid_afk: 'aw-watcher-afk_' + this.hostname,
filter_afk: this.filter_afk,
classes: classes,
filter_classes: null, // classes.map(c => c[0]),
});
query += '; RETURN = events;';
const query_array = query.split(';').map(s => s.trim() + ';');
const timeperiods = [start.format() + '/' + stop.format()];
try {
this.status = 'searching';
const data = await this.$aw.query(timeperiods, query_array);
console.log(data);
this.events = data[0];
this.error = '';
} catch (e) {
console.error(e);
this.error = e.response.data.message;
return;
} finally {
this.status = null;
}
let grouped = _.groupBy(this.events, e => e.data.$category);
const sumCats = Object.fromEntries(
_.map(Object.entries(grouped), entry => {
const [group, events] = entry;
return [group.split(','), _.sumBy(events, 'duration')];
})
);
this.alert_times = sumCats;
this.last_updated = new Date();
},
},
};
</script>
11 changes: 1 addition & 10 deletions src/views/activity/Activity.vue
Original file line number Diff line number Diff line change
Expand Up @@ -210,16 +210,7 @@ export default {
return this.$route.meta.subview;
},
categories: function () {
const cats = this.$store.getters['categories/all_categories'];
const entries = cats
.map(c => {
return { text: c.join(' > '), value: c };
})
.sort((a, b) => a.text > b.text);
return [
{ text: 'All', value: null },
{ text: 'Uncategorized', value: ['Uncategorized'] },
].concat(entries);
return this.$store.getters['categories/category_select'](true);
},
filterCategories: function () {
if (this.filterCategory) {
Expand Down

0 comments on commit b0d860f

Please sign in to comment.