pull/429/head
Hunter Long 2020-01-29 20:34:47 -08:00
parent 8061d91fd7
commit 6d3e204131
22 changed files with 356 additions and 168 deletions

View File

@ -44,11 +44,11 @@ func checkServices() {
func (s *Service) Check(record bool) { func (s *Service) Check(record bool) {
switch s.Type { switch s.Type {
case "http": case "http":
s.checkHttp(record) s.CheckHttp(record)
case "tcp", "udp": case "tcp", "udp":
s.checkTcp(record) s.CheckTcp(record)
case "icmp": case "icmp":
s.checkIcmp(record) s.CheckIcmp(record)
} }
} }
@ -122,7 +122,7 @@ func isIPv6(address string) bool {
} }
// checkIcmp will send a ICMP ping packet to the service // checkIcmp will send a ICMP ping packet to the service
func (s *Service) checkIcmp(record bool) *Service { func (s *Service) CheckIcmp(record bool) *Service {
p := fastping.NewPinger() p := fastping.NewPinger()
resolveIP := "ip4:icmp" resolveIP := "ip4:icmp"
if isIPv6(s.Domain) { if isIPv6(s.Domain) {
@ -148,7 +148,7 @@ func (s *Service) checkIcmp(record bool) *Service {
} }
// checkTcp will check a TCP service // checkTcp will check a TCP service
func (s *Service) checkTcp(record bool) *Service { func (s *Service) CheckTcp(record bool) *Service {
dnsLookup, err := s.dnsCheck() dnsLookup, err := s.dnsCheck()
if err != nil { if err != nil {
if record { if record {
@ -188,7 +188,7 @@ func (s *Service) checkTcp(record bool) *Service {
} }
// checkHttp will check a HTTP service // checkHttp will check a HTTP service
func (s *Service) checkHttp(record bool) *Service { func (s *Service) CheckHttp(record bool) *Service {
dnsLookup, err := s.dnsCheck() dnsLookup, err := s.dnsCheck()
if err != nil { if err != nil {
if record { if record {

View File

@ -5,7 +5,7 @@
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "rm -rf dist && cross-env NODE_ENV=production webpack", "build": "rm -rf dist && cross-env NODE_ENV=production webpack",
"dev": "cross-env NODE_ENV=development webpack-dev-server --progress", "dev": "cross-env NODE_ENV=development webpack-dev-server --host 0.0.0.0 --port 8888 --progress",
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
@ -22,6 +22,7 @@
"querystring": "^0.2.0", "querystring": "^0.2.0",
"vue": "^2.6.10", "vue": "^2.6.10",
"vue-apexcharts": "^1.5.2", "vue-apexcharts": "^1.5.2",
"vue-flatpickr-component": "^8.1.5",
"vue-moment": "^4.1.0", "vue-moment": "^4.1.0",
"vue-router": "~3.0", "vue-router": "~3.0",
"vuedraggable": "^2.23.2", "vuedraggable": "^2.23.2",

View File

@ -64,6 +64,10 @@ class Api {
return axios.post('/api/groups', data).then(response => (response.data)) return axios.post('/api/groups', data).then(response => (response.data))
} }
async group_update (data) {
return axios.post('/api/groups/'+data.id, data).then(response => (response.data))
}
async users () { async users () {
return axios.get('/api/users').then(response => (response.data)) return axios.get('/api/users').then(response => (response.data))
} }
@ -72,6 +76,10 @@ class Api {
return axios.post('/api/users', data).then(response => (response.data)) return axios.post('/api/users', data).then(response => (response.data))
} }
async user_update (data) {
return axios.post('/api/users/'+data.id, data).then(response => (response.data))
}
async user_delete (id) { async user_delete (id) {
return axios.delete('/api/users/'+id).then(response => (response.data)) return axios.delete('/api/users/'+id).then(response => (response.data))
} }
@ -80,6 +88,14 @@ class Api {
return axios.get('/api/messages').then(response => (response.data)) return axios.get('/api/messages').then(response => (response.data))
} }
async message_create (data) {
return axios.post('/api/messages', data).then(response => (response.data))
}
async message_update (data) {
return axios.post('/api/messages/'+data.id, data).then(response => (response.data))
}
async message_delete (id) { async message_delete (id) {
return axios.delete('/api/messages/'+id).then(response => (response.data)) return axios.delete('/api/messages/'+id).then(response => (response.data))
} }

View File

@ -16,13 +16,13 @@
<tr v-for="(message, index) in $store.getters.messages" v-bind:key="index"> <tr v-for="(message, index) in $store.getters.messages" v-bind:key="index">
<td>{{message.title}}</td> <td>{{message.title}}</td>
<td class="d-none d-md-table-cell"> <td class="d-none d-md-table-cell">
<router-link to="/service/${service(message.service).id}">{{service(message.service)}}</router-link> <router-link :to="serviceLink(message.service)">{{service(message.service)}}</router-link>
</td> </td>
<td class="d-none d-md-table-cell">{{message.start_on}}</td> <td class="d-none d-md-table-cell">{{message.start_on}}</td>
<td class="text-right"> <td class="text-right">
<div class="btn-group"> <div class="btn-group">
<a href="message/1" class="btn btn-outline-secondary"><i class="fas fa-exclamation-triangle"></i> Edit</a> <a @click.prevent="editMessage(message, edit)" href="#" class="btn btn-outline-secondary"><i class="fas fa-exclamation-triangle"></i> Edit</a>
<a @click="deleteMessage(message)" href="#" class="btn btn-danger"><font-awesome-icon icon="times" /></a> <a @click.prevent="deleteMessage(message)" href="#" class="btn btn-danger"><font-awesome-icon icon="times" /></a>
</div> </div>
</td> </td>
</tr> </tr>
@ -31,15 +31,8 @@
</table> </table>
</div> </div>
<FormMessage :edit="editChange" :in_message="message"/>
<div class="col-12">
<h1 class="text-black-50 mt-5">Create Message</h1>
<div class="card">
<div class="card-body">
<FormMessage/>
</div>
</div>
</div>
</div> </div>
</template> </template>
@ -52,12 +45,22 @@
components: {FormMessage}, components: {FormMessage},
data () { data () {
return { return {
edit: false,
message: {}
} }
}, },
methods: { methods: {
editChange(v) {
this.message = {}
this.edit = v
},
editMessage(m, mode) {
this.message = m
this.edit = !mode
},
service (id) { service (id) {
return this.$store.getters.serviceById(id).name || "" const s = this.$store.getters.serviceById(id) || {}
return s.name || "Global Message"
}, },
async deleteMessage(m) { async deleteMessage(m) {
let c = confirm(`Are you sure you want to delete message '${m.title}'?`) let c = confirm(`Are you sure you want to delete message '${m.title}'?`)

View File

@ -6,6 +6,7 @@
<i class="fas fa-plus"></i> Create <i class="fas fa-plus"></i> Create
</router-link> </router-link>
</h1> </h1>
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
@ -16,7 +17,7 @@
<th scope="col"></th> <th scope="col"></th>
</tr> </tr>
</thead> </thead>
<draggable @update="log" tag="tbody" v-model="servicesList" :list="$store.getters.servicesInOrder" :key="this.$store.getters.servicesInOrder.length" class="sortable" handle=".drag_icon"> <draggable tag="tbody" v-model="servicesList" :list="$store.getters.servicesInOrder" :key="this.$store.getters.servicesInOrder.length" class="sortable" handle=".drag_icon">
<tr v-for="(service, index) in $store.getters.services" :key="index"> <tr v-for="(service, index) in $store.getters.services" :key="index">
<td> <td>
<span class="drag_icon d-none d-md-inline"> <span class="drag_icon d-none d-md-inline">
@ -40,7 +41,7 @@
<router-link :to="{path: `/dashboard/edit_service/${service.id}`, params: {service: service} }" class="btn btn-outline-secondary"> <router-link :to="{path: `/dashboard/edit_service/${service.id}`, params: {service: service} }" class="btn btn-outline-secondary">
<i class="fas fa-chart-area"></i> View <i class="fas fa-chart-area"></i> View
</router-link> </router-link>
<a @click="deleteService(service)" href="#" class="btn btn-danger"> <a @click.prevent="deleteService(service)" href="#" class="btn btn-danger">
<font-awesome-icon icon="times" /> <font-awesome-icon icon="times" />
</a> </a>
</div> </div>
@ -74,8 +75,8 @@
</td> </td>
<td class="text-right"> <td class="text-right">
<div class="btn-group"> <div class="btn-group">
<a href="group/2" class="btn btn-outline-secondary"><font-awesome-icon icon="chart-area" /> Edit</a> <a @click.prevent="editGroup(group, edit)" href="#" class="btn btn-outline-secondary"><font-awesome-icon icon="chart-area" /> Edit</a>
<a @click="deleteGroup(group)" href="#" class="btn btn-danger"> <a @click.prevent="deleteGroup(group)" href="#" class="btn btn-danger">
<font-awesome-icon icon="times" /> <font-awesome-icon icon="times" />
</a> </a>
</div> </div>
@ -85,13 +86,7 @@
</draggable> </draggable>
</table> </table>
<h1 class="text-muted mt-5">Create Group</h1> <FormGroup :edit="editChange" :in_group="group"/>
<div class="card">
<div class="card-body">
<FormGroup/>
</div>
</div>
</div> </div>
</div> </div>
@ -112,7 +107,8 @@
}, },
data () { data () {
return { return {
edit: false,
group: {}
} }
}, },
computed: { computed: {
@ -149,6 +145,14 @@
}, },
methods: { methods: {
editChange(v) {
this.group = {}
this.edit = v
},
editGroup(g, mode) {
this.group = g
this.edit = !mode
},
reordered_services() { reordered_services() {
}, },

View File

@ -14,15 +14,15 @@
<td>{{user.username}}</td> <td>{{user.username}}</td>
<td class="text-right"> <td class="text-right">
<div class="btn-group"> <div class="btn-group">
<a @click="editUser(user)" href="#" class="btn btn-outline-secondary"><font-awesome-icon icon="user" /> Edit</a> <a @click.prevent="editUser(user, edit)" href="" class="btn btn-outline-secondary"><font-awesome-icon icon="user" /> Edit</a>
<a @click="deleteUser(user)" href="#" class="btn btn-danger"><font-awesome-icon icon="times" /></a> <a v-if="index !== 0" @click.prevent="deleteUser(user)" href="" class="btn btn-danger"><font-awesome-icon icon="times" /></a>
</div> </div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<FormUser :in_user="user"/> <FormUser :edit="editChange" :in_user="user"/>
</div> </div>
</template> </template>
@ -36,13 +36,17 @@
data () { data () {
return { return {
edit: false, edit: false,
user: null user: {}
} }
}, },
methods: { methods: {
editUser(u) { editChange(v) {
this.edit = true this.user = {}
this.edit = v
},
editUser(u, mode) {
this.user = u this.user = u
this.edit = !mode
}, },
async deleteUser(u) { async deleteUser(u) {
let c = confirm(`Are you sure you want to delete user '${u.username}'?`) let c = confirm(`Are you sure you want to delete user '${u.username}'?`)

View File

@ -1,6 +1,10 @@
<template> <template>
<div> <div class="col-12">
<FormService v-if="ready" :in_service="service"/> <div class="card">
<div class="card-body">
<FormService :in_service="service"/>
</div>
</div>
</div> </div>
</template> </template>
@ -25,15 +29,16 @@
data () { data () {
return { return {
ready: false, ready: false,
service: null service: {}
} }
}, },
computed: { computed: {
}, },
async beforeCreate() { async beforeCreate() {
if (this.$route.params.id) { const id = this.$route.params.id
this.service = await Api.service(this.$route.params.id) if (id) {
this.service = await Api.service(id)
} }
this.ready = true this.ready = true
}, },

View File

@ -1,8 +1,10 @@
<template v-if="service"> <template v-if="service">
<div class="col-12 card mb-3" style="min-height: 260px"> <div class="col-12 card mb-3" style="min-height: 260px">
<div class="card-body"> <div class="card-body">
<h5 class="card-title"><a href="service/7">{{service.name}}</a> <h5 class="card-title"><router-link :to="serviceLink(service)">{{service.name}}</router-link>
<span class="badge float-right badge-success">{{service.online ? "ONLINE" : "OFFLINE"}}</span> <span class="badge float-right" :class="{'badge-success': service.online, 'badge-danger': !service.online}">
{{service.online ? "ONLINE" : "OFFLINE"}}
</span>
</h5> </h5>
<div class="row"> <div class="row">
<div class="col-md-3 col-sm-6"> <div class="col-md-3 col-sm-6">

View File

@ -2,7 +2,7 @@
<footer> <footer>
<div class="footer text-center mb-4 p-2"> <div class="footer text-center mb-4 p-2">
<a href="https://github.com/hunterlong/statping" target="_blank">Statping {{version}} made with <i class="text-danger fas fa-heart"></i></a> | <a href="https://github.com/hunterlong/statping" target="_blank">Statping {{version}} made with <i class="text-danger fas fa-heart"></i></a> |
<router-link to="/dashboard">Dashboard</router-link> <a href="/dashboard">Dashboard</a>
</div> </div>
</footer> </footer>
</template> </template>

View File

@ -35,7 +35,7 @@
<span class="d-none d-md-inline">Online, last Failure was Wednesday 1:16:49PM, Dec 18 2019</span> <span class="d-none d-md-inline">Online, last Failure was Wednesday 1:16:49PM, Dec 18 2019</span>
</div> </div>
<div class="col-sm-12 col-md-2"> <div class="col-sm-12 col-md-2">
<router-link :to="`/service/${service.id}`" class="btn btn-success btn-sm float-right dyn-dark btn-block">View Service</router-link> <router-link :to="serviceLink(service)" class="btn btn-success btn-sm float-right dyn-dark btn-block">View Service</router-link>
</div> </div>
</div> </div>

View File

@ -84,7 +84,7 @@
<div class="form-group row mt-3"> <div class="form-group row mt-3">
<label class="col-sm-3 col-form-label">API Key</label> <label class="col-sm-3 col-form-label">API Key</label>
<div class="col-sm-9"> <div class="col-sm-9">
<input v-model="core.api_key" type="text" class="form-control select-input" readonly> <input v-model="core.api_key" @focus="$event.target.select()" type="text" class="form-control select-input" readonly>
<small class="form-text text-muted">API Key can be used for read only routes</small> <small class="form-text text-muted">API Key can be used for read only routes</small>
</div> </div>
</div> </div>
@ -92,7 +92,7 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-3 col-form-label">API Secret</label> <label class="col-sm-3 col-form-label">API Secret</label>
<div class="col-sm-9"> <div class="col-sm-9">
<input v-model="core.api_secret" type="text" class="form-control select-input" readonly> <input v-model="core.api_secret" @focus="$event.target.select()" type="text" class="form-control select-input" readonly>
<small class="form-text text-muted">API Secret is used for read, create, update and delete routes</small> <small class="form-text text-muted">API Secret is used for read, create, update and delete routes</small>
<small class="form-text text-muted">You can <a href="#" @click="renewApiKeys">Regenerate API Keys</a> if you need to.</small> <small class="form-text text-muted">You can <a href="#" @click="renewApiKeys">Regenerate API Keys</a> if you need to.</small>
</div> </div>
@ -102,21 +102,15 @@
</template> </template>
<script> <script>
import time from '../components/Time' import Api from '../components/API'
import Api from '../components/API'
export default { export default {
name: 'CoreSettings', name: 'CoreSettings',
data () { data () {
return { return {
core: null, core: this.$store.getters.core,
} }
}, },
async created() {
const core = await Api.core()
this.core = core
this.$store.commit('setCore', core)
},
async mounted () { async mounted () {
}, },
@ -126,7 +120,6 @@ export default {
const c = this.core const c = this.core
const coreForm = {name: c.name, description: c.description, domain: c.domain, const coreForm = {name: c.name, description: c.description, domain: c.domain,
timezone: c.timezone, using_cdn: c.using_cdn, footer: c.footer, update_notify: c.update_notify} timezone: c.timezone, using_cdn: c.using_cdn, footer: c.footer, update_notify: c.update_notify}
alert(JSON.stringify(coreForm))
await Api.core_save(coreForm) await Api.core_save(coreForm)
const core = await Api.core() const core = await Api.core()
this.$store.commit('setCore', core) this.$store.commit('setCore', core)
@ -140,7 +133,10 @@ export default {
this.$store.commit('setCore', core) this.$store.commit('setCore', core)
this.core = core this.core = core
} }
} },
selectAll() {
this.$refs.input.select();
}
} }
} }
</script> </script>

View File

@ -1,9 +1,17 @@
<template> <template>
<div>
<h1 class="text-muted mt-5">
{{group.id ? `Update ${group.name}` : "Create Group"}}
<button @click="removeEdit" v-if="group.id" class="mt-3 btn float-right btn-danger btn-sm">Close</button>
</h1>
<div class="card">
<div class="card-body">
<form @submit="saveGroup"> <form @submit="saveGroup">
<div class="form-group row"> <div class="form-group row">
<label for="title" class="col-sm-4 col-form-label">Group Name</label> <label for="title" class="col-sm-4 col-form-label">Group Name</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input v-model="group.name" type="text" name="name" class="form-control" value="" id="title" placeholder="Group Name" required> <input v-model="group.name" type="text" class="form-control" id="title" placeholder="Group Name" required>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
@ -17,21 +25,29 @@
</div> </div>
<div class="form-group row"> <div class="form-group row">
<div class="col-sm-12"> <div class="col-sm-12">
<button @click="saveGroup" type="submit" class="btn btn-primary btn-block">Create Group</button> <button @click="saveGroup" type="submit" class="btn btn-block" :class="{'btn-primary': !group.name, 'btn-secondary': group.name}">
{{group.id ? "Update Group" : "Create Group"}}
</button>
</div> </div>
</div> </div>
<div class="alert alert-danger d-none" id="alerter" role="alert"></div> <div class="alert alert-danger d-none" id="alerter" role="alert"></div>
</form> </form>
</div>
</div>
</div>
</template> </template>
<script> <script>
import Api from "../components/API"; import Api from "../components/API";
export default { export default {
name: 'FormGroup', name: 'FormGroup',
props: { props: {
in_group: { in_group: {
type: Object type: Object
},
edit: {
type: Function
} }
}, },
data () { data () {
@ -42,20 +58,41 @@ export default {
} }
} }
}, },
mounted() { watch: {
if (this.props.in_group) { in_group() {
this.group = this.props.in_group this.group = this.in_group
} }
}, },
methods: { methods: {
removeEdit() {
this.group = {}
this.edit(false)
},
async saveGroup(e) { async saveGroup(e) {
e.preventDefault(); e.preventDefault();
if (this.in_group) {
await this.updateGroup()
} else {
await this.createGroup()
}
},
async createGroup() {
const g = this.group const g = this.group
const data = {name: g.name, public: g.public} const data = {name: g.name, public: g.public}
await Api.group_create(data) await Api.group_create(data)
const groups = await Api.groups() const groups = await Api.groups()
this.$store.commit('setGroups', groups) this.$store.commit('setGroups', groups)
this.group = {}
}, },
async updateGroup() {
const g = this.group
const data = {id: g.id, name: g.name, public: g.public}
await Api.group_update(data)
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
this.edit(false)
}
} }
} }
</script> </script>

View File

@ -1,56 +1,42 @@
<template> <template>
<form class="ajax_form" action="api/messages" data-redirect="messages" method="POST"> <div class="col-12">
<h1 class="text-black-50 mt-5">
{{message.id ? `Update ${message.title}` : "Create Message"}}
<button @click="removeEdit" v-if="message.id" class="mt-3 btn float-right btn-danger btn-sm">Close</button>
</h1>
<div class="card">
<div class="card-body">
<form @submit="saveMessage">
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-4 col-form-label">Title</label> <label class="col-sm-4 col-form-label">Title</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="text" name="title" class="form-control" value="" id="title" placeholder="Message Title" required> <input v-model="message.title" type="text" name="title" class="form-control" id="title" placeholder="Message Title" required>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-4 col-form-label">Description</label> <label class="col-sm-4 col-form-label">Description</label>
<div class="col-sm-8"> <div class="col-sm-8">
<textarea rows="5" name="description" class="form-control" id="description" required></textarea> <textarea v-model="message.description" rows="5" name="description" class="form-control" id="description" required></textarea>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-4 col-form-label">Message Date Range</label> <label class="col-sm-4 col-form-label">Message Date Range</label>
<div class="col-sm-4"> <div class="col-sm-4">
<input type="text" name="start_on" class="form-control form-control-plaintext" id="start_on" value="0001-01-01T00:00:00Z" required> <flatPickr v-model="message.start_on" :config="config" type="text" name="start_on" class="form-control form-control-plaintext" id="start_on" value="0001-01-01T00:00:00Z" required />
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
<input type="text" name="end_on" class="form-control form-control-plaintext" id="end_on" value="0001-01-01T00:00:00Z" required> <flatPickr v-model="message.end_on" :config="config" type="text" name="end_on" class="form-control form-control-plaintext" id="end_on" value="0001-01-01T00:00:00Z" required />
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="service_id" class="col-sm-4 col-form-label">Service</label> <label for="service_id" class="col-sm-4 col-form-label">Service</label>
<div class="col-sm-8"> <div class="col-sm-8">
<select class="form-control" name="service" id="service_id"> <select v-model="message.service_id" class="form-control" name="service" id="service_id">
<option value="0" selected>Global Message</option> <option :value="0">Global Message</option>
<option v-for="(service, index) in $store.getters.services" :value="service.id" v-bind:key="index" >{{service.name}}</option>
<option value="7" >Statping API</option>
<option value="6" >Push Notification Server</option>
<option value="1" >Google</option>
<option value="2" >Statping Github</option>
<option value="3" >JSON Users Test</option>
<option value="4" >JSON API Tester</option>
<option value="5" >Google DNS</option>
</select> </select>
</div> </div>
</div> </div>
@ -58,15 +44,15 @@
<div class="form-group row"> <div class="form-group row">
<label for="notify_method" class="col-sm-4 col-form-label">Notification Method</label> <label for="notify_method" class="col-sm-4 col-form-label">Notification Method</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="text" name="notify_method" class="form-control" id="notify_method" value="" placeholder="email"> <input v-model="message.notify_method" type="text" name="notify_method" class="form-control" id="notify_method" value="" placeholder="email">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="notify_method" class="col-sm-4 col-form-label">Notify Users</label> <label for="notify_method" class="col-sm-4 col-form-label">Notify Users</label>
<div class="col-sm-8"> <div class="col-sm-8">
<span class="switch"> <span @click="message.notify = !!message.notify" class="switch">
<input @click="message.notify = !!message.notify" type="checkbox" class="switch" id="switch-normal"> <input v-model="message.notify" type="checkbox" class="switch" id="switch-normal">
<label for="switch-normal">Notify Users Before Scheduled Time</label> <label for="switch-normal">Notify Users Before Scheduled Time</label>
</span> </span>
</div> </div>
@ -76,8 +62,8 @@
<label for="notify_before" class="col-sm-4 col-form-label">Notify Before</label> <label for="notify_before" class="col-sm-4 col-form-label">Notify Before</label>
<div class="col-sm-8"> <div class="col-sm-8">
<div class="form-inline"> <div class="form-inline">
<input type="number" name="notify_before" class="col-4 form-control" id="notify_before" value="0"> <input v-model="message.notify_before" type="number" name="notify_before" class="col-4 form-control" id="notify_before">
<select class="ml-2 col-7 form-control" name="notify_before_scale" id="notify_before_scale"> <select v-model="message.notify_before_scale" class="ml-2 col-7 form-control" name="notify_before_scale" id="notify_before_scale">
<option value="minute">Minutes</option> <option value="minute">Minutes</option>
<option value="hour">Hours</option> <option value="hour">Hours</option>
<option value="day">Days</option> <option value="day">Days</option>
@ -88,42 +74,91 @@
<div class="form-group row"> <div class="form-group row">
<div class="col-sm-12"> <div class="col-sm-12">
<button type="submit" class="btn btn-primary btn-block">Create Message</button> <button @click="saveMessage" type="submit" class="btn btn-block" :class="{'btn-primary': !message.id, 'btn-secondary': message.id}">
{{message.id ? "Edit Message" : "Create Message"}}
</button>
</div> </div>
</div> </div>
<div class="alert alert-danger d-none" id="alerter" role="alert"></div> <div class="alert alert-danger d-none" id="alerter" role="alert"></div>
</form> </form>
</div>
</div>
{{JSON.stringify(temp)}}
</div>
</template> </template>
<script> <script>
import Api from "../components/API"; import Api from "../components/API";
import flatPickr from 'vue-flatpickr-component';
import 'flatpickr/dist/flatpickr.css';
export default { export default {
name: 'FormMessage', name: 'FormMessage',
components: {
flatPickr
},
props: { props: {
in_message: {
type: Object
},
edit: {
type: Function
}
}, },
data () { data () {
return { return {
group: { message: {
name: "", title: "",
public: true description: "",
} start_on: new Date(),
end_on: new Date(),
service_id: 0,
notify_method: "",
notify: false,
notify_before: 0,
notify_before_scale: "minute",
},
config: {
altFormat: "Y-m-d H:iK",
altInput: true,
enableTime: true,
dateFormat: "Z",
},
temp: {}
} }
}, },
mounted() { watch: {
if (this.props.group) { in_message() {
this.group = this.props.group this.message = this.in_message
} }
}, },
methods: { methods: {
async saveGroup(e) { removeEdit() {
e.preventDefault(); this.message = {}
const data = {name: this.group.name, public: this.group.public} this.edit(false)
await Api.group_create(data)
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
}, },
async saveMessage(e) {
e.preventDefault();
if (this.message.id) {
await this.updateMessage()
} else {
await this.createMessage()
}
},
async createMessage() {
await Api.message_create(this.message)
const messages = await Api.messages()
this.$store.commit('setMessages', messages)
this.message = {}
},
async updateMessage() {
await Api.message_update(this.message)
const messages = await Api.messages()
this.$store.commit('setMessages', messages)
this.edit(false)
}
} }
} }
</script> </script>

View File

@ -38,7 +38,7 @@
</div> </div>
</div> </div>
<h4 class="mt-5 mb-5 text-muted">Request Details</h4> <h4 v-if="service.type !== 'icmp'" class="mt-5 mb-5 text-muted">Request Details</h4>
<div v-if="service.type.match(/^(http)$/)" class="form-group row"> <div v-if="service.type.match(/^(http)$/)" class="form-group row">
<label class="col-sm-4 col-form-label">Service Check Type</label> <label class="col-sm-4 col-form-label">Service Check Type</label>
@ -82,7 +82,7 @@
</div> </div>
</div> </div>
<div v-if="service.type.match(/^(tcp|udp)$/)" class="form-group row"> <div v-if="service.type.match(/^(tcp|udp)$/)" class="form-group row">
<label class="col-sm-4 col-form-label">TCP Port</label> <label class="col-sm-4 col-form-label">{{service.type.toUpperCase()}} Port</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input v-model="service.port" type="number" name="port" class="form-control" id="service_port" placeholder="8080"> <input v-model="service.port" type="number" name="port" class="form-control" id="service_port" placeholder="8080">
</div> </div>
@ -146,8 +146,12 @@
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<div class="col-12"> <div class="col-6">
<button @click="saveService" type="submit" class="btn btn-success btn-block">Create Service</button> <button @click.prevent="saveService" type="submit" class="btn btn-success btn-block">Create Service</button>
</div>
<div class="col-6">
<button @click.prevent="saveService" class="btn btn-secondary btn-block">Test</button>
</div> </div>
</div> </div>
@ -178,7 +182,7 @@
check_interval: 60, check_interval: 60,
timeout: 15, timeout: 15,
permalink: "", permalink: "",
order: 0, order: 1,
verify_ssl: true, verify_ssl: true,
allow_notifications: true, allow_notifications: true,
public: true, public: true,
@ -186,16 +190,17 @@
groups: [], groups: [],
} }
}, },
props: { props: {
in_service: { in_service: {
type: Object, type: Object
required: false, }
} },
}, watch: {
async created() { in_service() {
if (this.props.in_service) { this.service = this.in_service
this.service = this.props.in_service }
} },
async mounted() {
if (!this.$store.getters.groups) { if (!this.$store.getters.groups) {
const groups = await Api.groups() const groups = await Api.groups()
this.$store.commit('setGroups', groups) this.$store.commit('setGroups', groups)
@ -211,7 +216,10 @@
delete s.last_success delete s.last_success
delete s.latency delete s.latency
delete s.online_24_hours delete s.online_24_hours
await Api.service_save() await Api.service_create(s)
},
async testService(e) {
} }
} }
} }

View File

@ -6,7 +6,7 @@
<div class="col-12"> <div class="col-12">
<form @submit=""> <form @submit="saveSetup">
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<div class="form-group"> <div class="form-group">
@ -147,15 +147,13 @@
this.loading = false this.loading = false
return return
} }
await this.completeAuth()
this.loading = false
this.$router.push('')
},
async completeAuth() {
const auth = await Api.login(this.setup.username, this.setup.password)
this.auth = Api.saveToken(this.setup.username, auth.token)
const auth = await Api.login(s.username, s.password)
this.auth = Api.saveToken(s.username, auth.token)
await this.$store.dispatch('loadAdmin') await this.$store.dispatch('loadAdmin')
this.loading = false
this.$router.push('/')
} }
} }
} }

