mirror of https://github.com/statping/statping
441 lines
24 KiB
Vue
441 lines
24 KiB
Vue
<template>
|
|
<form v-if="service.type" @submit.prevent="saveService">
|
|
<div class="card contain-card mb-4">
|
|
<div class="card-header">{{ $t('service_info') }}</div>
|
|
<div class="card-body">
|
|
<div class="form-group row">
|
|
<label class="col-sm-4 col-form-label">{{ $t('service_name') }}</label>
|
|
<div class="col-sm-8">
|
|
<input v-model="service.name" @input="updatePermalink" id="name" type="text" name="name" class="form-control" placeholder="Server Name" required spellcheck="false" autocorrect="off">
|
|
<small class="form-text text-muted">Give your service a name you can recognize</small>
|
|
</div>
|
|
</div>
|
|
<div class="form-group row">
|
|
<label for="service_type" class="col-sm-4 col-form-label">{{ $t('service_type') }}</label>
|
|
<div class="col-sm-8">
|
|
<select v-model="service.type" @change="updateDefaultValues()" class="form-control" id="service_type">
|
|
<option value="http">HTTP {{ $t('service') }}</option>
|
|
<option value="tcp">TCP {{ $t('service') }}</option>
|
|
<option value="udp">UDP {{ $t('service') }}</option>
|
|
<option value="icmp">ICMP Ping</option>
|
|
<option value="grpc">gRPC {{ $t('service') }}</option>
|
|
<option value="smtp">SMTP {{ $t('service') }}</option>
|
|
<option value="imap">IMAP {{ $t('service') }}</option>
|
|
<option value="static">Static {{ $t('service') }}</option>
|
|
</select>
|
|
<small class="form-text text-muted">Use HTTP if you are checking a website or use TCP if you are checking a server</small>
|
|
</div>
|
|
</div>
|
|
<div class="form-group row">
|
|
<label for="service_type" class="col-sm-4 col-form-label">{{ $t('group') }}</label>
|
|
<div class="col-sm-8">
|
|
<select v-model.number="service.group_id" class="form-control">
|
|
<option value="0" >No Group</option>
|
|
<option v-for="(group, index) in $store.getters.cleanGroups()" :value="group.id">{{group.name}}</option>
|
|
</select>
|
|
<small class="form-text text-muted">Attach this service to a group</small>
|
|
</div>
|
|
</div>
|
|
<div class="form-group row">
|
|
<label class="col-sm-4 col-form-label">{{ $t('permalink') }}</label>
|
|
<div class="col-sm-8">
|
|
<input v-model="service.permalink" type="text" name="permalink" class="form-control" id="permalink" autocapitalize="none" spellcheck="true" placeholder='awesome_service'>
|
|
<small class="form-text text-muted">Use text for the service URL rather than the service number.</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group row">
|
|
<label class="col-sm-4 col-form-label">{{ $t('service_public') }}</label>
|
|
<div class="col-12 col-md-8 mt-1 mb-2">
|
|
<span @click="service.public = !!service.public" class="switch float-left">
|
|
<input v-model="service.public" type="checkbox" name="public-option" class="switch" id="switch-public" v-bind:checked="service.public">
|
|
<label v-if="service.public" for="switch-public">This service will be visible for everyone</label>
|
|
<label v-if="!service.public" for="switch-public">This service will only be visible for users and administrators.</label>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="service.type !== 'static'" class="form-group row">
|
|
<label for="service_interval" class="col-sm-4 col-form-label">{{ $t('check_interval') }}</label>
|
|
<div class="col-sm-6">
|
|
<span class="slider-info">{{secondsHumanize(service.check_interval)}}</span>
|
|
<input v-model.number="service.check_interval" type="range" class="slider" id="service_interval" min="1" max="1800" :step="1">
|
|
<small id="interval" class="form-text text-muted">Interval to check your service state</small>
|
|
</div>
|
|
<div class="col-sm-2">
|
|
<input v-model.number="service.check_interval" type="number" name="check_interval" class="form-control">
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="service.type !== 'static'" class="card contain-card mb-4">
|
|
<div class="card-header">Request Details</div>
|
|
<div class="card-body">
|
|
|
|
<div class="form-group row">
|
|
<label for="service_url" class="col-sm-4 col-form-label">
|
|
{{ $t('service_endpoint') }} {{service.type === 'http' ? "(URL)" : "(Domain)"}}
|
|
</label>
|
|
<div class="col-sm-8">
|
|
<input v-model="service.domain" type="url" class="form-control" id="service_url" :placeholder="service.type === 'http' ? 'https://google.com' : '192.168.1.1'" required autocapitalize="none" spellcheck="false">
|
|
<small class="form-text text-muted">Statping will attempt to connect to this address</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="service.type.match(/^(tcp|udp|grpc)$/)" class="form-group row">
|
|
<label class="col-sm-4 col-form-label">Port</label>
|
|
<div class="col-sm-8">
|
|
<input v-model.number="service.port" type="number" name="port" class="form-control" id="service_port" placeholder="8080">
|
|
</div>
|
|
</div>
|
|
<div v-if="service.type.match(/^(smtp|imap)$/)" class="form-group row">
|
|
<label class="col-sm-4 col-form-label">Port</label>
|
|
<div class="col-sm-8">
|
|
<input v-model.number="service.port" type="number" name="port" class="form-control" id="service_port" placeholder="587">
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="service.type.match(/^(http)$/)" class="form-group row">
|
|
<label class="col-sm-4 col-form-label">{{ $t('service_check') }}</label>
|
|
<div class="col-sm-8">
|
|
<select v-model="service.method" name="method" class="form-control">
|
|
<option value="GET" >GET</option>
|
|
<option value="HEAD" >HEAD</option>
|
|
<option value="POST" >POST</option>
|
|
<option value="DELETE" >DELETE</option>
|
|
<option value="PATCH" >PATCH</option>
|
|
<option value="PUT" >PUT</option>
|
|
</select>
|
|
<small class="form-text text-muted">A GET/HEAD request will simply request the endpoint, you can also send data with POST.</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group row">
|
|
<label class="col-sm-4 col-form-label">{{ $t('service_timeout') }}</label>
|
|
<div class="col-sm-6">
|
|
<span v-if="service.timeout >= 0" class="slider-info">{{secondsHumanize(service.timeout)}}</span>
|
|
<input v-model.number="service.timeout" type="range" id="timeout" name="timeout" class="slider" min="1" max="180">
|
|
<small class="form-text text-muted">If the endpoint does not respond within this time it will be considered to be offline</small>
|
|
</div>
|
|
|
|
<div class="col-sm-2">
|
|
<input v-model.number="service.timeout" type="number" name="service_timeout" class="form-control">
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div v-if="service.type.match(/^(http)$/) && service.method.match(/^(POST|PATCH|DELETE|PUT)$/)" class="form-group row">
|
|
<label class="col-sm-4 col-form-label">Optional Post Data (JSON)</label>
|
|
<div class="col-sm-8">
|
|
<textarea v-model="service.post_data" class="form-control" rows="3" autocapitalize="none" spellcheck="false" placeholder='{"data": { "method": "success", "id": 148923 } }'></textarea>
|
|
<small class="form-text text-muted">Insert a JSON string to send data to the endpoint.</small>
|
|
</div>
|
|
</div>
|
|
<div v-if="service.type.match(/^(http)$/)" class="form-group row">
|
|
<label class="col-sm-4 col-form-label">HTTP Headers</label>
|
|
<div class="col-sm-8">
|
|
<input v-model="service.headers" class="form-control" autocapitalize="none" spellcheck="false" placeholder='Authorization=1010101,Content-Type=application/json'>
|
|
<small class="form-text text-muted">Comma delimited list of HTTP Headers (KEY=VALUE,KEY=VALUE)</small>
|
|
</div>
|
|
</div>
|
|
<div v-if="service.type.match(/^(smtp|imap)$/)" class="form-group row">
|
|
<label class="col-sm-4 col-form-label">Credentials</label>
|
|
<div class="col-sm-8">
|
|
<input v-model="service.headers" class="form-control" autocapitalize="none" spellcheck="false" placeholder='Username=user@domain.com,Password=secretpassword'>
|
|
<small class="form-text text-muted">Comma delimited list of IMAP/SMTP credentials (Username=user@domain.com,Password=secretpassword)</small>
|
|
</div>
|
|
</div>
|
|
<div v-if="service.type.match(/^(http)$/)" class="form-group row">
|
|
<label class="col-sm-4 col-form-label">{{ $t('expected_resp') }} (Regex)</label>
|
|
<div class="col-sm-8">
|
|
<textarea v-model="service.expected" class="form-control" rows="3" autocapitalize="none" spellcheck="false" placeholder='(method)": "((\\"|[success])*)"'></textarea>
|
|
<small class="form-text text-muted">You can use plain text or insert <a target="_blank" href="https://regex101.com/r/I5bbj9/1">Regex</a> to validate the response</small>
|
|
</div>
|
|
</div>
|
|
<div v-if="service.type.match(/^(http)$/)" class="form-group row">
|
|
<label for="service_response_code" class="col-sm-4 col-form-label">{{ $t('expected_code') }}</label>
|
|
<div class="col-sm-8">
|
|
<input v-model="service.expected_status" type="number" name="expected_status" class="form-control" placeholder="200" id="service_response_code">
|
|
<small class="form-text text-muted">A status code of 200 is success, or view all the <a target="_blank" href="https://www.restapitutorial.com/httpstatuscodes.html">HTTP Status Codes</a></small>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="service.type.match(/^(http)$/)" class="form-group row">
|
|
<label class="col-12 col-md-4 col-form-label">{{ $t('follow_redir') }}</label>
|
|
<div class="col-12 col-md-8 mt-1 mb-2 mb-md-0">
|
|
<span @click="service.redirect = !!service.redirect" class="switch float-left">
|
|
<input v-model="service.redirect" type="checkbox" name="redirect-option" class="switch" id="switch-redirect" v-bind:checked="service.redirect">
|
|
<label for="switch-redirect">Follow HTTP Redirects if server attempts</label>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div v-if="service.type.match(/^(http|grpc|smtp|imap)$/)" class="form-group row">
|
|
<label class="col-12 col-md-4 col-form-label">{{ $t('verify_ssl') }}</label>
|
|
<div class="col-12 col-md-8 mt-1 mb-2 mb-md-0">
|
|
<span @click="service.verify_ssl = !!service.verify_ssl" class="switch float-left">
|
|
<input v-model="service.verify_ssl" type="checkbox" name="verify_ssl-option" class="switch" id="switch-verify-ssl" v-bind:checked="service.verify_ssl">
|
|
<label for="switch-verify-ssl" v-if="service.verify_ssl">Verify SSL Certificate for this service</label>
|
|
<label for="switch-verify-ssl" v-if="!service.verify_ssl">Skip SSL Certificate verification for this service</label>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="service.type.match(/^(grpc)$/)" class="form-group row">
|
|
<label class="col-12 col-md-4 col-form-label"><a href="https://github.com/grpc/grpc/blob/master/doc/health-checking.md#grpc-health-checking-protocol">GRPC Health Check</a></label>
|
|
<div class="col-12 col-md-8 mt-1 mb-2 mb-md-0">
|
|
<span @click="service.grpc_health_check = !!service.grpc_health_check" class="switch float-left">
|
|
<input v-model="service.grpc_health_check" type="checkbox" name="grpc_health_check-option" class="switch" id="switch-grpc-health-check" v-bind:checked="service.grpc_health_check">
|
|
<label for="switch-grpc-health-check" v-if="service.grpc_health_check">Check against GRPC health check endpoint.</label>
|
|
<label for="switch-grpc-health-check" v-if="!service.grpc_health_check">Only checks if GRPC connection can be established.</label>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="service.grpc_health_check" class="form-group row">
|
|
<label class="col-sm-4 col-form-label">Expected Response</label>
|
|
<div class="col-sm-8">
|
|
<textarea v-model="service.expected" class="form-control" rows="3" autocapitalize="none" spellcheck="false" placeholder='status:SERVING'></textarea>
|
|
<small class="form-text text-muted">Check <a target="_blank" href="https://pkg.go.dev/google.golang.org/grpc/health/grpc_health_v1?tab=doc#pkg-variables">GPRC health check response codes</a> for more information.</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="service.grpc_health_check" class="form-group row">
|
|
<label for="service_response_code" class="col-sm-4 col-form-label">Expected Status Code</label>
|
|
<div class="col-sm-8">
|
|
<input v-model="service.expected_status" type="number" name="expected_status" class="form-control" placeholder="1" id="service_response_code">
|
|
<small class="form-text text-muted">A status code of 1 is success, or view all the <a target="_blank" href="https://pkg.go.dev/google.golang.org/grpc/health/grpc_health_v1?tab=doc#HealthCheckResponse_ServingStatus">GRPC Status Codes</a></small>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="service.type.match(/^(tcp|smtp|imap|http)$/)" class="form-group row">
|
|
<label class="col-12 col-md-4 col-form-label">{{ $t('tls_cert') }}</label>
|
|
<div class="col-12 col-md-8 mt-1 mb-2 mb-md-0">
|
|
<span @click="use_tls = !!use_tls" class="switch float-left">
|
|
<input v-model="use_tls" type="checkbox" name="verify_ssl-option" class="switch" id="switch-use-tls" v-bind:checked="use_tls">
|
|
<label for="switch-use-tls" v-if="use_tls">Custom TLS Certificates for mTLS services</label>
|
|
<label for="switch-use-tls" v-if="!use_tls">Ignore TLS Certificates</label>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="use_tls" class="form-group row">
|
|
<label for="service_tls_cert" class="col-sm-4 col-form-label">TLS Client Certificate</label>
|
|
<div class="col-sm-8">
|
|
<textarea v-model="service.tls_cert" name="tls_cert" class="form-control" id="service_tls_cert"></textarea>
|
|
<small class="form-text text-muted">Absolute path to TLS Client Certificate file or in PEM format</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="use_tls" class="form-group row">
|
|
<label for="service_tls_cert_key" class="col-sm-4 col-form-label">TLS Client Key</label>
|
|
<div class="col-sm-8">
|
|
<textarea v-model="service.tls_cert_key" name="tls_cert_key" class="form-control" id="service_tls_cert_key"></textarea>
|
|
<small class="form-text text-muted">Absolute path to TLS Client Key file or in PEM format</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="use_tls" class="form-group row">
|
|
<label for="service_tls_cert_chain" class="col-sm-4 col-form-label">Root CA</label>
|
|
<div class="col-sm-8">
|
|
<textarea v-model="service.tls_cert_root" name="tls_cert_key" class="form-control" id="service_tls_cert_chain"></textarea>
|
|
<small class="form-text text-muted">Absolute path to Root CA file or in PEM format (optional)</small>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card contain-card mb-4">
|
|
<div class="card-header">{{ $t('notification_opts') }}</div>
|
|
<div class="card-body">
|
|
|
|
<div class="form-group row">
|
|
<label class="col-sm-4 col-form-label">{{ $t('notifications_enable') }}</label>
|
|
<div class="col-12 col-md-8 mt-1 mb-2 mb-md-0">
|
|
<span @click="service.allow_notifications = !!service.allow_notifications" class="switch float-left">
|
|
<input v-model="service.allow_notifications" type="checkbox" name="allow_notifications-option" class="switch" id="switch-notifications" v-bind:checked="service.allow_notifications">
|
|
<label for="switch-notifications">Allow notifications to be sent for this service</label>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div v-if="service.allow_notifications" class="form-group row">
|
|
<label class="col-sm-4 col-form-label">{{ $t('notify_after') }}</label>
|
|
<div class="col-sm-8">
|
|
<span class="slider-info">{{service.notify_after === 0 ? "First Failure" : service.notify_after+' Failures'}}</span>
|
|
<input v-model="service.notify_after" type="range" name="notify_after" class="slider" id="notify_after" min="0" max="20">
|
|
<small class="form-text text-muted">Send Notification after {{service.notify_after === 0 ? 'the first Failure' : service.notify_after+' Failures'}} </small>
|
|
</div>
|
|
</div>
|
|
<div v-if="service.allow_notifications" class="form-group row">
|
|
<label class="col-sm-4 col-form-label">{{ $t('notify_all') }}</label>
|
|
<div class="col-12 col-md-8 mt-1">
|
|
<span @click="service.notify_all_changes = !!service.notify_all_changes" class="switch float-left">
|
|
<input v-model="service.notify_all_changes" type="checkbox" name="notify_all-option" class="switch" id="notify_all" v-bind:checked="service.notify_all_changes">
|
|
<label v-if="service.notify_all_changes" for="notify_all">Continuously send notifications when service is failing.</label>
|
|
<label v-if="!service.notify_all_changes" for="notify_all">Only notify one time when service hits an error</label>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group row">
|
|
<div class="col-12">
|
|
<button :disabled="loading" @click.prevent="saveService" type="submit" class="btn btn-success btn-block">
|
|
{{service.id ? $t('service_update') : $t('service_create')}}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="alert alert-danger d-none" id="alerter" role="alert"></div>
|
|
</form>
|
|
</template>
|
|
|
|
<script>
|
|
import Api from "../API";
|
|
|
|
export default {
|
|
name: 'FormService',
|
|
data () {
|
|
return {
|
|
loading: false,
|
|
service: {
|
|
name: "",
|
|
type: "http",
|
|
domain: "",
|
|
group_id: 0,
|
|
method: "GET",
|
|
post_data: "",
|
|
headers: "",
|
|
expected: "",
|
|
expected_status: 200,
|
|
port: 80,
|
|
check_interval: 60,
|
|
timeout: 15,
|
|
permalink: "",
|
|
order: 1,
|
|
verify_ssl: true,
|
|
grpc_health_check: false,
|
|
redirect: true,
|
|
allow_notifications: true,
|
|
notify_all_changes: true,
|
|
notify_after: 2,
|
|
public: true,
|
|
tls_cert: "",
|
|
tls_cert_key: "",
|
|
tls_cert_root: "",
|
|
},
|
|
use_tls: false,
|
|
groups: [],
|
|
}
|
|
},
|
|
props: {
|
|
in_service: {
|
|
type: Object
|
|
}
|
|
},
|
|
watch: {
|
|
in_service(svr, old) {
|
|
this.service = svr
|
|
this.use_tls = svr.tls_cert
|
|
}
|
|
},
|
|
async mounted () {
|
|
if (!this.$store.getters.groups) {
|
|
const groups = await Api.groups()
|
|
this.$store.commit('setGroups', groups)
|
|
}
|
|
this.update()
|
|
},
|
|
created () {
|
|
this.update()
|
|
},
|
|
methods: {
|
|
update() {
|
|
if (this.in_service) {
|
|
this.service = this.in_service
|
|
}
|
|
this.use_tls = this.service.tls_cert !== ""
|
|
},
|
|
updateDefaultValues() {
|
|
if (this.service.type === "grpc") {
|
|
this.service.expected_status = 1
|
|
this.service.expected = "status:SERVING"
|
|
this.service.port = 50051
|
|
this.service.verify_ssl = false
|
|
this.service.method = ""
|
|
} else {
|
|
this.service.expected_status = 200
|
|
this.service.expected = ""
|
|
this.service.port = 80
|
|
this.service.verify_ssl = true
|
|
this.service.method = "GET"
|
|
}
|
|
},
|
|
updatePermalink() {
|
|
const a = 'àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìłḿñńǹňôöòóœøōõőṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż·/_,:;'
|
|
const b = 'aaaaaaaaaacccddeeeeeeeegghiiiiiilmnnnnoooooooooprrsssssttuuuuuuuuuwxyyzzz------'
|
|
const p = new RegExp(a.split('').join('|'), 'g')
|
|
|
|
this.service.permalink = this.service.name.toLowerCase()
|
|
.replace(/\s+/g, '-') // Replace spaces with -
|
|
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
|
|
.replace(/&/g, '-and-') // Replace & with 'and'
|
|
.replace(/[^\w\-]+/g, '') // Remove all non-word characters
|
|
.replace(/\-\-+/g, '-') // Replace multiple - with single -
|
|
.replace(/^-+/, '') // Trim - from start of text
|
|
.replace(/-+$/, '') // Trim - from end of text
|
|
},
|
|
stepVal(val) {
|
|
if (val > 1800) {
|
|
return 300
|
|
} else if (val > 300) {
|
|
return 60
|
|
} else if (val > 120) {
|
|
return 10
|
|
}
|
|
return 1
|
|
},
|
|
async saveService () {
|
|
let s = this.service
|
|
this.loading = true
|
|
delete s.failures
|
|
delete s.created_at
|
|
delete s.updated_at
|
|
delete s.last_success
|
|
delete s.latency
|
|
delete s.online_24_hours
|
|
s.check_interval = parseInt(s.check_interval)
|
|
s.timeout = parseInt(s.timeout)
|
|
s.port = parseInt(s.port)
|
|
s.notify_after = parseInt(s.notify_after)
|
|
s.expected_status = parseInt(s.expected_status)
|
|
s.order = parseInt(s.order)
|
|
|
|
if (s.id) {
|
|
await this.updateService(s)
|
|
} else {
|
|
await this.createService(s)
|
|
}
|
|
const services = await Api.services()
|
|
this.$store.commit('setServices', services)
|
|
this.loading = false
|
|
this.$router.push('/dashboard/services')
|
|
},
|
|
async createService (s) {
|
|
await Api.service_create(s)
|
|
},
|
|
async updateService (s) {
|
|
await Api.service_update(s)
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
<style scoped>
|
|
</style>
|