pull/429/head
hunterlong 2020-01-16 20:55:50 -08:00
parent 978bc17768
commit d7fbc91b78
33 changed files with 2471 additions and 1703 deletions

16
frontend/.babelrc Normal file
View File

@ -0,0 +1,16 @@
{
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8", "ie >= 11"]
}
}
]
],
"plugins": [
"@babel/plugin-syntax-dynamic-import"
]
}

59
frontend/.eslintrc.json Normal file
View File

@ -0,0 +1,59 @@
{
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"node": true
},
"parserOptions": {
"parser": "babel-eslint",
"sourceType": "module",
"ecmaVersion": 2018
},
"extends": [
"eslint:recommended",
"plugin:vue/base",
"plugin:vue/essential",
"plugin:vue/strongly-recommended",
"plugin:vue/recommended"
],
"rules": {
"array-bracket-spacing": [ "error", "always" ],
"arrow-spacing": "error",
"constructor-super": "error",
"curly": "error",
"indent": ["error", 4, { "SwitchCase": 1 }],
"keyword-spacing": "error",
"no-console": 0,
"no-const-assign": "error",
"no-debugger": 0,
"no-duplicate-imports": "error",
"no-multiple-empty-lines": [ 2, { "max": 2, "maxEOF": 1 } ],
"no-this-before-super": "error",
"no-unreachable": "error",
"no-unused-vars": 0,
"no-useless-escape": 0,
"no-var": "error",
"object-curly-spacing": [ "error", "always" ],
"one-var": ["error", { "var": "never", "let": "never", "const": "never" }],
"prefer-arrow-callback": "error",
"prefer-const": "error",
"quotes": [ "error", "single", { "allowTemplateLiterals": true } ],
"semi": ["error", "always"],
"sort-imports": ["error", { "ignoreDeclarationSort": true }],
"space-before-function-paren": "error",
"valid-typeof": "error",
"vue/component-name-in-template-casing": ["error", "PascalCase",
{
"ignores": [
"router-link",
"router-view",
"transition"
]
}
]
},
"plugins": [
"vue"
]
}

3
frontend/config/dev.env Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
NODE_ENV: 'development'
};

View File

@ -0,0 +1,15 @@
'use strict';
const path = require('path');
const _root = path.resolve(__dirname, '..');
exports.root = function (args) {
args = Array.prototype.slice.call(arguments, 0);
return path.join.apply(path, [ _root ].concat(args));
};
exports.assetsPath = function (_path) {
return path.posix.join('assets', _path);
};

3
frontend/config/prod.env Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
NODE_ENV: 'production'
};

View File

@ -0,0 +1,68 @@
'use strict';
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlPlugin = require('html-webpack-plugin');
const MiniCSSExtractPlugin = require('mini-css-extract-plugin');
const helpers = require('./helpers');
const isDev = process.env.NODE_ENV === 'development';
const webpackConfig = {
entry: {
polyfill: '@babel/polyfill',
main: helpers.root('src', 'main'),
},
resolve: {
extensions: [ '.js', '.vue' ],
alias: {
'vue$': isDev ? 'vue/dist/vue.runtime.js' : 'vue/dist/vue.runtime.min.js',
'@': helpers.root('src')
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
include: [ helpers.root('src') ]
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [ helpers.root('src') ]
},
{
test: /\.css$/,
use: [
isDev ? 'vue-style-loader' : MiniCSSExtractPlugin.loader,
{ loader: 'css-loader', options: { sourceMap: isDev } },
]
},
{
test: /\.scss$/,
use: [
isDev ? 'vue-style-loader' : MiniCSSExtractPlugin.loader,
{ loader: 'css-loader', options: { sourceMap: isDev } },
{ loader: 'sass-loader', options: { sourceMap: isDev } }
]
},
{
test: /\.sass$/,
use: [
isDev ? 'vue-style-loader' : MiniCSSExtractPlugin.loader,
{ loader: 'css-loader', options: { sourceMap: isDev } },
{ loader: 'sass-loader', options: { sourceMap: isDev } }
]
},
{
test: /\.(gif|svg|jpg|png)$/,
loader: "file-loader",
}
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlPlugin({ template: 'public/index.html', chunksSortMode: 'dependency' })
]
};
module.exports = webpackConfig;

View File

@ -0,0 +1,49 @@
'use strict';
const webpack = require('webpack');
const merge = require('webpack-merge');
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
const helpers = require('./helpers');
const commonConfig = require('./webpack.config.common');
const environment = require('./dev.env');
const webpackConfig = merge(commonConfig, {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
output: {
path: helpers.root('dist'),
publicPath: '/',
filename: 'js/[name].bundle.js',
chunkFilename: 'js/[id].chunk.js'
},
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all'
}
},
plugins: [
new webpack.EnvironmentPlugin(environment),
new webpack.HotModuleReplacementPlugin(),
new FriendlyErrorsPlugin()
],
devServer: {
compress: true,
historyApiFallback: true,
hot: true,
open: true,
overlay: true,
port: 8888,
stats: {
normal: true
},
proxy: {
'/api': {
logLevel: 'debug',
target: 'http://0.0.0.0:8585'
}
}
}
});
module.exports = webpackConfig;

View File

@ -0,0 +1,83 @@
'use strict';
const webpack = require('webpack');
const merge = require('webpack-merge');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const MiniCSSExtractPlugin = require('mini-css-extract-plugin');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const helpers = require('./helpers');
const commonConfig = require('./webpack.config.common');
const isProd = process.env.NODE_ENV === 'production';
const environment = require('./prod.env');
const webpackConfig = merge(commonConfig, {
mode: 'production',
output: {
path: helpers.root('dist'),
publicPath: '/',
filename: 'js/[hash].js',
chunkFilename: 'js/[id].[hash].chunk.js'
},
optimization: {
runtimeChunk: 'single',
minimizer: [
new OptimizeCSSAssetsPlugin({
cssProcessorPluginOptions: {
preset: [ 'default', { discardComments: { removeAll: true } } ],
}
}),
new UglifyJSPlugin({
cache: true,
parallel: true,
sourceMap: !isProd
})
],
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name (module) {
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
return `npm.${packageName.replace('@', '')}`;
}
},
styles: {
test: /\.css$/,
name: 'styles',
chunks: 'all',
enforce: true
}
}
}
},
plugins: [
new webpack.EnvironmentPlugin(environment),
new MiniCSSExtractPlugin({
filename: 'css/[name].[hash].css',
chunkFilename: 'css/[id].[hash].css'
}),
new CompressionPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp('\\.(js|css)$'),
threshold: 10240,
minRatio: 0.8
}),
new webpack.HashedModuleIdsPlugin()
]
});
if (!isProd) {
webpackConfig.devtool = 'source-map';
if (process.env.npm_config_report) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}
}
module.exports = webpackConfig;

View File