View File

@ -1,6 +1,9 @@
<template> <template>
<div> <div>
<h1 class="text-black-50 mt-5">{{in_user === null ? "Create User" : "Edit User"}}</h1> <h1 class="text-black-50 mt-5">
{{user.id ? `Update ${user.username}` : "Create User"}}
<button @click="removeEdit" v-if="user.id" class="mt-3 btn float-right btn-danger btn-sm">Close</button></h1>
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
@ -20,24 +23,26 @@
<div class="form-group row"> <div class="form-group row">
<label for="email" class="col-sm-4 col-form-label">Email Address</label> <label for="email" class="col-sm-4 col-form-label">Email Address</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input v-model="user.email" type="email" name="email" class="form-control" id="email" value="" placeholder="user@domain.com" required autocapitalize="none" spellcheck="false"> <input v-model="user.email" type="email" class="form-control" id="email" placeholder="user@domain.com" required autocapitalize="none" spellcheck="false">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="password" class="col-sm-4 col-form-label">Password</label> <label class="col-sm-4 col-form-label">Password</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input v-model="user.password" type="password" name="password" class="form-control" id="password" placeholder="Password" required> <input v-model="user.password" type="password" class="form-control" placeholder="Password" required>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="password_confirm" class="col-sm-4 col-form-label">Confirm Password</label> <label class="col-sm-4 col-form-label">Confirm Password</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input v-model="user.confirm_password" type="password" name="password_confirm" class="form-control" id="password_confirm" placeholder="Confirm Password" required> <input v-model="user.confirm_password" type="password" class="form-control" placeholder="Confirm Password" required>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<div class="col-sm-12"> <div class="col-sm-12">
<button @click="saveUser" class="btn btn-primary btn-block">Create User</button> <button @click="saveUser" class="btn btn-block" :class="{'btn-primary': !user.id, 'btn-secondary': user.id}">
{{user.id ? "Update User" : "Create User"}}
</button>
</div> </div>
</div> </div>
<div class="alert alert-danger d-none" id="alerter" role="alert"></div> <div class="alert alert-danger d-none" id="alerter" role="alert"></div>
@ -55,6 +60,9 @@
props: { props: {
in_user: { in_user: {
type: Object type: Object
},
edit: {
type: Function
} }
}, },
data () { data () {
@ -68,23 +76,46 @@
} }
} }
}, },
mounted() { watch: {
if (this.in_user) { in_user() {
this.user = this.in_user const u = this.in_user
delete u.password
delete u.confirm_password
this.user = u
} }
},
computed() {
}, },
methods: { methods: {
removeEdit() {
this.user = {}
this.edit(false)
},
async saveUser(e) { async saveUser(e) {
e.preventDefault(); e.preventDefault();
if (this.user.id) {
await this.updateUser()
} else {
await this.createUser()
}
},
async createUser() {
let user = this.user let user = this.user
delete user.confirm_password delete user.confirm_password
await Api.user_create(user) await Api.user_create(user)
const users = await Api.users() const users = await Api.users()
this.$store.commit('setUsers', users) this.$store.commit('setUsers', users)
this.user = {}
}, },
async updateUser() {
let user = this.user
if (!user.password) {
delete user.password
}
delete user.confirm_password
await Api.user_update(user)
const users = await Api.users()
this.$store.commit('setUsers', users)
this.edit(false)
}
} }
} }
</script> </script>

