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) {
switch s.Type {
case "http":
s.checkHttp(record)
s.CheckHttp(record)
case "tcp", "udp":
s.checkTcp(record)
s.CheckTcp(record)
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
func (s *Service) checkIcmp(record bool) *Service {
func (s *Service) CheckIcmp(record bool) *Service {
p := fastping.NewPinger()
resolveIP := "ip4:icmp"
if isIPv6(s.Domain) {
@ -148,7 +148,7 @@ func (s *Service) checkIcmp(record bool) *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()
if err != nil {
if record {
@ -188,7 +188,7 @@ func (s *Service) checkTcp(record bool) *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()
if err != nil {
if record {

View File

@ -5,7 +5,7 @@
"scripts": {
"serve": "vue-cli-service serve",
"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"
},
"dependencies": {
@ -22,6 +22,7 @@
"querystring": "^0.2.0",
"vue": "^2.6.10",
"vue-apexcharts": "^1.5.2",
"vue-flatpickr-component": "^8.1.5",
"vue-moment": "^4.1.0",
"vue-router": "~3.0",
"vuedraggable": "^2.23.2",

View File

@ -64,6 +64,10 @@ class Api {
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 () {
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))
}
async user_update (data) {
return axios.post('/api/users/'+data.id, data).then(response => (response.data))
}
async user_delete (id) {
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))
}
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) {
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">
<td>{{message.title}}</td>
<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 class="d-none d-md-table-cell">{{message.start_on}}</td>
<td class="text-right">
<div class="btn-group">
<a href="message/1" 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="editMessage(message, edit)" href="#" class="btn btn-outline-secondary"><i class="fas fa-exclamation-triangle"></i> Edit</a>
<a @click.prevent="deleteMessage(message)" href="#" class="btn btn-danger"><font-awesome-icon icon="times" /></a>
</div>
</td>
</tr>
@ -31,15 +31,8 @@
</table>
</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>
</template>
@ -52,12 +45,22 @@
components: {FormMessage},
data () {
return {
edit: false,
message: {}
}
},
methods: {
editChange(v) {
this.message = {}
this.edit = v
},
editMessage(m, mode) {
this.message = m
this.edit = !mode
},
service (id) {
return this.$store.getters.serviceById(id).name || ""
const s = this.$store.getters.serviceById(id) || {}
return s.name || "Global Message"
},
async deleteMessage(m) {
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
</router-link>
</h1>
<table class="table">
<thead>
<tr>
@ -16,7 +17,7 @@
<th scope="col"></th>
</tr>
</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">
<td>
<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">
<i class="fas fa-chart-area"></i> View
</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" />
</a>
</div>
@ -74,8 +75,8 @@
</td>
<td class="text-right">
<div class="btn-group">
<a href="group/2" 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="editGroup(group, edit)" href="#" class="btn btn-outline-secondary"><font-awesome-icon icon="chart-area" /> Edit</a>
<a @click.prevent="deleteGroup(group)" href="#" class="btn btn-danger">
<font-awesome-icon icon="times" />
</a>
</div>
@ -85,13 +86,7 @@
</draggable>
</table>
<h1 class="text-muted mt-5">Create Group</h1>
<div class="card">
<div class="card-body">
<FormGroup/>
</div>
</div>
<FormGroup :edit="editChange" :in_group="group"/>
</div>
</div>
@ -112,7 +107,8 @@
},
data () {
return {
edit: false,
group: {}
}
},
computed: {
@ -149,6 +145,14 @@
},
methods: {
editChange(v) {
this.group = {}
this.edit = v
},
editGroup(g, mode) {
this.group = g
this.edit = !mode
},
reordered_services() {
},

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
<footer>
<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> |
<router-link to="/dashboard">Dashboard</router-link>
<a href="/dashboard">Dashboard</a>
</div>
</footer>
</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>
</div>
<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>

View File

@ -84,7 +84,7 @@
<div class="form-group row mt-3">
<label class="col-sm-3 col-form-label">API Key</label>
<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>
</div>
</div>
@ -92,7 +92,7 @@
<div class="form-group row">
<label class="col-sm-3 col-form-label">API Secret</label>
<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">You can <a href="#" @click="renewApiKeys">Regenerate API Keys</a> if you need to.</small>
</div>
@ -102,21 +102,15 @@
</template>
<script>
import time from '../components/Time'
import Api from '../components/API'
import Api from '../components/API'
export default {
export default {
name: 'CoreSettings',
data () {
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 () {
},
@ -126,7 +120,6 @@ export default {
const c = this.core
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}
alert(JSON.stringify(coreForm))
await Api.core_save(coreForm)
const core = await Api.core()
this.$store.commit('setCore', core)
@ -140,7 +133,10 @@ export default {
this.$store.commit('setCore', core)
this.core = core
}
}
},
selectAll() {
this.$refs.input.select();
}
}
}
</script>

View File

@ -1,9 +1,17 @@
<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">
<div class="form-group row">
<label for="title" class="col-sm-4 col-form-label">Group Name</label>
<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 class="form-group row">
@ -17,21 +25,29 @@
</div>
<div class="form-group row">
<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 class="alert alert-danger d-none" id="alerter" role="alert"></div>
</form>
</div>
</div>
</div>
</template>
<script>
import Api from "../components/API";
import Api from "../components/API";
export default {
export default {
name: 'FormGroup',
props: {
in_group: {
type: Object
},
edit: {
type: Function
}
},
data () {
@ -42,20 +58,41 @@ export default {
}
}
},
mounted() {
if (this.props.in_group) {
this.group = this.props.in_group
watch: {
in_group() {
this.group = this.in_group
}
},
methods: {
removeEdit() {
this.group = {}
this.edit(false)
},
async saveGroup(e) {
e.preventDefault();
if (this.in_group) {
await this.updateGroup()
} else {
await this.createGroup()
}
},
async createGroup() {
const g = this.group
const data = {name: g.name, public: g.public}
await Api.group_create(data)
const groups = await Api.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>

View File

@ -1,56 +1,42 @@
<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">
<label class="col-sm-4 col-form-label">Title</label>
<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 class="form-group row">
<label class="col-sm-4 col-form-label">Description</label>
<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 class="form-group row">
<label class="col-sm-4 col-form-label">Message Date Range</label>
<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 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 class="form-group row">
<label for="service_id" class="col-sm-4 col-form-label">Service</label>
<div class="col-sm-8">
<select class="form-control" name="service" id="service_id">
<option value="0" selected>Global Message</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 v-model="message.service_id" class="form-control" name="service" id="service_id">
<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>
</select>
</div>
</div>
@ -58,15 +44,15 @@
<div class="form-group row">
<label for="notify_method" class="col-sm-4 col-form-label">Notification Method</label>
<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 class="form-group row">
<label for="notify_method" class="col-sm-4 col-form-label">Notify Users</label>
<div class="col-sm-8">
<span class="switch">
<input @click="message.notify = !!message.notify" type="checkbox" class="switch" id="switch-normal">
<span @click="message.notify = !!message.notify" class="switch">
<input v-model="message.notify" type="checkbox" class="switch" id="switch-normal">
<label for="switch-normal">Notify Users Before Scheduled Time</label>
</span>
</div>
@ -76,8 +62,8 @@
<label for="notify_before" class="col-sm-4 col-form-label">Notify Before</label>
<div class="col-sm-8">
<div class="form-inline">
<input type="number" name="notify_before" class="col-4 form-control" id="notify_before" value="0">
<select class="ml-2 col-7 form-control" name="notify_before_scale" id="notify_before_scale">
<input v-model="message.notify_before" type="number" name="notify_before" class="col-4 form-control" id="notify_before">
<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="hour">Hours</option>
<option value="day">Days</option>
@ -88,42 +74,91 @@
<div class="form-group row">
<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 class="alert alert-danger d-none" id="alerter" role="alert"></div>
</form>
</div>
</div>
{{JSON.stringify(temp)}}
</div>
</template>
<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',
components: {
flatPickr
},
props: {
in_message: {
type: Object
},
edit: {
type: Function
}
},
data () {
return {
group: {
name: "",
public: true
}
message: {
title: "",
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() {
if (this.props.group) {
this.group = this.props.group
watch: {
in_message() {
this.message = this.in_message
}
},
methods: {
async saveGroup(e) {
e.preventDefault();
const data = {name: this.group.name, public: this.group.public}
await Api.group_create(data)
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
removeEdit() {
this.message = {}
this.edit(false)
},
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>

View File

@ -38,7 +38,7 @@
</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">
<label class="col-sm-4 col-form-label">Service Check Type</label>
@ -82,7 +82,7 @@
</div>
</div>
<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">
<input v-model="service.port" type="number" name="port" class="form-control" id="service_port" placeholder="8080">
</div>
@ -146,8 +146,12 @@
</div>
</div>
<div class="form-group row">
<div class="col-12">
<button @click="saveService" type="submit" class="btn btn-success btn-block">Create Service</button>
<div class="col-6">
<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>
@ -178,7 +182,7 @@
check_interval: 60,
timeout: 15,
permalink: "",
order: 0,
order: 1,
verify_ssl: true,
allow_notifications: true,
public: true,
@ -186,16 +190,17 @@
groups: [],
}
},
props: {
in_service: {
type: Object,
required: false,
}
},
async created() {
if (this.props.in_service) {
this.service = this.props.in_service
}
props: {
in_service: {
type: Object
}
},
watch: {
in_service() {
this.service = this.in_service
}
},
async mounted() {
if (!this.$store.getters.groups) {
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
@ -211,7 +216,10 @@
delete s.last_success
delete s.latency
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">
<form @submit="">
<form @submit="saveSetup">
<div class="row">
<div class="col-6">
<div class="form-group">
@ -147,15 +147,13 @@
this.loading = false
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')
this.loading = false
this.$router.push('/')
}
}
}

View File

@ -1,6 +1,9 @@
<template>
<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-body">
@ -20,24 +23,26 @@
<div class="form-group row">
<label for="email" class="col-sm-4 col-form-label">Email Address</label>
<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 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">
<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 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">
<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 class="form-group row">
<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 class="alert alert-danger d-none" id="alerter" role="alert"></div>
@ -55,6 +60,9 @@
props: {
in_user: {
type: Object
},
edit: {
type: Function
}
},
data () {
@ -68,23 +76,46 @@
}
}
},
mounted() {
if (this.in_user) {
this.user = this.in_user
watch: {
in_user() {
const u = this.in_user
delete u.password
delete u.confirm_password
this.user = u
}
},
computed() {
},
methods: {
removeEdit() {
this.user = {}
this.edit(false)
},
async saveUser(e) {
e.preventDefault();
if (this.user.id) {
await this.updateUser()
} else {
await this.createUser()
}
},
async createUser() {
let user = this.user
delete user.confirm_password
await Api.user_create(user)
const users = await Api.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>

View File

@ -9,6 +9,19 @@ export default Vue.mixin({
return this.now() - seconds
},
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">
<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='checkins'" v-if="$store.getters.token.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='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" class="flex-sm-fill text-sm-center nav-link">Response</a>
</nav>
<div class="tab-content">
<div class="tab-pane fade active show">
<div class="list-group mt-3 mb-4">
@ -138,7 +139,7 @@ export default {
return {
id: null,
tab: "failures",
service: null,
service: {},
authenticated: false,
ready: false,
data: null,
@ -219,19 +220,22 @@ export default {
}]
}
},
created() {
this.service = this.$route.params.service
async created() {
this.id = this.$route.params.id
if (!this.service) {
this.getService(this.id)
let service;
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() {
},
methods: {
async getService(id) {
this.service = await Api.service(id)
async getService(s) {
this.service = await Api.service(s.id)
await this.chartHits()
await this.serviceFailures()
},

View File

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

View File

@ -3551,6 +3551,11 @@ flat-cache@^2.0.1:
rimraf "2.6.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:
version "2.0.1"
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"
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:
version "2.3.4"
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
r.Handle("/api/services", scoped(apiAllServicesHandler)).Methods("GET")
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/reorder/services", authenticated(reorderServiceHandler, 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)
}
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) {
vars := mux.Vars(r)
service := core.SelectService(utils.ToInt(vars["id"]))