@ -4,43 +4,62 @@
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build": "cross-env NODE_ENV=production webpack",
"dev": "cross-env NODE_ENV=development webpack-dev-server --progress",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@babel/polyfill": "~7.2",
"apexcharts": "^3.15.0",
"axios": "^0.19.1",
"core-js": "^3.4.4",
"querystring": "^0.2.0",
"vue": "^2.6.10",
"vue-apexcharts": "^1.5.2",
"vue-router": "^3.1.3"
"vue-router": "~3.0",
"vuex": "^3.1.2"
},
"devDependencies": {
"@babel/core": "~7.2",
"@babel/plugin-proposal-class-properties": "~7.3",
"@babel/plugin-proposal-decorators": "~7.3",
"@babel/plugin-proposal-json-strings": "~7.2",
"@babel/plugin-syntax-dynamic-import": "~7.2",
"@babel/plugin-syntax-import-meta": "~7.2",
"@babel/preset-env": "~7.8.3",
"@vue/babel-preset-app": "^4.1.2",
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-eslint": "^4.1.0",
"@vue/cli-service": "^4.1.0",
"babel-eslint": "^10.0.3",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"vue-template-compiler": "^2.6.10"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
"babel-eslint": "~10.0",
"babel-loader": "~8.0",
"compression-webpack-plugin": "~2.0",
"cross-env": "~5.2",
"css-loader": "~2.1",
"eslint": "~5.16",
"eslint-config-standard": "~10.2",
"eslint-friendly-formatter": "~3.0",
"eslint-loader": "~2.1",
"eslint-plugin-html": "~3.0",
"eslint-plugin-import": "~2.14",
"eslint-plugin-node": "~4.2",
"eslint-plugin-promise": "~3.5",
"eslint-plugin-standard": "~3.0",
"eslint-plugin-vue": "~5.1",
"file-loader": "^5.0.2",
"friendly-errors-webpack-plugin": "~1.7",
"html-webpack-plugin": "~3.2",
"mini-css-extract-plugin": "~0.5",
"node-sass": "~4.12",
"optimize-css-assets-webpack-plugin": "~5.0",
"sass-loader": "~7.1",
"uglifyjs-webpack-plugin": "~1.2",
"vue-loader": "~15.6",
"vue-style-loader": "~4.1",
"vue-template-compiler": "~2.6",
"webpack": "~4.29",
"webpack-bundle-analyzer": "~3.3",
"webpack-cli": "~3.2",
"webpack-dev-server": "~3.1",
"webpack-hot-middleware": "~2.24",
"webpack-merge": "~4.2"
}
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}

View File

@ -4,7 +4,6 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1.0, user-scalable=0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>Statping</title>
</head>
<body>

View File