View File

@ -9,6 +9,19 @@ export default Vue.mixin({
return this.now() - seconds return this.now() - seconds
}, },
hour(){ return 3600 }, hour(){ return 3600 },
day() { return 3600 * 24 } day() { return 3600 * 24 },
serviceLink(service) {
if (!service) {
return ""
}
if (!service.id) {
service = this.$store.getters.serviceById(service)
}
let link = service.permalink ? service.permalink : service.id
return `/service/${link}`
},
isInt(n) {
return n % 1 === 0;
}
} }
}); });

View File

@ -49,10 +49,11 @@
<nav class="nav nav-pills flex-column flex-sm-row mt-3" id="service_tabs"> <nav class="nav nav-pills flex-column flex-sm-row mt-3" id="service_tabs">
<a @click="tab='failures'" class="flex-sm-fill text-sm-center nav-link active">Failures</a> <a @click="tab='failures'" class="flex-sm-fill text-sm-center nav-link active">Failures</a>
<a @click="tab='incidents'" class="flex-sm-fill text-sm-center nav-link">Incidents</a> <a @click="tab='incidents'" class="flex-sm-fill text-sm-center nav-link">Incidents</a>
<a @click="tab='checkins'" v-if="$store.getters.token.token" class="flex-sm-fill text-sm-center nav-link">Checkins</a> <a @click="tab='checkins'" v-if="$store.getters.token" class="flex-sm-fill text-sm-center nav-link">Checkins</a>
<a @click="tab='response'" v-if="$store.getters.token.token" class="flex-sm-fill text-sm-center nav-link">Response</a> <a @click="tab='response'" v-if="$store.getters.token" class="flex-sm-fill text-sm-center nav-link">Response</a>
</nav> </nav>
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane fade active show"> <div class="tab-pane fade active show">
<div class="list-group mt-3 mb-4"> <div class="list-group mt-3 mb-4">
@ -138,7 +139,7 @@ export default {
return { return {
id: null, id: null,
tab: "failures", tab: "failures",
service: null, service: {},
authenticated: false, authenticated: false,
ready: false, ready: false,
data: null, data: null,
@ -219,19 +220,22 @@ export default {
}] }]
} }
}, },
created() { async created() {
this.service = this.$route.params.service
this.id = this.$route.params.id this.id = this.$route.params.id
if (!this.service) { let service;
this.getService(this.id) if (this.isInt(this.id)) {
service = this.$store.getters.serviceById(this.id)
} else {
service = this.$store.getters.serviceByPermalink(this.id)
} }
await this.getService(service)
}, },
mounted() { mounted() {
}, },
methods: { methods: {
async getService(id) { async getService(s) {
this.service = await Api.service(id) this.service = await Api.service(s.id)
await this.chartHits() await this.chartHits()
await this.serviceFailures() await this.serviceFailures()
}, },

View File

@ -44,8 +44,8 @@ export default new Vuex.Store({
serviceById: (state) => (id) => { serviceById: (state) => (id) => {
return state.services.find(s => s.id === id) return state.services.find(s => s.id === id)
}, },
serviceByName: (state) => (name) => { serviceByPermalink: (state) => (permalink) => {
return state.services.find(s => s.name === name) return state.services.find(s => s.permalink === permalink)
}, },
servicesInGroup: (state) => (id) => { servicesInGroup: (state) => (id) => {
return state.services.filter(s => s.group_id === id) return state.services.filter(s => s.group_id === id)
@ -57,7 +57,7 @@ export default new Vuex.Store({
return state.groups.find(g => g.id === id) return state.groups.find(g => g.id === id)
}, },
cleanGroups: (state) => () => { cleanGroups: (state) => () => {
return state.groups.filter(g => g.name !== '') return state.groups.filter(g => g.name !== 'Empty Group')
}, },
userById: (state) => (id) => { userById: (state) => (id) => {
return state.users.find(u => u.id === id) return state.users.find(u => u.id === id)

View File

@ -3551,6 +3551,11 @@ flat-cache@^2.0.1:
rimraf "2.6.3" rimraf "2.6.3"
write "1.0.3" write "1.0.3"
flatpickr@^4.6.1:
version "4.6.3"
resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.6.3.tgz#15a8b76b6e34e3a072861250503a5995b9d3bc60"
integrity sha512-007VucCkqNOMMb9ggRLNuJowwaJcyOh4sKAFcdGfahfGc7JQbf94zSzjdBq/wVyHWUEs5o3+idhFZ0wbZMRmVQ==
flatted@^2.0.0: flatted@^2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08"
@ -7940,6 +7945,13 @@ vue-eslint-parser@^4.0.2:
esquery "^1.0.1" esquery "^1.0.1"
lodash "^4.17.11" lodash "^4.17.11"
vue-flatpickr-component@^8.1.5:
version "8.1.5"
resolved "https://registry.yarnpkg.com/vue-flatpickr-component/-/vue-flatpickr-component-8.1.5.tgz#a7a7978d0034c8e44cf1527442d100c0655882fb"
integrity sha512-whrR+WM7fWyHW+1ZxCx7uVSuOlTeZXEMzhsgcILXGxIzQxr5uX5RlS5amLXdGGSSVf+zukrb6MvYit/uIkhk3Q==
dependencies:
flatpickr "^4.6.1"
vue-hot-reload-api@^2.3.0: vue-hot-reload-api@^2.3.0:
version "2.3.4" version "2.3.4"
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2" resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"

View File

@ -94,6 +94,7 @@ func Router() *mux.Router {
// API SERVICE Routes // API SERVICE Routes
r.Handle("/api/services", scoped(apiAllServicesHandler)).Methods("GET") r.Handle("/api/services", scoped(apiAllServicesHandler)).Methods("GET")
r.Handle("/api/services", authenticated(apiCreateServiceHandler, false)).Methods("POST") r.Handle("/api/services", authenticated(apiCreateServiceHandler, false)).Methods("POST")
r.Handle("/api/services_test", authenticated(apiTestServiceHandler, false)).Methods("POST")
r.Handle("/api/services/{id}", scoped(apiServiceHandler)).Methods("GET") r.Handle("/api/services/{id}", scoped(apiServiceHandler)).Methods("GET")
r.Handle("/api/reorder/services", authenticated(reorderServiceHandler, false)).Methods("POST") r.Handle("/api/reorder/services", authenticated(reorderServiceHandler, false)).Methods("POST")
r.Handle("/api/services/{id}/running", authenticated(apiServiceRunningHandler, false)).Methods("POST") r.Handle("/api/services/{id}/running", authenticated(apiServiceRunningHandler, false)).Methods("POST")

View File

@ -147,6 +147,24 @@ func apiCreateServiceHandler(w http.ResponseWriter, r *http.Request) {
sendJsonAction(newService, "create", w, r) sendJsonAction(newService, "create", w, r)
} }
func apiTestServiceHandler(w http.ResponseWriter, r *http.Request) {
var service *types.Service
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&service)
if err != nil {
sendErrorJson(err, w, r)
return
}
newService := core.ReturnService(service)
_, err = newService.Create(true)
if err != nil {
sendErrorJson(err, w, r)
return
}
sendJsonAction(newService, "create", w, r)
}
func apiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) { func apiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
service := core.SelectService(utils.ToInt(vars["id"])) service := core.SelectService(utils.ToInt(vars["id"]))