@ -6,13 +6,52 @@
</template>
<script>
import Api from './components/API';
import Footer from "./components/Footer";
export default {
export default {
name: 'app',
components: {
Footer
},
created () {
if (!this.$store.getters.hasPublicData) {
this.setAllObjects()
}
},
mounted () {
this.$store.commit('setHasPublicData', true)
},
methods: {
async setAllObjects () {
await this.setCore()
await this.setToken()
await this.setServices()
await this.setGroups()
await this.setMessages()
this.$store.commit('setHasPublicData', true)
},
async setCore () {
const core = await Api.core()
this.$store.commit('setCore', core)
},
async setToken () {
const token = await Api.token()
this.$store.commit('setToken', token)
},
async setServices () {
const services = await Api.services()
this.$store.commit('setServices', services)
},
async setGroups () {
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
},
async setMessages () {
const messages = await Api.messages()
this.$store.commit('setMessages', messages)
}
}
}
</script>

View File

@ -8,7 +8,7 @@ class Api {
}
async root () {
async core () {
return axios.get('/api').then(response => (response.data))
}

View File

@ -3,7 +3,7 @@
<div class="row stats_area mb-5">
<div class="col-4">
<span class="lg_number">{{services.length}}</span>
<span class="lg_number">{{$store.getters.services.length}}</span>
Total Services
</div>
<div class="col-4">
@ -11,12 +11,12 @@
Failures last 24 Hours
</div>
<div class="col-4">
<span class="lg_number">7</span>
<span class="lg_number">{{$store.getters.onlineServices(true).length}}</span>
Online Services
</div>
</div>
<div v-for="(service, index) in services" v-bind:key="index">
<div v-for="(service, index) in $store.getters.services" v-bind:key="index">
<ServiceInfo :service=service />
</div>
</div>
@ -30,9 +30,6 @@
components: {
ServiceInfo
},
props: {
services: Array
},
methods: {
}

View File

@ -30,9 +30,11 @@
</thead>
<tbody>
<tr v-for="(message, index) in messages" v-bind:key="index">
<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"><a href="service/1">{{message.service}}</a></td>
<td class="d-none d-md-table-cell">
<router-link to="/service/${service(message.service).id}">{{service(message.service).name}}</router-link>
</td>
<td class="d-none d-md-table-cell">{{message.start_on}}</td>
<td class="text-right">
<div class="btn-group">
@ -162,15 +164,15 @@
name: 'DashboardMessages',
data () {
return {
messages: null
}
},
created() {
this.getMessages()
},
methods: {
async getMessages () {
this.messages = await Api.messages()
service (id) {
return this.$store.getters.serviceById(id)
}
}
}

View File

@ -18,8 +18,8 @@
<template>
<div>
<div class="col-12">
<h1 class="text-black-50">Services <a href="service/create" class="btn btn-outline-success mt-1 float-right">
<i class="fas fa-plus"></i> Create</a>
<h1 class="text-black-50">Services
<router-link to="/service/create" class="btn btn-outline-success mt-1 float-right"><i class="fas fa-plus"></i> Create</router-link>
</h1>
<table class="table">
<thead>
@ -33,7 +33,7 @@
</thead>
<tbody class="sortable" id="services_table">
<tr v-for="(service, index) in services" v-bind:key="index">
<tr v-for="(service, index) in $store.getters.services" v-bind:key="index">
<td><span class="drag_icon d-none d-md-inline"><i class="fas fa-bars"></i></span> {{service.name}}</td>
<td class="d-none d-md-table-cell"><span class="badge badge-success">ONLINE</span>
<i class="toggle-service fas fa-toggle-on text-success" data-online="true" data-id="1"></i>
@ -67,7 +67,7 @@
</thead>
<tbody class="sortable_groups" id="groups_table">
<tr v-for="(group, index) in groups" v-bind:key="index">
<tr v-for="(group, index) in $store.getters.groups" v-bind:key="index">
<td><span class="drag_icon d-none d-md-inline"><i class="fas fa-bars"></i></span>{{group.name}}</td>
<td></td>
<td><span class="badge badge-secondary">PRIVATE</span></td>
@ -123,10 +123,6 @@
export default {
name: 'DashboardServices',
props: {
services: Array,
groups: Array,
},
data () {
return {

View File

@ -27,7 +27,7 @@
</thead>
<tbody id="users_table">
<tr v-for="(user, index) in users" v-bind:key="index" >
<tr v-for="(user, index) in $store.getters.users" v-bind:key="index" >
<td>{{user.username}}</td>
<td class="text-right">
<div class="btn-group">
@ -95,16 +95,13 @@
name: 'DashboardUsers',
data () {
return {
users: null
}
},
created() {
this.getUsers()
},
methods: {
async getUsers () {
this.users = await Api.users()
}
}
}
</script>

View File

@ -0,0 +1,185 @@
<template>
<div class="col-12">
<div class="card">
<div class="card-body">
<form class="ajax_form" action="api/services" data-redirect="services" method="POST">
<h4 class="mb-5 text-muted">Basic Information</h4>
<div class="form-group row">
<label for="service_name" class="col-sm-4 col-form-label">Service Name</label>
<div class="col-sm-8">
<input type="text" name="name" class="form-control" id="service_name" value="" placeholder="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">Service Type</label>
<div class="col-sm-8">
<select name="type" class="form-control" id="service_type" value="" >
<option value="http" >HTTP Service</option>
<option value="tcp" >TCP Service</option>
<option value="udp" >UDP Service</option>
<option value="icmp" >ICMP Ping</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_url" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
<div class="col-sm-8">
<input type="text" name="domain" class="form-control" id="service_url" value="" placeholder="https://google.com" required autocapitalize="none" spellcheck="false">
<small class="form-text text-muted">Statping will attempt to connect to this URL</small>
</div>
</div>
<div class="form-group row">
<label for="service_type" class="col-sm-4 col-form-label">Group</label>
<div class="col-sm-8">
<select name="group_id" class="form-control" id="group_id">
<option value="0" selected>None</option>
<option value="1" >JSON Test Servers</option>
<option value="2" >Google Servers</option>
<option value="3" >Statping Servers</option>
</select>
<small class="form-text text-muted">Attach this service to a group</small>
</div>
</div>
<h4 class="mt-5 mb-5 text-muted">Request Details</h4>
<div class="form-group row">
<label for="service_check_type" class="col-sm-4 col-form-label">Service Check Type</label>
<div class="col-sm-8">
<select name="method" class="form-control" id="service_check_type" value="">
<option value="GET" >GET</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 request will simply request the endpoint, you can also send data with POST.</small>
</div>
</div>
<div class="form-group row d-none">
<label for="post_data" class="col-sm-4 col-form-label">Optional Post Data (JSON)</label>
<div class="col-sm-8">
<textarea name="post_data" class="form-control" id="post_data" 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 class="form-group row">
<label for="headers" class="col-sm-4 col-form-label">HTTP Headers</label>
<div class="col-sm-8">
<input name="headers" class="form-control" id="headers" autocapitalize="none" spellcheck="false" placeholder='Authorization=1010101,Content-Type=application/json' value="">
<small class="form-text text-muted">Comma delimited list of HTTP Headers (KEY=VALUE,KEY=VALUE)</small>
</div>
</div>
<div class="form-group row">
<label for="service_response" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
<div class="col-sm-8">
<textarea name="expected" class="form-control" id="service_response" 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 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 type="number" name="expected_status" class="form-control" value="200" 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 class="form-group row d-none">
<label for="port" class="col-sm-4 col-form-label">TCP Port</label>
<div class="col-sm-8">
<input type="number" name="port" class="form-control" value="" id="service_port" placeholder="8080">
</div>
</div>
<h4 class="mt-5 mb-5 text-muted">Additional Options</h4>
<div class="form-group row">
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
<div class="col-sm-8">
<input type="number" name="check_interval" class="form-control" value="60" min="1" id="service_interval" required>
<small id="interval" class="form-text text-muted">10,000+ will be checked in Microseconds (1 millisecond = 1000 microseconds).</small>
</div>
</div>
<div class="form-group row">
<label for="service_timeout" class="col-sm-4 col-form-label">Timeout in Seconds</label>
<div class="col-sm-8">
<input type="number" name="timeout" class="form-control" value="15" placeholder="15" id="service_timeout" min="1">
<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>
<div class="form-group row">
<label for="post_data" class="col-sm-4 col-form-label">Permalink URL</label>
<div class="col-sm-8">
<input type="text" name="permalink" class="form-control" value="" 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 d-none">
<label for="order" class="col-sm-4 col-form-label">List Order</label>
<div class="col-sm-8">
<input type="number" name="order" class="form-control" min="0" value="0" id="order">
<small class="form-text text-muted">You can also drag and drop services to reorder on the Services tab.</small>
</div>
</div>
<div class="form-group row">
<label for="order" class="col-sm-4 col-form-label">Verify SSL</label>
<div class="col-8 mt-1">
<span class="switch float-left">
<input type="checkbox" name="verify_ssl-option" class="switch" id="switch-verify-ssl" checked>
<label for="switch-verify-ssl">Verify SSL Certificate for this service</label>
<input type="hidden" name="verify_ssl" id="switch-verify-ssl-value" value="true">
</span>
</div>
</div>
<div class="form-group row">
<label for="order" class="col-sm-4 col-form-label">Notifications</label>
<div class="col-8 mt-1">
<span class="switch float-left">
<input type="checkbox" name="allow_notifications-option" class="switch" id="switch-notifications" checked>
<label for="switch-notifications">Allow notifications to be sent for this service</label>
<input type="hidden" name="allow_notifications" id="switch-notifications-value" value="true">
</span>
</div>
</div>
<div class="form-group row">
<label for="order" class="col-sm-4 col-form-label">Visible</label>
<div class="col-8 mt-1">
<span class="switch float-left">
<input type="checkbox" name="public-option" class="switch" id="switch-public" checked>
<label for="switch-public">Show service details to the public</label>
<input type="hidden" name="public" id="switch-public-value" value="true">
</span>
</div>
</div>
<div class="form-group row">
<div class="col-12">
<button type="submit" class="btn btn-success btn-block">Create Service</button>
</div>
</div>
<div class="alert alert-danger d-none" id="alerter" role="alert"></div>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ServiceForm',
props: {
service: Object
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

View File

@ -26,7 +26,10 @@
export default {
name: 'ServiceInfo',
props: {
service: Object
service: {
type: Object,
required: true
}
}
}
</script>

View File

@ -50,6 +50,8 @@
methods: {
async logout () {
await Api.logout()
this.$store.commit('setHasAllData', false)
this.$store.commit('setToken', null)
await this.$router.push('/')
}
}

View File

@ -1,24 +1,31 @@
<template>
<div class="col-12 full-col-12">
<h4 class="group_header">{{group.name}}</h4>
<h4 v-if="group.name !== 'Empty Group'" class="group_header">{{group.name}}</h4>
<div class="list-group online_list mb-3">
<GroupedService/>
<a v-for="(service, index) in $store.getters.servicesInGroup(group.id)" v-bind:key="index" href="#" class="service_li list-group-item list-group-item-action " data-id="7">
{{service.name}}
<span class="badge bg-success float-right pulse-glow">{{service.online ? "ONLINE" : "OFFLINE"}}</span>
</a>
</div>
</div>
</template>
<script>
import GroupedService from './GroupedService'
export default {
name: 'Group',
components: {
GroupedService
},
props: {
group: Object
},
methods: {
serviceBadge (s) {
}
}
}
</script>

View File

@ -1,19 +0,0 @@
<template>
<a href="#" class="service_li list-group-item list-group-item-action " data-id="7">
Statping API
<span class="badge bg-success float-right pulse-glow">ONLINE</span>
</a>
</template>
<script>
export default {
name: 'GroupedService',
props: {
service: Object
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

View File

@ -1,16 +1,13 @@
<template>
<div>
<h1 class="col-12 text-center mb-4 mt-sm-3 header-title">{{core.name}}</h1>
<h5 class="col-12 text-center mb-5 header-desc">{{core.description}}</h5>
<h1 class="col-12 text-center mb-4 mt-sm-3 header-title">{{$store.getters.core.name}}</h1>
<h5 class="col-12 text-center mb-5 header-desc">{{$store.getters.core.description}}</h5>
</div>
</template>
<script>
export default {
name: 'Header',
props: {
core: Object
}
}
</script>

View File

@ -0,0 +1,14 @@
class Time {
now () {
return new Date();
}
utc () {
return new Date().getUTCDate();
}
utcToLocal (utc) {
let u = new Date().setUTCDate(utc)
return u.toLocaleString()
}
}
const time = new Time()
export default time

View File

@ -0,0 +1,121 @@
<template>
<form @submit="saveSettings" method="POST" action="settings">
<div class="form-group">
<label>Project Name</label>
<input v-model="core.name" type="text" class="form-control" placeholder="Great Uptime">
</div>
<div class="form-group">
<label>Project Description</label>
<input v-model="core.description" type="text" class="form-control" placeholder="Great Uptime">
</div>
<div class="form-group row">
<div class="col-8 col-sm-9">
<label>Domain</label>
<input v-model="core.domain" type="url" class="form-control">
</div>
<div class="col-4 col-sm-3 mt-sm-1 mt-0">
<label class="d-inline d-sm-none">Enable CDN</label>
<label class="d-none d-sm-block">Enable CDN</label>
<span class="switch">
<input v-model="core.using_cdn" type="checkbox" class="switch" v-bind:disabled="core.using_cdn">
<label class="mt-2 mt-sm-0"></label>
</span>
<input v-model="core.using_cdn" type="hidden" name="enable_cdn" id="switch-normal-value" value="false">
</div>
</div>
<div class="form-group">
<label>Custom Footer</label>
<textarea rows="4" class="form-control">{{core.footer}}</textarea>
<small class="form-text text-muted">HTML is allowed inside the footer</small>
</div>
<div class="form-group">
<label for="timezone">Timezone</label><span class="mt-1 small float-right">Current: {{now}}</span>
<select class="form-control" name="timezone" id="timezone">
<option value="-12.0" >(GMT -12:00) Eniwetok, Kwajalein</option>
<option value="-11.0" >(GMT -11:00) Midway Island, Samoa</option>
<option value="-10.0" >(GMT -10:00) Hawaii</option>
<option value="-9.0" >(GMT -9:00) Alaska</option>
<option value="-8.0" selected>(GMT -8:00) Pacific Time (US &amp; Canada)</option>
<option value="-7.0" >(GMT -7:00) Mountain Time (US &amp; Canada)</option>
<option value="-6.0" >(GMT -6:00) Central Time (US &amp; Canada), Mexico City</option>
<option value="-5.0" >(GMT -5:00) Eastern Time (US &amp; Canada), Bogota, Lima</option>
<option value="-4.0" >(GMT -4:00) Atlantic Time (Canada), Caracas, La Paz</option>
<option value="-3.5" >(GMT -3:30) Newfoundland</option>
<option value="-3.0" >(GMT -3:00) Brazil, Buenos Aires, Georgetown</option>
<option value="-2.0" >(GMT -2:00) Mid-Atlantic</option>
<option value="-1.0" >(GMT -1:00 hour) Azores, Cape Verde Islands</option>
<option value="0.0" >(GMT) Western Europe Time, London, Lisbon, Casablanca</option>
<option value="1.0" >(GMT +1:00 hour) Brussels, Copenhagen, Madrid, Paris</option>
<option value="2.0" >(GMT +2:00) Kaliningrad, South Africa</option>
<option value="3.0" >(GMT +3:00) Baghdad, Riyadh, Moscow, St. Petersburg</option>
<option value="3.5" >(GMT +3:30) Tehran</option>
<option value="4.0" >(GMT +4:00) Abu Dhabi, Muscat, Baku, Tbilisi</option>
<option value="4.5" >(GMT +4:30) Kabul</option>
<option value="5.0" >(GMT +5:00) Ekaterinburg, Islamabad, Karachi, Tashkent</option>
<option value="5.5" >(GMT +5:30) Bombay, Calcutta, Madras, New Delhi</option>
<option value="5.75" >(GMT +5:45) Kathmandu</option>
<option value="6.0" >(GMT +6:00) Almaty, Dhaka, Colombo</option>
<option value="7.0" >(GMT +7:00) Bangkok, Hanoi, Jakarta</option>
<option value="8.0" >(GMT +8:00) Beijing, Perth, Singapore, Hong Kong</option>
<option value="9.0" >(GMT +9:00) Tokyo, Seoul, Osaka, Sapporo, Yakutsk</option>
<option value="9.5" >(GMT +9:30) Adelaide, Darwin</option>
<option value="10.0" >(GMT +10:00) Eastern Australia, Guam, Vladivostok</option>
<option value="11.0" >(GMT +11:00) Magadan, Solomon Islands, New Caledonia</option>
<option value="12.0" >(GMT +12:00) Auckland, Wellington, Fiji, Kamchatka</option>
</select>
</div>
<button type="submit" class="btn btn-primary btn-block">Save Settings</button>
<div class="form-group row mt-3">
<label for="api_key" class="col-sm-3 col-form-label">API Key</label>
<div class="col-sm-9">
<input type="text" class="form-control select-input" value="9e657102489b63946908a084befc187e6e506eb0" id="api_key" readonly>
<small class="form-text text-muted">API Key can be used for read only routes</small>
</div>
</div>
<div class="form-group row">
<label for="api_secret" class="col-sm-3 col-form-label">API Secret</label>
<div class="col-sm-9">
<input type="text" class="form-control select-input" value="6b05b48f4b3a1460f3864c31b26cab6a27dbaff9" id="api_secret" 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 class="confirm_btn" data-msg="Are you sure you want to reset the API keys?" href="api/renew">Regenerate API Keys</a> if you need to.</small>
</div>
</div>
</form>
</template>
<script>
import time from '../components/Time'
export default {
name: 'CoreSettings',
data () {
return {
core: this.$store.getters.core,
}
},
computed: {
now () {
return time.now()
}
},
methods: {
saveSettings () {
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

View File

@ -0,0 +1,178 @@
<template>
<form class="ajax_form" action="api/services" data-redirect="services" method="POST">
<h4 class="mb-5 text-muted">Basic Information</h4>
<div class="form-group row">
<label for="service_name" class="col-sm-4 col-form-label">Service Name</label>
<div class="col-sm-8">
<input type="text" name="name" class="form-control" id="service_name" value="" placeholder="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">Service Type</label>
<div class="col-sm-8">
<select name="type" class="form-control" id="service_type" value="" >
<option value="http" >HTTP Service</option>
<option value="tcp" >TCP Service</option>
<option value="udp" >UDP Service</option>
<option value="icmp" >ICMP Ping</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_url" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
<div class="col-sm-8">
<input type="text" name="domain" class="form-control" id="service_url" value="" placeholder="https://google.com" required autocapitalize="none" spellcheck="false">
<small class="form-text text-muted">Statping will attempt to connect to this URL</small>
</div>
</div>
<div class="form-group row">
<label for="service_type" class="col-sm-4 col-form-label">Group</label>
<div class="col-sm-8">
<select name="group_id" class="form-control" id="group_id">
<option value="0" selected>None</option>
<option value="1" >JSON Test Servers</option>
<option value="2" >Google Servers</option>
<option value="3" >Statping Servers</option>
</select>
<small class="form-text text-muted">Attach this service to a group</small>
</div>
</div>
<h4 class="mt-5 mb-5 text-muted">Request Details</h4>
<div class="form-group row">
<label for="service_check_type" class="col-sm-4 col-form-label">Service Check Type</label>
<div class="col-sm-8">
<select name="method" class="form-control" id="service_check_type" value="">
<option value="GET" >GET</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 request will simply request the endpoint, you can also send data with POST.</small>
</div>
</div>
<div class="form-group row d-none">
<label for="post_data" class="col-sm-4 col-form-label">Optional Post Data (JSON)</label>
<div class="col-sm-8">
<textarea name="post_data" class="form-control" id="post_data" 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 class="form-group row">
<label for="headers" class="col-sm-4 col-form-label">HTTP Headers</label>
<div class="col-sm-8">
<input name="headers" class="form-control" id="headers" autocapitalize="none" spellcheck="false" placeholder='Authorization=1010101,Content-Type=application/json' value="">
<small class="form-text text-muted">Comma delimited list of HTTP Headers (KEY=VALUE,KEY=VALUE)</small>
</div>
</div>
<div class="form-group row">
<label for="service_response" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
<div class="col-sm-8">
<textarea name="expected" class="form-control" id="service_response" 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 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 type="number" name="expected_status" class="form-control" value="200" 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 class="form-group row d-none">
<label for="port" class="col-sm-4 col-form-label">TCP Port</label>
<div class="col-sm-8">
<input type="number" name="port" class="form-control" value="" id="service_port" placeholder="8080">
</div>
</div>
<h4 class="mt-5 mb-5 text-muted">Additional Options</h4>
<div class="form-group row">
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
<div class="col-sm-8">
<input type="number" name="check_interval" class="form-control" value="60" min="1" id="service_interval" required>
<small id="interval" class="form-text text-muted">10,000+ will be checked in Microseconds (1 millisecond = 1000 microseconds).</small>
</div>
</div>
<div class="form-group row">
<label for="service_timeout" class="col-sm-4 col-form-label">Timeout in Seconds</label>
<div class="col-sm-8">
<input type="number" name="timeout" class="form-control" value="15" placeholder="15" id="service_timeout" min="1">
<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>
<div class="form-group row">
<label for="post_data" class="col-sm-4 col-form-label">Permalink URL</label>
<div class="col-sm-8">
<input type="text" name="permalink" class="form-control" value="" 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 d-none">
<label for="order" class="col-sm-4 col-form-label">List Order</label>
<div class="col-sm-8">
<input type="number" name="order" class="form-control" min="0" value="0" id="order">
<small class="form-text text-muted">You can also drag and drop services to reorder on the Services tab.</small>
</div>
</div>
<div class="form-group row">
<label for="order" class="col-sm-4 col-form-label">Verify SSL</label>
<div class="col-8 mt-1">
<span class="switch float-left">
<input type="checkbox" name="verify_ssl-option" class="switch" id="switch-verify-ssl" checked>
<label for="switch-verify-ssl">Verify SSL Certificate for this service</label>
<input type="hidden" name="verify_ssl" id="switch-verify-ssl-value" value="true">
</span>
</div>
</div>
<div class="form-group row">
<label for="order" class="col-sm-4 col-form-label">Notifications</label>
<div class="col-8 mt-1">
<span class="switch float-left">
<input type="checkbox" name="allow_notifications-option" class="switch" id="switch-notifications" checked>
<label for="switch-notifications">Allow notifications to be sent for this service</label>
<input type="hidden" name="allow_notifications" id="switch-notifications-value" value="true">
</span>
</div>
</div>
<div class="form-group row">
<label for="order" class="col-sm-4 col-form-label">Visible</label>
<div class="col-8 mt-1">
<span class="switch float-left">
<input type="checkbox" name="public-option" class="switch" id="switch-public" checked>
<label for="switch-public">Show service details to the public</label>
<input type="hidden" name="public" id="switch-public-value" value="true">
</span>
</div>
</div>
<div class="form-group row">
<div class="col-12">
<button type="submit" class="btn btn-success btn-block">Create Service</button>
</div>
</div>
<div class="alert alert-danger d-none" id="alerter" role="alert"></div>
</form>
</template>
<script>
export default {
name: 'FormService',
props: {
service: Object
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

View File

@ -1,15 +1,18 @@
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import Index from "./pages/Index";
import Dashboard from "./pages/Dashboard";
import Login from "./pages/Login";
import Settings from "./pages/Settings";
import Services from "./pages/Services";
import Service from "./pages/Service";
require("./assets/css/bootstrap.min.css")
require("./assets/css/base.css")
import App from '@/App.vue'
import store from '@/store'
const Index = () => import("@/pages/Index");
const Dashboard = () => import("@/pages/Dashboard");
const Login = () => import("@/pages/Login");
const Settings = () => import("@/pages/Settings");
const Services = () => import("@/pages/Services");
const Service = () => import("@/pages/Service");
require("@/assets/css/bootstrap.min.css")
require("@/assets/css/base.css")
// require("./assets/js/bootstrap.min")
// require("./assets/js/flatpickr")
@ -27,7 +30,8 @@ const routes = [
path: '/dashboard',
name: 'Dashboard',
component: Dashboard,
alias: ['/dashboard/settings', '/dashboard/services', '/dashboard/messages', '/dashboard/groups', '/dashboard/users', '/dashboard/logs', '/dashboard/help']
alias: ['/dashboard/settings', '/dashboard/services', '/dashboard/messages', '/dashboard/groups', '/dashboard/users', '/dashboard/logs', '/dashboard/help',
'/service/create']
},
{
path: '/login',
@ -63,5 +67,6 @@ Vue.use(VueRouter);
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App),
}).$mount('#app')

View File

@ -1,20 +1,22 @@
<template>
<div>
<Login v-show="token === null"/>
<Login v-show="$store.getters.token === null"/>
<div v-show="token !== null" class="container col-md-7 col-sm-12 mt-md-5 bg-light">
<div v-show="$store.getters.token !== null" class="container col-md-7 col-sm-12 mt-md-5 bg-light">
<TopNav :changeView="changeView"/>
<DashboardIndex v-show="view === 'DashboardIndex'" :services="services"/>
<DashboardIndex v-show="view === 'DashboardIndex'"/>
<DashboardServices v-show="view === 'DashboardServices'" :services="services"/>
<DashboardServices v-show="view === 'DashboardServices'"/>
<DashboardUsers v-show="view === 'DashboardUsers'" :services="services"/>
<DashboardUsers v-show="view === 'DashboardUsers'"/>
<DashboardMessages v-show="view === 'DashboardMessages'" :services="services"/>
<DashboardMessages v-show="view === 'DashboardMessages'"/>
<Settings v-show="view === 'Settings'" :services="services"/>
<Settings v-show="view === 'Settings'"/>
<ServiceForm v-show="view === 'ServiceForm'"/>
</div>
</div>
@ -22,6 +24,7 @@
<script>
import Api from "../components/API"
import ServiceForm from '../components/Dashboard/ServiceForm';
import Login from "./Login";
import TopNav from "../components/Dashboard/TopNav";
import DashboardIndex from "../components/Dashboard/DashboardIndex";
@ -33,6 +36,7 @@
export default {
name: 'Dashboard',
components: {
ServiceForm,
Settings,
DashboardMessages,
DashboardUsers,
@ -43,17 +47,13 @@
},
data () {
return {
services: null,
groups: null,
core: null,
token: null,
view: "DashboardIndex",
authenticated: false
}
},
created() {
this.pathView(this.$route.path)
this.token = Api.token()
this.loadAll()
this.isAuthenticated()
},
methods: {
pathView (path) {
@ -70,6 +70,9 @@
case "/dashboard/services":
this.view = "DashboardServices"
break
case "/service/create":
this.view = "ServiceForm"
break
default:
this.view = "DashboardIndex"
}
@ -78,11 +81,25 @@
this.view = v
this.$router.push('/'+name)
},
async loadAll () {
this.token = await Api.token()
this.core = await Api.root()
this.groups = await Api.groups()
this.services = await Api.services()
isAuthenticated () {
const token = this.$store.getters.token
if (token.token) {
this.authenticated = true
if (!this.$store.getters.hasAllData) {
this.loadAllData()
}
}
},
async loadAllData () {
const users = await Api.users()
const groups = await Api.groups()
const messages = await Api.messages()
const notifiers = await Api.notifiers()
this.$store.commit('setMessages', messages)
this.$store.commit('setUsers', users)
this.$store.commit('setGroups', groups)
this.$store.commit('setNotifiers', notifiers)
this.$store.commit('setHasAllData', true)
}
}
}

View File

@ -1,9 +1,9 @@
<template>
<div v-show="core" class="container col-md-7 col-sm-12 mt-2 sm-container">
<div class="container col-md-7 col-sm-12 mt-2 sm-container">
<Header :core="core"/>
<Header/>
<div v-for="(group, index) in groups" v-bind:key="index">
<div v-for="(group, index) in $store.getters.groups" v-bind:key="index">
<Group :group=group />
</div>
@ -13,7 +13,7 @@
<div class="col-12 full-col-12">
<div v-for="(service, index) in services" v-bind:key="index">
<div v-for="(service, index) in $store.getters.services" v-bind:key="index">
<ServiceBlock :service=service />
</div>
@ -22,13 +22,12 @@
</template>
<script>
import ServiceBlock from '../components/Service/ServiceBlock.vue'
import MessageBlock from "../components/Index/MessageBlock";
import Group from "../components/Index/Group";
import Header from "../components/Index/Header";
import Api from "../components/API"
const Header = () => import("@/components/Index/Header");
const ServiceBlock = () => import("@/components/Service/ServiceBlock.vue");
const MessageBlock = () => import("@/components/Index/MessageBlock");
const Group = () => import("@/components/Index/Group");
export default {
export default {
name: 'Index',
components: {
Header,
@ -38,26 +37,17 @@
},
data () {
return {
services: null,
groups: null,
core: null,
auth: null
}
},
created() {
this.auth = Api.authToken()
this.core = Api.root()
},
mounted() {
this.loadAll()
},
methods: {
async loadAll () {
this.auth = Api.authToken()
this.core = await Api.root()
this.groups = await Api.groups()
this.services = await Api.services()
}
}
}
</script>

View File

@ -2,9 +2,8 @@
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
<div class="col-10 offset-1 col-md-8 offset-md-2 mt-md-2">
<div class="col-12 col-md-8 offset-md-2 mb-4">
<img class="col-12 mt-5 mt-md-0" :src="require(`@/assets/banner.png`)">
<img class="col-12 mt-5 mt-md-0" src="../assets/banner.png">
</div>
{{auth}}
<form id="login_form" @submit="login" method="post">
<div class="form-group row">
<label for="username" class="col-sm-2 col-form-label">Username</label>
@ -20,7 +19,7 @@
</div>
<div class="form-group row">
<div class="col-sm-12">
<button type="submit" class="btn btn-primary btn-block mb-3">Sign in</button>
<button v-on:click="login" type="submit" class="btn btn-primary btn-block mb-3">Sign in</button>
</div>
</div>
</form>
@ -49,7 +48,8 @@
const auth = await Api.login(this.username, this.password)
if (auth.token !== null) {
this.auth = Api.saveToken(this.username, auth.token)
await this.$router.push('/dashboard')
this.$store.commit('setToken', auth)
this.$router.push('/dashboard')
}
}
}

View File

@ -12,7 +12,7 @@
<h6 class="mt-4 text-muted">Notifiers</h6>
<a v-for="(notifier, index) in notifiers" v-bind:key="index" v-on:click="changeTab" class="nav-link text-capitalize" v-bind:class="{active: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`)}" v-bind:id="`v-pills-${notifier.method.toLowerCase()}-tab`" data-toggle="pill" v-bind:href="`#v-pills-${notifier.method.toLowerCase()}`" role="tab" v-bind:aria-controls="`v-pills-${notifier.method.toLowerCase()}`" aria-selected="false"><i class="fas fa-terminal"></i> {{notifier.method}}</a>
<a v-for="(notifier, index) in $store.getters.notifiers" v-bind:key="index" v-on:click="changeTab" class="nav-link text-capitalize" v-bind:class="{active: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`)}" v-bind:id="`v-pills-${notifier.method.toLowerCase()}-tab`" data-toggle="pill" v-bind:href="`#v-pills-${notifier.method.toLowerCase()}`" role="tab" v-bind:aria-controls="`v-pills-${notifier.method.toLowerCase()}`" aria-selected="false"><i class="fas fa-terminal"></i> {{notifier.method}}</a>
<h6 class="mt-4 text-muted">Integrations (beta)</h6>
@ -28,98 +28,8 @@
<div class="tab-content" id="v-pills-tabContent">
<div class="tab-pane fade" v-bind:class="{active: liClass('v-pills-home-tab'), show: liClass('v-pills-home-tab')}" id="v-pills-home" role="tabpanel" aria-labelledby="v-pills-home-tab">
<form method="POST" action="settings">
<div class="form-group">
<label for="project">Project Name</label>
<input type="text" name="project" class="form-control" value="Statup Demo" id="project" placeholder="Great Uptime">
</div>
<div class="form-group">
<label for="description">Project Description</label>
<input type="text" name="description" class="form-control" value="An Awesome Demo of Statping running on Docker" id="description" placeholder="Great Uptime">
</div>
<div class="form-group row">
<div class="col-8 col-sm-9">
<label for="domain">Domain</label>
<input type="url" name="domain" class="form-control" value="https://demo.statping.com" id="domain">
</div>
<div class="col-4 col-sm-3 mt-sm-1 mt-0">
<label for="enable_cdn" class="d-inline d-sm-none">Enable CDN</label>
<label for="enable_cdn" class="d-none d-sm-block">Enable CDN</label>
<span class="switch">
<input type="checkbox" name="enable_cdn-option" class="switch" id="switch-normal" disabled>
<label for="switch-normal" class="mt-2 mt-sm-0"></label>
</span>
<input type="hidden" name="enable_cdn" id="switch-normal-value" value="false">
</div>
</div>
<div class="form-group">
<label for="footer">Custom Footer</label>
<textarea rows="4" name="footer" class="form-control" id="footer"></textarea>
<small class="form-text text-muted">HTML is allowed inside the footer</small>
</div>
<div class="form-group">
<label for="timezone">Timezone</label><span class="mt-1 small float-right">Current: Wednesday 08:03:24 PM</span>
<select class="form-control" name="timezone" id="timezone">
<option value="-12.0" >(GMT -12:00) Eniwetok, Kwajalein</option>
<option value="-11.0" >(GMT -11:00) Midway Island, Samoa</option>
<option value="-10.0" >(GMT -10:00) Hawaii</option>
<option value="-9.0" >(GMT -9:00) Alaska</option>
<option value="-8.0" selected>(GMT -8:00) Pacific Time (US &amp; Canada)</option>
<option value="-7.0" >(GMT -7:00) Mountain Time (US &amp; Canada)</option>
<option value="-6.0" >(GMT -6:00) Central Time (US &amp; Canada), Mexico City</option>
<option value="-5.0" >(GMT -5:00) Eastern Time (US &amp; Canada), Bogota, Lima</option>
<option value="-4.0" >(GMT -4:00) Atlantic Time (Canada), Caracas, La Paz</option>
<option value="-3.5" >(GMT -3:30) Newfoundland</option>
<option value="-3.0" >(GMT -3:00) Brazil, Buenos Aires, Georgetown</option>
<option value="-2.0" >(GMT -2:00) Mid-Atlantic</option>
<option value="-1.0" >(GMT -1:00 hour) Azores, Cape Verde Islands</option>
<option value="0.0" >(GMT) Western Europe Time, London, Lisbon, Casablanca</option>
<option value="1.0" >(GMT +1:00 hour) Brussels, Copenhagen, Madrid, Paris</option>
<option value="2.0" >(GMT +2:00) Kaliningrad, South Africa</option>
<option value="3.0" >(GMT +3:00) Baghdad, Riyadh, Moscow, St. Petersburg</option>
<option value="3.5" >(GMT +3:30) Tehran</option>
<option value="4.0" >(GMT +4:00) Abu Dhabi, Muscat, Baku, Tbilisi</option>
<option value="4.5" >(GMT +4:30) Kabul</option>
<option value="5.0" >(GMT +5:00) Ekaterinburg, Islamabad, Karachi, Tashkent</option>
<option value="5.5" >(GMT +5:30) Bombay, Calcutta, Madras, New Delhi</option>
<option value="5.75" >(GMT +5:45) Kathmandu</option>
<option value="6.0" >(GMT +6:00) Almaty, Dhaka, Colombo</option>
<option value="7.0" >(GMT +7:00) Bangkok, Hanoi, Jakarta</option>
<option value="8.0" >(GMT +8:00) Beijing, Perth, Singapore, Hong Kong</option>
<option value="9.0" >(GMT +9:00) Tokyo, Seoul, Osaka, Sapporo, Yakutsk</option>
<option value="9.5" >(GMT +9:30) Adelaide, Darwin</option>
<option value="10.0" >(GMT +10:00) Eastern Australia, Guam, Vladivostok</option>
<option value="11.0" >(GMT +11:00) Magadan, Solomon Islands, New Caledonia</option>
<option value="12.0" >(GMT +12:00) Auckland, Wellington, Fiji, Kamchatka</option>
</select>
</div>
<button type="submit" class="btn btn-primary btn-block">Save Settings</button>
<div class="form-group row mt-3">
<label for="api_key" class="col-sm-3 col-form-label">API Key</label>
<div class="col-sm-9">
<input type="text" class="form-control select-input" value="9e657102489b63946908a084befc187e6e506eb0" id="api_key" readonly>
<small class="form-text text-muted">API Key can be used for read only routes</small>
</div>
</div>
<div class="form-group row">
<label for="api_secret" class="col-sm-3 col-form-label">API Secret</label>
<div class="col-sm-9">
<input type="text" class="form-control select-input" value="6b05b48f4b3a1460f3864c31b26cab6a27dbaff9" id="api_secret" 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 class="confirm_btn" data-msg="Are you sure you want to reset the API keys?" href="api/renew">Regenerate API Keys</a> if you need to.</small>
</div>
</div>
</form>
<CoreSettings/>
<h2 class="mt-5">Bulk Import Services</h2>
You can import multiple services based on a CSV file with the format shown on the <a href="https://github.com/hunterlong/statping/wiki/Bulk-Import-Services" target="_blank">Bulk Import Wiki</a>.
@ -214,210 +124,6 @@
</thead>
<tbody>
<tr>
<td>/api/services/1/data?start=1577923728&amp;end=9999999999&amp;group=hour</td>
<td>14129</td>
<td>2020-01-15 16:09:20 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/1/data?start=1577923878&amp;end=9999999999&amp;group=hour</td>
<td>14129</td>
<td>2020-01-15 16:11:49 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/1/data?start=1577935040&amp;end=9999999999&amp;group=hour</td>
<td>14255</td>
<td>2020-01-15 19:17:52 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/1/data?start=1577935058&amp;end=9999999999&amp;group=hour</td>
<td>14255</td>
<td>2020-01-15 19:18:10 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/1/data?start=1577937580&amp;end=9999999999&amp;group=hour</td>
<td>14255</td>
<td>2020-01-15 20:00:11 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/2/data?start=1577923728&amp;end=9999999999&amp;group=hour</td>
<td>14165</td>
<td>2020-01-15 16:09:21 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/2/data?start=1577923878&amp;end=9999999999&amp;group=hour</td>
<td>14165</td>
<td>2020-01-15 16:11:49 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/2/data?start=1577935040&amp;end=9999999999&amp;group=hour</td>
<td>14291</td>
<td>2020-01-15 19:17:53 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/2/data?start=1577935058&amp;end=9999999999&amp;group=hour</td>
<td>14291</td>
<td>2020-01-15 19:18:11 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/2/data?start=1577937580&amp;end=9999999999&amp;group=hour</td>
<td>14291</td>
<td>2020-01-15 20:00:11 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/3/data?start=1577923728&amp;end=9999999999&amp;group=hour</td>
<td>13941</td>
<td>2020-01-15 16:09:21 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/3/data?start=1577923878&amp;end=9999999999&amp;group=hour</td>
<td>13941</td>
<td>2020-01-15 16:11:50 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/3/data?start=1577935040&amp;end=9999999999&amp;group=hour</td>
<td>14064</td>
<td>2020-01-15 19:17:54 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/3/data?start=1577935058&amp;end=9999999999&amp;group=hour</td>
<td>14064</td>
<td>2020-01-15 19:18:12 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/3/data?start=1577937580&amp;end=9999999999&amp;group=hour</td>
<td>14064</td>
<td>2020-01-15 20:00:11 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/4/data?start=1577923728&amp;end=9999999999&amp;group=hour</td>
<td>14229</td>
<td>2020-01-15 16:09:22 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/4/data?start=1577923878&amp;end=9999999999&amp;group=hour</td>
<td>14229</td>
<td>2020-01-15 16:11:50 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/4/data?start=1577935040&amp;end=9999999999&amp;group=hour</td>
<td>14355</td>
<td>2020-01-15 19:17:54 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/4/data?start=1577935058&amp;end=9999999999&amp;group=hour</td>
<td>14355</td>
<td>2020-01-15 19:18:13 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/4/data?start=1577937580&amp;end=9999999999&amp;group=hour</td>
<td>14355</td>
<td>2020-01-15 20:00:12 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/5/data?start=1577923728&amp;end=9999999999&amp;group=hour</td>
<td>13491</td>
<td>2020-01-15 16:09:22 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/5/data?start=1577923878&amp;end=9999999999&amp;group=hour</td>
<td>13491</td>
<td>2020-01-15 16:11:50 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/5/data?start=1577935040&amp;end=9999999999&amp;group=hour</td>
<td>13611</td>
<td>2020-01-15 19:17:55 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/5/data?start=1577935058&amp;end=9999999999&amp;group=hour</td>
<td>13611</td>
<td>2020-01-15 19:18:14 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/5/data?start=1577937580&amp;end=9999999999&amp;group=hour</td>
<td>13611</td>
<td>2020-01-15 20:00:12 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/6/data?start=1577923728&amp;end=9999999999&amp;group=hour</td>
<td>13828</td>
<td>2020-01-15 16:09:19 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/6/data?start=1577923878&amp;end=9999999999&amp;group=hour</td>
<td>13828</td>
<td>2020-01-15 16:11:48 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/6/data?start=1577935040&amp;end=9999999999&amp;group=hour</td>
<td>13951</td>
<td>2020-01-15 19:17:52 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/6/data?start=1577935058&amp;end=9999999999&amp;group=hour</td>
<td>13951</td>
<td>2020-01-15 19:18:09 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/6/data?start=1577937580&amp;end=9999999999&amp;group=hour</td>
<td>13951</td>
<td>2020-01-15 20:00:10 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/7/data?start=1577923728&amp;end=9999999999&amp;group=hour</td>
<td>13828</td>
<td>2020-01-15 16:09:18 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/7/data?start=1577923878&amp;end=9999999999&amp;group=hour</td>
<td>13828</td>
<td>2020-01-15 16:11:48 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/7/data?start=1577935040&amp;end=9999999999&amp;group=hour</td>
<td>13951</td>
<td>2020-01-15 19:17:51 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/7/data?start=1577935058&amp;end=9999999999&amp;group=hour</td>
<td>13951</td>
<td>2020-01-15 19:18:08 -0800 -0800</td>
</tr>
<tr>
<td>/api/services/7/data?start=1577937580&amp;end=9999999999&amp;group=hour</td>
<td>13951</td>
@ -429,7 +135,7 @@
<a href="api/clear_cache" class="btn btn-danger btn-block">Clear Cache</a>
</div>
<div v-for="(notifier, index) in notifiers" v-bind:key="index" class="tab-pane fade" v-bind:class="{active: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`), show: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`)}" v-bind:id="`v-pills-${notifier.method.toLowerCase()}-tab`" role="tabpanel" v-bind:aria-labelledby="`v-pills-${notifier.method.toLowerCase()}-tab`">
<div v-for="(notifier, index) in $store.getters.notifiers" v-bind:key="index" class="tab-pane fade" v-bind:class="{active: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`), show: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`)}" v-bind:id="`v-pills-${notifier.method.toLowerCase()}-tab`" role="tabpanel" v-bind:aria-labelledby="`v-pills-${notifier.method.toLowerCase()}-tab`">
<form class="ajax_form command">
<h4 class="text-capitalize">{{notifier.title}}</h4>
@ -1368,28 +1074,26 @@
<script>
import Api from "../components/API"
import CoreSettings from '../forms/CoreSettings';
export default {
name: 'Settings',
components: {
CoreSettings
},
data () {
return {
tab: "v-pills-home-tab",
notifiers: null
}
},
created() {
this.getNotifiers()
},
beforeMount() {
},
methods: {
async getNotifiers () {
this.notifiers = await Api.notifiers()
},
changeTab (e) {
this.tab = e.target.id
},

83
frontend/src/store.js Normal file
View File

@ -0,0 +1,83 @@
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
hasAllData: false,
hasPublicData: false,
core: {},
token: null,
services: [],
groups: [],
messages: [],
users: [],
notifiers: []
},
getters: {
hasAllData: state => state.hasAllData,
hasPublicData: state => state.hasPublicData,
core: state => state.core,
token: state => state.token,
services: state => state.services,
groups: state => state.groups,
messages: state => state.messages,
users: state => state.users,
notifiers: state => state.notifiers,
serviceById: (state) => (id) => {
return state.services.find(s => s.id === id)
},
serviceByName: (state) => (name) => {
return state.services.find(s => s.name === name)
},
servicesInGroup: (state) => (id) => {
return state.services.filter(s => s.group_id === id)
},
onlineServices: (state) => (online) => {
return state.services.filter(s => s.online === online)
},
groupById: (state) => (id) => {
return state.groups.find(g => g.id === id)
},
userById: (state) => (id) => {
return state.users.find(u => u.id === id)
},
messageById: (state) => (id) => {
return state.messages.find(m => m.id === id)
},
},
mutations: {
setHasAllData(state, bool) {
state.hasAllData = bool
},
setHasPublicData(state, bool) {
state.hasPublicData = bool
},
setCore(state, core) {
state.core = core
},
setToken(state, token) {
state.token = token
},
setServices(state, services) {
state.services = services
},
setGroups(state, groups) {
state.groups = groups
},
setMessages(state, messages) {
state.messages = messages
},
setUsers(state, users) {
state.users = users
},
setNotifiers(state, notifiers) {
state.notifiers = notifiers
}
},
actions: {
}
});

View File

@ -0,0 +1,9 @@
'use strict';
const environment = (process.env.NODE_ENV || 'development').trim();
if (environment === 'development') {
module.exports = require('./config/webpack.config.dev');
} else {
module.exports = require('./config/webpack.config.prod');
}

File diff suppressed because it is too large Load Diff