integrations

pull/342/head
hunterlong 2020-01-02 22:10:25 -08:00
parent 83ad6ab6db
commit 4fcd7d8347
44 changed files with 951 additions and 2352 deletions

View File

@ -272,6 +272,8 @@ func HelpEcho() {
fmt.Println(" ADMIN_PASS - Password for administrator account (default: admin)")
fmt.Println(" SASS - Set the absolute path to the sass binary location")
fmt.Println(" HTTP_PROXY - Use a HTTP Proxy for HTTP Requests")
fmt.Println(" AUTH_USERNAME - HTTP Basic Authentication username")
fmt.Println(" AUTH_PASSWORD - HTTP Basic Authentication password")
fmt.Println(" * You can insert environment variables into a '.env' file in root directory.")
fmt.Println("Give Statping a Star at https://github.com/hunterlong/statping")
}

View File

@ -19,6 +19,7 @@ import (
"errors"
"fmt"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/integrations"
"github.com/hunterlong/statping/notifiers"
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/types"
@ -68,6 +69,7 @@ func InitApp() {
checkServices()
AttachNotifiers()
CoreApp.Notifications = notifier.AllCommunications
CoreApp.Integrations = integrations.Integrations
go DatabaseMaintence()
SetupMode = false
}

View File

@ -1,15 +0,0 @@
{
"projectId": "bi8mhr",
"env": {
"DB_HOST": "localhost",
"DB_USER": "root",
"DB_DATABASE": "root",
"DB_PORT": "5432",
"DB_PASS": "password123",
"GO_ENV": "production"
},
"chromeWebSecurity": false,
"defaultCommandTimeout": 5000,
"requestTimeout": 5000,
"watchForFileChanges": false
}

View File

@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@ -1,65 +0,0 @@
/*
* Statping
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statping
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
context('Setup Process', () => {
// it('should go to setup Statping with Postgres', () => {
// cy.visit('http://localhost:8080')
// cy.get('select[name=db_connection]').select('postgres')
// cy.get('input[name="db_host"]').clear().type(Cypress.env('DB_HOST'))
// cy.get('input[name="db_port"]').clear().type('5432')
// cy.get('input[name="db_user"]').clear().type(Cypress.env('DB_USER'))
// if (Cypress.env('TRAVIS')==="yes") {
// cy.get('input[name="db_password"]').clear()
// } else {
// cy.get('input[name="db_password"]').clear().type(Cypress.env('DB_PASS'))
// }
// cy.get('input[name="db_database"]').clear().type(Cypress.env('DB_DATABASE'))
// cy.get('input[name="project"]').clear().type('Demo Tester')
// cy.get('input[name="description"]').clear().type('This is a test from Crypress!')
// cy.get('input[name="domain"]').clear().type('http://localhost:8080')
// cy.get('input[name="username"]').clear().type('admin')
// cy.get('input[name="email"]').clear().type('info@domain.com')
// cy.get('input[name="password"]').clear().type('admin')
// cy.scrollTo('bottom')
// cy.get('#setup_button').click().wait(10000)
// cy.get('.header-title').should('contain', 'Demo Tester')
// cy.get('.header-desc').should('contain', 'This is a test from Crypress!')
// cy.scrollTo('bottom')
// cy.get('.service_li').should('have.length', 5)
// cy.get('.card').should('have.length', 5)
// })
it('should go to setup Statping with SQLite', () => {
cy.visit('http://localhost:8080')
cy.get('select[name=db_connection]').select('sqlite')
cy.get('input[name="project"]').clear().type('Demo Tester')
cy.get('input[name="description"]').clear().type('This is a test from Crypress!')
cy.get('input[name="domain"]').clear().type('http://localhost:8080')
cy.get('input[name="username"]').clear().type('admin')
cy.get('input[name="email"]').clear().type('info@domain.com')
cy.get('input[name="password"]').clear().type('admin')
cy.scrollTo('bottom')
cy.get('#setup_button').click()
cy.get('.header-title').should('contain', 'Demo Tester')
cy.get('.header-desc').should('contain', 'This is a test from Crypress!')
cy.scrollTo('bottom')
cy.get('.service_li').should('have.length', 5)
cy.get('.card').should('have.length', 5)
})
})

View File

@ -1,56 +0,0 @@
/*
* Statping
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statping
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
context('Asset Tests', () => {
beforeEach(function () {
cy.visit('http://localhost:8080/dashboard')
cy.get('input[name="username"]').type('admin')
cy.get('input[name="password"]').type('admin')
cy.get('form').submit()
})
it('should create local assets', () => {
cy.visit('http://localhost:8080/settings/build')
cy.get('#v-pills-style-tab').click()
cy.wait(500)
cy.get(':nth-child(2) > .CodeMirror-line').should('contain', '$background-color')
})
it('should save assets form', () => {
cy.request({method: 'POST', url: 'http://localhost:8080/settings/css', form: true, body: {
variables: '$tester: #bababa',
theme: '@import \'variables\'; .test-var { color: $tester; }'
}})
})
it('should confirm sass variable in css', () => {
cy.request('http://localhost:8080/css/base.css').its('body').should('contain', '.test-var')
})
it('should delete assets', () => {
cy.visit('http://localhost:8080/settings')
cy.get('#v-pills-style-tab').click()
cy.wait(500)
cy.get('.btn-danger').click()
})
it('should check css file after delete', () => {
cy.request('http://localhost:8080/css/base.css').its('body').should('contain', 'BODY')
})
});

View File

@ -1,41 +0,0 @@
/*
* Statping
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statping
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
context('Dashboard Tests', () => {
beforeEach(function() {
cy.visit('http://localhost:8080/dashboard')
cy.get('input[name="username"]').type('admin')
cy.get('input[name="password"]').type('admin')
cy.get('form').submit()
})
it('should view logs', () => {
cy.visit('http://localhost:8080/settings')
cy.get(':nth-child(5) > .nav-link').click()
cy.wait(10000)
cy.get('#live_logs').should('contain', 'Service')
})
it('should view help', () => {
cy.visit('http://localhost:8080/settings')
cy.get(':nth-child(6) > .nav-link').click()
cy.title().should('eq', 'Statping | Help')
cy.get('.col-12 > :nth-child(1)').should('contain', 'Statping')
})
});

View File

@ -1,115 +0,0 @@
/*
* Statping
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statping
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
context('Service Tests', () => {
beforeEach(function () {
cy.visit('http://localhost:8080/dashboard')
cy.get('input[name="username"]').type('admin')
cy.get('input[name="password"]').type('admin')
cy.get('form').submit()
})
it('should view services', () => {
cy.visit('http://localhost:8080/services')
cy.get('tr').should('have.length', 6)
cy.title().should('eq', 'Statping | Services')
})
it('should create HTTP GET service', () => {
cy.visit('http://localhost:8080/services')
cy.get('select[name="method"]').select('GET')
cy.get('input[name="name"]').clear().type('Google.com')
cy.get('select[name="check_type"]').select('http')
cy.get('input[name="domain"]').clear().type('https://google.com')
cy.get('input[name="expected_status"]').clear().type('200')
cy.get('input[name="interval"]').clear().type('25')
cy.get('input[name="timeout"]').clear().type('30')
cy.get('form').submit()
cy.title().should('eq', 'Statping | Services')
cy.get('tr').should('have.length', 7)
})
it('should create HTTP POST service', () => {
cy.visit('http://localhost:8080/services')
cy.get('select[name="method"]').select('POST')
cy.get('input[name="name"]').clear().type('JSON Regex Test')
cy.get('select[name="check_type"]').select('http')
cy.get('input[name="domain"]').clear().type('https://jsonplaceholder.typicode.com/posts')
cy.get('textarea[name="post_data"]').clear().type(`(title)": "((\\"|[statping])*)"`)
cy.get('input[name="expected_status"]').clear().type('201')
cy.get('input[name="interval"]').clear().type('15')
cy.get('input[name="timeout"]').clear().type('45')
cy.get('form').submit()
cy.title().should('eq', 'Statping | Services')
cy.get('tr').should('have.length', 8)
})
it('should create TCP service', () => {
cy.visit('http://localhost:8080/services')
cy.get('select[name="check_type"]').select('tcp')
cy.get('input[name="name"]').clear().type('Google DNS')
cy.get('input[name="domain"]').clear().type('8.8.8.8')
cy.get('input[name="port"]').clear().type('53')
cy.get('input[name="interval"]').clear().type('25')
cy.get('input[name="timeout"]').clear().type('15')
cy.get('form').submit()
cy.title().should('eq', 'Statping | Services')
cy.get('tr').should('have.length', 9)
})
it('should view HTTP GET service', () => {
cy.visit('http://localhost:8080/service/6')
cy.title().should('eq', 'Statping | Google.com Service')
})
it('should view HTTP POST service', () => {
cy.visit('http://localhost:8080/service/7')
cy.title().should('eq', 'Statping | JSON Regex Test Service')
})
it('should view TCP service', () => {
cy.visit('http://localhost:8080/service/8')
cy.title().should('eq', 'Statping | Google DNS Service')
})
it('should update HTTP service', () => {
cy.visit('http://localhost:8080/service/6')
cy.title().should('eq', 'Statping | Google.com Service')
cy.get('#service_name').clear().type('Google Updated')
cy.get('#service_interval').clear().type('60')
cy.get(':nth-child(3) > form').submit()
cy.title().should('eq', 'Statping | Google Updated Service')
cy.get('#service_name').should('have.value', 'Google Updated')
});
it('should check the updated service', () => {
cy.visit('http://localhost:8080/service/6')
cy.title().should('eq', 'Statping | Google Updated Service')
cy.get('#service_name').should('have.value', 'Google Updated')
cy.get('#service_interval').should('have.value', '60')
})
it('should delete a service', () => {
cy.visit('http://localhost:8080/services')
cy.get(':nth-child(5) > .text-right > .btn-group > .btn-danger').click()
cy.title().should('eq', 'Statping | Services')
cy.get('tr').should('have.length', 8)
})
});

View File

@ -1,48 +0,0 @@
/*
* Statping
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statping
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
context('Settings Forms', () => {
beforeEach(function() {
cy.visit('http://localhost:8080/dashboard')
cy.get('input[name="username"]').type('admin')
cy.get('input[name="password"]').type('admin')
cy.get('form').submit()
})
it('should edit main settings', () => {
cy.visit('http://localhost:8080/settings')
cy.get('input[name="project"]').clear().type('Project Updated')
cy.get('input[name="description"]').clear().type('This is an awesome page')
cy.get('input[name="domain"]').clear().type('http://0.0.0.0:8080')
cy.get('textarea[name="footer"]').clear().type('This is a custom footer')
cy.get('#v-pills-home > form').submit()
cy.title().should('eq', 'Statping | Settings')
cy.get('input[name="project"]').should('have.value', 'Project Updated')
cy.get('input[name="description"]').should('have.value', 'This is an awesome page')
cy.get('input[name="domain"]').should('have.value', 'http://0.0.0.0:8080')
cy.get('.footer').should('contain', 'This is a custom footer')
})
// it('should check index page for changes', () => {
// cy.visit('http://localhost:8080/')
// cy.title().should('eq', 'Project Updated Status')
// cy.get('.header-title').should('contain', 'Project Updated')
// cy.get('.header-desc').should('contain', 'This is an awesome page')
// })
});

View File

@ -1,69 +0,0 @@
/*
* Statping
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statping
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
context('User Testing', () => {
beforeEach(function () {
cy.visit('http://localhost:8080/dashboard')
cy.get('input[name="username"]').type('admin')
cy.get('input[name="password"]').type('admin')
cy.get('form').submit()
})
it('should view users', () => {
cy.visit('http://localhost:8080/users')
cy.get('tr').should('have.length', 2)
cy.title().should('eq', 'Statping | Users')
})
it('should create a new user', () => {
cy.visit('http://localhost:8080/users')
cy.get('input[name="username"]').type('hunterlong')
cy.get('input[name="email"]').type('info@yayaya.com')
cy.get('input[name="password"]').type('admin')
cy.get('input[name="password_confirm"]').type('admin')
cy.get('form').submit()
cy.get('tr').should('have.length', 3)
})
it('should create a edit user', () => {
cy.visit('http://localhost:8080/user/2')
cy.get('input[name="password"]').type('password567')
cy.get('input[name="password_confirm"]').type('password567')
cy.get('form').submit()
cy.get('tr').should('have.length', 3)
})
// it('should logout and login with new password', () => {
// cy.visit('http://localhost:8080/logout')
// cy.title().should('eq', 'Statping | Users')
// cy.get('#user_2 > .btn-group > .btn-danger').click()
// cy.get('tr').should('have.length', 2)
// cy.visit('http://localhost:8080/login')
// cy.get('input[name="username"]').type('hunterlong')
// cy.get('input[name="password"]').type('password567')
// cy.get('form').submit()
// cy.title().should('eq', 'Project Updated Status')
// })
it('should delete a user', () => {
cy.visit('http://localhost:8080/users')
cy.get('#user_2 > .btn-group > .btn-danger').click()
cy.get('tr').should('have.length', 2)
})
});

View File

@ -1,34 +0,0 @@
/*
* Statping
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statping
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View File

@ -1,42 +0,0 @@
/*
* Statping
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statping
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

View File

@ -1,37 +0,0 @@
/*
* Statping
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statping
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +0,0 @@
{
"name": "statping-testing",
"version": "1.0.0",
"description": "Statping Application Testing using Cypress!",
"main": "index.js",
"dependencies": {
"cypress": "^3.1.0"
},
"devDependencies": {
"start-server-and-test": "^1.7.0"
},
"scripts": {
"start": "curl -I -X GET http://localhost:8080/robots.txt",
"cy:run-video": "cypress run --record --key $CYPRESS_KEY",
"cy:run": "cypress run",
"test": "bash -c \"./test.sh\"",
"test-docker": "bash -c \"./test-docker.sh\"",
"testnovid": "cypress run",
"open": "cypress open"
},
"author": "Hunter Long",
"license": "ISC"
}

View File

@ -1,5 +0,0 @@
#!/usr/bin/env bash
RELEASE_MASTER='{ "request": { "branch": "master", "config": { "script": "make docker-run-test", "services": ["docker"], "before_script": [], "after_deploy": [], "after_success": ["make publish-homebrew", "make publish-latest"], "deploy": [], "before_deploy": [], "env": { "VERSION": "$(VERSION)" } } } }'
curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token $(TRAVIS_API)" -d $(RELEASE_MASTER) https://api.travis-ci.com/repo/hunterlong%2Fstatping/requests

View File

@ -1,5 +0,0 @@
#!/usr/bin/env bash
statping > /dev/null &
./node_modules/.bin/start-server-and-test start http://localhost:8080/robots.txt cy:run

View File

@ -1,13 +0,0 @@
#!/usr/bin/env bash
DIR=`pwd`
DOCKER=`which docker`
$DOCKER build -t hunterlong/statping:dev -f ../Dockerfile ../../
$DOCKER run -it -d -p 8080:8080 -v $DIR/app:/app --name statping_dev hunterlong/statping:dev
./node_modules/.bin/start-server-and-test start http://localhost:8080/robots.txt cy:run
$DOCKER stop statping_dev || true && $DOCKER rm -f statping_dev || true
sudo rm -rf $DIR/app

5
go.mod
View File

@ -8,6 +8,10 @@ require (
github.com/agnivade/levenshtein v1.0.2 // indirect
github.com/ararog/timeago v0.0.0-20160328174124-e9969cf18b8d
github.com/daaku/go.zipexe v1.0.1 // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v1.13.1
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.4.0 // indirect
github.com/fatih/structs v1.1.0
github.com/go-mail/mail v2.3.1+incompatible
github.com/go-yaml/yaml v2.1.0+incompatible
@ -18,6 +22,7 @@ require (
github.com/jinzhu/gorm v1.9.11
github.com/joho/godotenv v1.3.0
github.com/lib/pq v1.2.0 // indirect
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f
github.com/russross/blackfriday/v2 v2.0.1
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect

10
go.sum
View File

@ -34,6 +34,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA=
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
@ -119,6 +127,8 @@ github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYX
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=

View File

@ -43,7 +43,7 @@ var (
httpServer *http.Server
usingSSL bool
mainTmpl = `{{define "main" }} {{ template "base" . }} {{ end }}`
templates = []string{"base.gohtml", "head.gohtml", "nav.gohtml", "footer.gohtml", "scripts.gohtml", "form_service.gohtml", "form_notifier.gohtml", "form_group.gohtml", "form_user.gohtml", "form_checkin.gohtml", "form_message.gohtml"}
templates = []string{"base.gohtml", "head.gohtml", "nav.gohtml", "footer.gohtml", "scripts.gohtml", "form_service.gohtml", "form_notifier.gohtml", "form_integration.gohtml", "form_group.gohtml", "form_user.gohtml", "form_checkin.gohtml", "form_message.gohtml"}
javascripts = []string{"charts.js", "chart_index.js"}
mainTemplate *template.Template
)

33
handlers/integrations.go Normal file
View File

@ -0,0 +1,33 @@
package handlers
import (
"github.com/gorilla/mux"
"github.com/hunterlong/statping/integrations"
"net/http"
)
func apiAllIntegrationsHandler(w http.ResponseWriter, r *http.Request) {
integrations := integrations.Integrations
returnJson(integrations, w, r)
}
func apiIntegrationHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
intg := vars["name"]
r.ParseForm()
for k, v := range r.PostForm {
log.Info(k, v)
}
integration, err := integrations.Find(intg)
if err != nil {
sendErrorJson(err, w, r)
return
}
list, err := integration.List()
if err != nil {
sendErrorJson(err, w, r)
return
}
returnJson(list, w, r)
}

View File

@ -1,6 +1,7 @@
package handlers
import (
"crypto/subtle"
"fmt"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/utils"
@ -9,6 +10,28 @@ import (
"time"
)
var (
authUser string
authPass string
)
// basicAuthHandler is a middleware to implement HTTP basic authentication using
// AUTH_USERNAME and AUTH_PASSWORD environment variables
func basicAuthHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok || subtle.ConstantTimeCompare([]byte(user),
[]byte(authUser)) != 1 || subtle.ConstantTimeCompare([]byte(pass),
[]byte(authPass)) != 1 {
w.Header().Set("WWW-Authenticate", `Basic realm="statping"`)
w.WriteHeader(401)
w.Write([]byte("You are unauthorized to access the application.\n"))
return
}
next.ServeHTTP(w, r)
})
}
// sendLog is a http middleware that will log the duration of request and other useful fields
func sendLog(handler func(w http.ResponseWriter, r *http.Request)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

View File

@ -25,6 +25,7 @@ import (
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/utils"
"net/http"
"os"
)
var (
@ -38,6 +39,11 @@ func Router() *mux.Router {
dir := utils.Directory
CacheStorage = NewStorage()
r := mux.NewRouter()
if os.Getenv("AUTH_USERNAME") != "" && os.Getenv("AUTH_PASSWORD") != "" {
authUser = os.Getenv("AUTH_USERNAME")
authPass = os.Getenv("AUTH_PASSWORD")
r.Use(basicAuthHandler)
}
r.Handle("/", sendLog(indexHandler))
if source.UsingAssets(dir) {
indexHandler := http.FileServer(http.Dir(dir + "/assets/"))
@ -86,6 +92,7 @@ func Router() *mux.Router {
r.Handle("/settings/delete_assets", authenticated(deleteAssetsHandler, true)).Methods("GET")
r.Handle("/settings/export", authenticated(exportHandler, true)).Methods("GET")
r.Handle("/settings/bulk_import", authenticated(bulkImportHandler, true)).Methods("POST")
r.Handle("/settings/integrator/{name}", authenticated(integratorHandler, true)).Methods("POST")
// SERVICE Routes
r.Handle("/services", authenticated(servicesHandler, true)).Methods("GET")
@ -109,6 +116,10 @@ func Router() *mux.Router {
r.Handle("/api/renew", authenticated(apiRenewHandler, false))
r.Handle("/api/clear_cache", authenticated(apiClearCacheHandler, false))
r.Handle("/api/integrations", authenticated(apiAllIntegrationsHandler, false)).Methods("GET")
r.Handle("/api/integrations/{name}", authenticated(apiIntegrationHandler, false)).Methods("GET")
r.Handle("/api/integrations/{name}", authenticated(apiIntegrationHandler, false)).Methods("POST")
// API SERVICE Routes
r.Handle("/api/services", readOnly(apiAllServicesHandler, false)).Methods("GET")
r.Handle("/api/services", authenticated(apiCreateServiceHandler, false)).Methods("POST")

View File

@ -18,7 +18,9 @@ package handlers
import (
"bytes"
"fmt"
"github.com/gorilla/mux"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/integrations"
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
@ -144,6 +146,54 @@ func bulkImportHandler(w http.ResponseWriter, r *http.Request) {
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
}
type integratorOut struct {
Integrator *types.Integration `json:"integrator"`
Services []*types.Service `json:"services"`
Error error
}
func integratorHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
integratorName := vars["name"]
r.ParseForm()
integrator, err := integrations.Find(integratorName)
if err != nil {
log.Errorln(err)
ExecuteResponse(w, r, "integrator.gohtml", integratorOut{
Error: err,
}, nil)
return
}
log.Info(r.PostForm)
for _, v := range integrator.Get().Fields {
log.Info(v.Name, v.Value)
}
integrations.SetFields(integrator, r.PostForm)
for _, v := range integrator.Get().Fields {
log.Info(v.Name, v.Value)
}
services, err := integrator.List()
if err != nil {
log.Errorln(err)
ExecuteResponse(w, r, "integrator.gohtml", integratorOut{
Integrator: integrator.Get(),
Error: err,
}, nil)
return
}
ExecuteResponse(w, r, "integrator.gohtml", integratorOut{
Integrator: integrator.Get(),
Services: services,
}, nil)
}
// commaToService will convert a CSV comma delimited string slice to a Service type
// this function is used for the bulk import services feature
func commaToService(s []string) (*types.Service, error) {

130
integrations/csv_file.go Normal file
View File

@ -0,0 +1,130 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package integrations
import (
"errors"
"fmt"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"io/ioutil"
"strconv"
"strings"
"time"
)
const requiredSize = 17
type csvIntegration struct {
*types.Integration
}
var csvIntegrator = &csvIntegration{&types.Integration{
ShortName: "csv",
Name: "CSV File",
Description: "Import multiple services from a CSV file",
Fields: []*types.IntegrationField{
{
Name: "input",
Type: "file",
MimeType: "application/csv",
},
},
}}
var csvData [][]string
func (t *csvIntegration) Get() *types.Integration {
return t.Integration
}
func (t *csvIntegration) List() ([]*types.Service, error) {
path := csvIntegrator.Fields[0].Value.(string)
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
for _, line := range strings.Split(strings.TrimSuffix(string(data), "\n"), "\n") {
col := strings.Split(line, ",")
csvData = append(csvData, col)
}
var services []*types.Service
for _, v := range csvData {
s, err := commaToService(v)
if err != nil {
return nil, err
}
services = append(services, s)
}
return services, nil
}
// commaToService will convert a CSV comma delimited string slice to a Service type
// this function is used for the bulk import services feature
func commaToService(s []string) (*types.Service, error) {
if len(s) != requiredSize {
err := fmt.Errorf("file has %v columns of data, not the expected amount of %v columns for a service", len(s), requiredSize)
return nil, err
}
interval, err := time.ParseDuration(s[4])
if err != nil {
return nil, errors.New("could not parse internal duration: " + s[4])
}
timeout, err := time.ParseDuration(s[9])
if err != nil {
return nil, errors.New("could not parse timeout duration: " + s[9])
}
allowNotifications, err := strconv.ParseBool(s[11])
if err != nil {
return nil, errors.New("could not parse allow notifications boolean: " + s[11])
}
public, err := strconv.ParseBool(s[12])
if err != nil {
return nil, errors.New("could not parse public boolean: " + s[12])
}
verifySsl, err := strconv.ParseBool(s[16])
if err != nil {
return nil, errors.New("could not parse verifiy SSL boolean: " + s[16])
}
newService := &types.Service{
Name: s[0],
Domain: s[1],
Expected: types.NewNullString(s[2]),
ExpectedStatus: int(utils.ToInt(s[3])),
Interval: int(utils.ToInt(interval.Seconds())),
Type: s[5],
Method: s[6],
PostData: types.NewNullString(s[7]),
Port: int(utils.ToInt(s[8])),
Timeout: int(utils.ToInt(timeout.Seconds())),
AllowNotifications: types.NewNullBool(allowNotifications),
Public: types.NewNullBool(public),
GroupId: int(utils.ToInt(s[13])),
Headers: types.NewNullString(s[14]),
Permalink: types.NewNullString(s[15]),
VerifySSL: types.NewNullBool(verifySsl),
}
return newService, nil
}

View File

@ -0,0 +1,45 @@
package integrations
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
func TestCsvFileIntegration(t *testing.T) {
csvIntegrator.Fields[0].Value = "test_files/example_services.csv"
t.Run("CSV File", func(t *testing.T) {
path := csvIntegrator.Fields[0].Value
assert.Equal(t, "test_files/example_services.csv", path)
})
t.Run("CSV Open File", func(t *testing.T) {
err := csvIntegrator.Open()
require.Nil(t, err)
})
t.Run("List Services from CSV File", func(t *testing.T) {
services, err := csvIntegrator.List()
require.Nil(t, err)
assert.Equal(t, len(services), 1)
})
t.Run("Confirm Services from CSV File", func(t *testing.T) {
services, err := csvIntegrator.List()
require.Nil(t, err)
assert.Equal(t, "Bulk Upload", services[0].Name)
assert.Equal(t, "http://google.com", services[0].Domain)
assert.Equal(t, 60, services[0].Interval)
for _, s := range services {
t.Log(s)
}
})
t.Run("Close CSV", func(t *testing.T) {
err := csvIntegrator.Close()
require.Nil(t, err)
})
}

102
integrations/docker.go Normal file
View File

@ -0,0 +1,102 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package integrations
import (
"context"
dTypes "github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/hunterlong/statping/types"
"os"
)
type dockerIntegration struct {
*types.Integration
}
var dockerIntegrator = &dockerIntegration{&types.Integration{
ShortName: "docker",
Name: "Docker",
Description: `Import multiple services from Docker by attaching the unix socket to Statping.
You can also do this in Docker by setting <u>-v /var/run/docker.sock:/var/run/docker.sock</u> in the Statping Docker container.
All of the containers with open TCP/UDP ports will be listed for you to choose which services you want to add. If you running Statping inside of a container,
this container must be attached to all networks you want to communicate with.`,
Fields: []*types.IntegrationField{
{
Name: "path",
Description: "The absolute path to the Docker unix socket",
Type: "text",
Value: client.DefaultDockerHost,
},
{
Name: "version",
Description: "Version number of Docker server",
Type: "text",
Value: client.DefaultVersion,
},
},
}}
var cli *client.Client
func (t *dockerIntegration) Get() *types.Integration {
return t.Integration
}
func (t *dockerIntegration) List() ([]*types.Service, error) {
var err error
path := Value(t, "path").(string)
version := Value(t, "version").(string)
os.Setenv("DOCKER_HOST", path)
os.Setenv("DOCKER_VERSION", version)
cli, err = client.NewEnvClient()
if err != nil {
return nil, err
}
defer cli.Close()
var services []*types.Service
containers, err := cli.ContainerList(context.Background(), dTypes.ContainerListOptions{})
if err != nil {
return nil, err
}
for _, container := range containers {
if container.State != "running" {
continue
}
for _, v := range container.Ports {
if v.IP == "" {
continue
}
service := &types.Service{
Name: container.Names[0][1:],
Domain: v.IP,
Type: v.Type,
Port: int(v.PublicPort),
Interval: 60,
Timeout: 2,
}
services = append(services, service)
}
}
return services, nil
}

View File

@ -0,0 +1,35 @@
package integrations
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
func TestDockerIntegration(t *testing.T) {
t.Run("Docker Open containers", func(t *testing.T) {
err := dockerIntegrator.Open()
require.Nil(t, err)
})
t.Run("List Services from Docker", func(t *testing.T) {
services, err := dockerIntegrator.List()
require.Nil(t, err)
assert.NotEqual(t, 0, len(services))
})
t.Run("Confirm Services from Docker", func(t *testing.T) {
services, err := dockerIntegrator.List()
require.Nil(t, err)
for _, s := range services {
t.Log(s)
}
})
t.Run("Close Docker", func(t *testing.T) {
err := dockerIntegrator.Close()
require.Nil(t, err)
})
}

View File

@ -0,0 +1,63 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package integrations
import (
"errors"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
)
var (
Integrations []types.Integrator
log = utils.Log.WithField("type", "integration")
)
func init() {
Integrations = append(Integrations,
csvIntegrator,
dockerIntegrator,
)
}
func Value(intg types.Integrator, fieldName string) interface{} {
for _, v := range intg.Get().Fields {
if fieldName == v.Name {
return v.Value
}
}
return nil
}
func SetFields(intg types.Integrator, data map[string][]string) (*types.Integration, error) {
i := intg.Get()
for _, v := range i.Fields {
if data[v.Name] != nil {
v.Value = data[v.Name][0]
}
}
return i, nil
}
func Find(name string) (types.Integrator, error) {
for _, i := range Integrations {
obj := i.Get()
if obj.ShortName == name {
return i, nil
}
}
return nil, errors.New(name + " not found")
}

View File

@ -0,0 +1,21 @@
package integrations
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
func TestIntegrations(t *testing.T) {
t.Run("Collect Integrations", func(t *testing.T) {
amount := len(Integrations)
assert.Equal(t, 2, amount)
})
t.Run("Close All Integrations", func(t *testing.T) {
closedAll := CloseAll()
require.Nil(t, closedAll)
})
}

View File

@ -0,0 +1 @@
Bulk Upload,http://google.com,,200,60s,http,get,,,60s,1,TRUE,TRUE,,Authorization=example,bulk_example,false
1 Bulk Upload http://google.com 200 60s http get 60s 1 TRUE TRUE Authorization=example bulk_example false

View File

@ -7,54 +7,66 @@
/* Mobile Service Container */
HTML, BODY {
background-color: #fcfcfc;
padding-bottom: 10px; }
padding-bottom: 10px;
}
.container {
padding-top: 20px;
padding-bottom: 25px;
max-width: 860px;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; }
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
}
.header-title {
color: #464646; }
color: #464646;
}
.header-desc {
color: #939393; }
color: #939393;
}
.btn {
border-radius: 0.2rem; }
border-radius: 0.2rem;
}
.online_list .badge {
margin-top: 0.2rem; }
margin-top: 0.2rem;
}
.navbar {
margin-bottom: 30px; }
margin-bottom: 30px;
}
.btn-sm {
line-height: 1.3;
font-size: 0.75rem; }
font-size: 0.75rem;
}
.view_service_btn {
position: absolute;
bottom: -40px;
right: 40px; }
right: 40px;
}
.service_lower_info {
position: absolute;
bottom: -40px;
left: 40px;
color: #d1ffca;
font-size: 0.85rem; }
font-size: 0.85rem;
}
.lg_number {
font-size: 2.3rem;
font-weight: bold;
display: block;
color: #4f4f4f; }
color: #4f4f4f;
}
.stats_area {
text-align: center;
color: #a5a5a5; }
color: #a5a5a5;
}
.lower_canvas {
height: 3.4rem;
@ -62,82 +74,101 @@ HTML, BODY {
background-color: #48d338;
padding: 15px 10px;
margin-left: 0px !important;
margin-right: 0px !important; }
margin-right: 0px !important;
}
.lower_canvas SPAN {
font-size: 1rem;
color: #fff; }
color: #fff;
}
.footer {
text-decoration: none;
margin-top: 20px; }
margin-top: 20px;
}
.footer A {
color: #8d8d8d;
text-decoration: none; }
text-decoration: none;
}
.footer A:HOVER {
color: #6d6d6d; }
color: #6d6d6d;
}
.badge {
color: white;
border-radius: 0.2rem; }
border-radius: 0.2rem;
}
.btn-group {
height: 25px; }
height: 25px;
}
.btn-group A {
padding: 0.1rem .75rem;
font-size: 0.8rem; }
padding: 0.1rem 0.75rem;
font-size: 0.8rem;
}
.card-body .badge {
color: #fff; }
color: #fff;
}
.nav-pills .nav-link {
border-radius: 0.2rem; }
border-radius: 0.2rem;
}
.form-control {
border-radius: 0.2rem; }
border-radius: 0.2rem;
}
.card {
background-color: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.125); }
border: 1px solid rgba(0, 0, 0, 0.125);
}
.card-body {
overflow: hidden; }
overflow: hidden;
}
.card-body H4 A {
color: #444444;
text-decoration: none; }
text-decoration: none;
}
.chart-container {
position: relative;
height: 170px;
width: 100%;
overflow: hidden; }
overflow: hidden;
}
.service-chart-container {
position: relative;
height: 400px;
width: 100%; }
width: 100%;
}
.service-chart-heatmap {
position: relative;
height: 300px;
width: 100%; }
width: 100%;
}
.inputTags-field {
border: 0;
background-color: transparent;
padding-top: .13rem; }
padding-top: 0.13rem;
}
input.inputTags-field:focus {
outline-width: 0; }
outline-width: 0;
}
.inputTags-list {
display: block;
width: 100%;
min-height: calc(2.25rem + 2px);
padding: .2rem .35rem;
padding: 0.2rem 0.35rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
@ -145,8 +176,9 @@ input.inputTags-field:focus {
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out; }
border-radius: 0.25rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.inputTags-item {
background-color: #3aba39;
@ -154,63 +186,81 @@ input.inputTags-field:focus {
padding: 5px 8px;
font-size: 10pt;
color: white;
border-radius: 4px; }
border-radius: 4px;
}
.inputTags-item .close-item {
margin-left: 6px;
font-size: 13pt;
font-weight: bold;
cursor: pointer; }
cursor: pointer;
}
.btn-primary {
background-color: #3e9bff;
border-color: #006fe6;
color: white; }
color: white;
}
.btn-primary.dyn-dark {
background-color: #32a825 !important;
border-color: #2c9320 !important; }
border-color: #2c9320 !important;
}
.btn-primary.dyn-light {
background-color: #75de69 !important;
border-color: #88e37e !important; }
border-color: #88e37e !important;
}
.btn-success {
background-color: #47d337; }
background-color: #47d337;
}
.btn-success.dyn-dark {
background-color: #32a825 !important;
border-color: #2c9320 !important; }
border-color: #2c9320 !important;
}
.btn-success.dyn-light {
background-color: #75de69 !important;
border-color: #88e37e !important; }
border-color: #88e37e !important;
}
.btn-danger {
background-color: #dd3545; }
background-color: #dd3545;
}
.btn-danger.dyn-dark {
background-color: #b61f2d !important;
border-color: #a01b28 !important; }
border-color: #a01b28 !important;
}
.btn-danger.dyn-light {
background-color: #e66975 !important;
border-color: #e97f89 !important; }
border-color: #e97f89 !important;
}
.bg-success {
background-color: #47d337 !important; }
background-color: #47d337 !important;
}
.bg-danger {
background-color: #dd3545 !important; }
background-color: #dd3545 !important;
}
.bg-success .dyn-dark {
background-color: #35b027 !important; }
background-color: #35b027 !important;
}
.bg-danger .dyn-dark {
background-color: #bf202f !important; }
background-color: #bf202f !important;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
background-color: #13a00d; }
background-color: #13a00d;
}
.nav-pills A {
color: #424242; }
color: #424242;
}
.nav-pills I {
margin-right: 10px; }
margin-right: 10px;
}
.CodeMirror {
/* Bootstrap Settings */
@ -230,23 +280,26 @@ input.inputTags-field:focus {
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
/* Code Mirror Settings */
font-family: monospace;
position: relative;
overflow: hidden;
height: 80vh; }
height: 80vh;
}
.CodeMirror-focused {
/* Bootstrap Settings */
border-color: #66afe9;
outline: 0;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; }
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
}
.switch {
font-size: 1rem;
position: relative; }
position: relative;
}
.switch input {
position: absolute;
@ -257,7 +310,8 @@ input.inputTags-field:focus {
clip: rect(0 0 0 0);
clip-path: inset(50%);
overflow: hidden;
padding: 0; }
padding: 0;
}
.switch input + label {
position: relative;
@ -270,23 +324,26 @@ input.inputTags-field:focus {
outline: none;
user-select: none;
vertical-align: middle;
text-indent: calc(calc(calc(2.375rem * .8) * 2) + .5rem); }
text-indent: calc(calc(calc(2.375rem * .8) * 2) + .5rem);
}
.switch input + label::before,
.switch input + label::after {
content: '';
content: "";
position: absolute;
top: 0;
left: 0;
width: calc(calc(2.375rem * .8) * 2);
bottom: 0;
display: block; }
display: block;
}
.switch input + label::before {
right: 0;
background-color: #dee2e6;
border-radius: calc(2.375rem * .8);
transition: 0.2s all; }
transition: 0.2s all;
}
.switch input + label::after {
top: 2px;
@ -295,105 +352,137 @@ input.inputTags-field:focus {
height: calc(calc(2.375rem * .8) - calc(2px * 2));
border-radius: 50%;
background-color: white;
transition: 0.2s all; }
transition: 0.2s all;
}
.switch input:checked + label::before {
background-color: #08d; }
background-color: #08d;
}
.switch input:checked + label::after {
margin-left: calc(2.375rem * .8); }
margin-left: calc(2.375rem * .8);
}
.switch input:focus + label::before {
outline: none;
box-shadow: 0 0 0 0.2rem rgba(0, 136, 221, 0.25); }
box-shadow: 0 0 0 0.2rem rgba(0, 136, 221, 0.25);
}
.switch input:disabled + label {
color: #868e96;
cursor: not-allowed; }
cursor: not-allowed;
}
.switch input:disabled + label::before {
background-color: #e9ecef; }
background-color: #e9ecef;
}
.switch.switch-sm {
font-size: 0.875rem; }
font-size: 0.875rem;
}
.switch.switch-sm input + label {
min-width: calc(calc(1.9375rem * .8) * 2);
height: calc(1.9375rem * .8);
line-height: calc(1.9375rem * .8);
text-indent: calc(calc(calc(1.9375rem * .8) * 2) + .5rem); }
text-indent: calc(calc(calc(1.9375rem * .8) * 2) + .5rem);
}
.switch.switch-sm input + label::before {
width: calc(calc(1.9375rem * .8) * 2); }
width: calc(calc(1.9375rem * .8) * 2);
}
.switch.switch-sm input + label::after {
width: calc(calc(1.9375rem * .8) - calc(2px * 2));
height: calc(calc(1.9375rem * .8) - calc(2px * 2)); }
height: calc(calc(1.9375rem * .8) - calc(2px * 2));
}
.switch.switch-sm input:checked + label::after {
margin-left: calc(1.9375rem * .8); }
margin-left: calc(1.9375rem * .8);
}
.switch.switch-lg {
font-size: 1.25rem; }
font-size: 1.25rem;
}
.switch.switch-lg input + label {
min-width: calc(calc(3rem * .8) * 2);
height: calc(3rem * .8);
line-height: calc(3rem * .8);
text-indent: calc(calc(calc(3rem * .8) * 2) + .5rem); }
text-indent: calc(calc(calc(3rem * .8) * 2) + .5rem);
}
.switch.switch-lg input + label::before {
width: calc(calc(3rem * .8) * 2); }
width: calc(calc(3rem * .8) * 2);
}
.switch.switch-lg input + label::after {
width: calc(calc(3rem * .8) - calc(2px * 2));
height: calc(calc(3rem * .8) - calc(2px * 2)); }
height: calc(calc(3rem * .8) - calc(2px * 2));
}
.switch.switch-lg input:checked + label::after {
margin-left: calc(3rem * .8); }
margin-left: calc(3rem * .8);
}
.switch + .switch {
margin-left: 1rem; }
margin-left: 1rem;
}
@keyframes pulse_animation {
0% {
transform: scale(1); }
transform: scale(1);
}
30% {
transform: scale(1); }
transform: scale(1);
}
40% {
transform: scale(1.02); }
transform: scale(1.02);
}
50% {
transform: scale(1); }
transform: scale(1);
}
60% {
transform: scale(1); }
transform: scale(1);
}
70% {
transform: scale(1.05); }
transform: scale(1.05);
}
80% {
transform: scale(1); }
transform: scale(1);
}
100% {
transform: scale(1); } }
transform: scale(1);
}
}
.pulse {
animation-name: pulse_animation;
animation-duration: 1500ms;
transform-origin: 70% 70%;
animation-iteration-count: infinite;
animation-timing-function: linear; }
animation-timing-function: linear;
}
@keyframes glow-grow {
0% {
opacity: 0;
transform: scale(1); }
transform: scale(1);
}
80% {
opacity: 1; }
opacity: 1;
}
100% {
transform: scale(2);
opacity: 0; } }
opacity: 0;
}
}
.pulse-glow {
animation-name: glow-grown;
animation-duration: 100ms;
transform-origin: 70% 30%;
animation-iteration-count: infinite;
animation-timing-function: linear; }
animation-timing-function: linear;
}
.pulse-glow:before,
.pulse-glow:after {
@ -405,10 +494,12 @@ input.inputTags-field:focus {
right: 2.15rem;
border-radius: 0;
box-shadow: 0 0 6px #47d337;
animation: glow-grow 2s ease-out infinite; }
animation: glow-grow 2s ease-out infinite;
}
.sortable_drag {
background-color: #0000000f; }
background-color: #0000000f;
}
.drag_icon {
cursor: move;
@ -422,112 +513,139 @@ input.inputTags-field:focus {
margin-right: 5px;
margin-left: -10px;
text-align: center;
color: #b1b1b1; }
color: #b1b1b1;
}
/* (Optional) Apply a "closed-hand" cursor during drag operation. */
.drag_icon:active {
cursor: grabbing;
cursor: -moz-grabbing;
cursor: -webkit-grabbing; }
cursor: -webkit-grabbing;
}
.switch_btn {
float: right;
margin: -1px 0px 0px 0px;
display: block; }
display: block;
}
#start_container {
position: absolute;
z-index: 99999;
margin-top: 20px; }
margin-top: 20px;
}
#end_container {
position: absolute;
z-index: 99999;
margin-top: 20px;
right: 0; }
right: 0;
}
.pointer {
cursor: pointer; }
cursor: pointer;
}
.jumbotron {
background-color: white; }
background-color: white;
}
.toggle-service {
font-size: 18pt;
float: left;
margin: 2px 3px 0 0;
cursor: pointer; }
cursor: pointer;
}
@media (max-width: 767px) {
HTML, BODY {
background-color: #fcfcfc; }
background-color: #fcfcfc;
}
.sm-container {
margin-top: 0px !important;
padding: 0 !important; }
padding: 0 !important;
}
.list-group-item H5 {
font-size: 0.9rem; }
font-size: 0.9rem;
}
.container {
padding: 0px !important;
padding-top: 15px !important; }
padding-top: 15px !important;
}
.group_header {
margin-left: 15px; }
margin-left: 15px;
}
.navbar {
margin-left: 0px;
margin-top: 0px;
width: 100%;
margin-bottom: 0; }
margin-bottom: 0;
}
.btn-sm {
line-height: 0.9rem;
font-size: 0.65rem; }
font-size: 0.65rem;
}
.full-col-12 {
padding-left: 0px;
padding-right: 0px; }
padding-right: 0px;
}
.card {
border: 0;
border-radius: 0rem;
padding: 0;
background-color: #ffffff; }
background-color: #ffffff;
}
.card-body {
font-size: 10pt;
padding: 10px 10px; }
padding: 10px 10px;
}
.lg_number {
font-size: 7.8vw; }
font-size: 7.8vw;
}
.stats_area {
margin-top: 1.5rem !important;
margin-bottom: 1.5rem !important; }
margin-bottom: 1.5rem !important;
}
.stats_area .col-4 {
padding-left: 0;
padding-right: 0;
font-size: 0.6rem; }
font-size: 0.6rem;
}
.list-group-item {
border-top: 1px solid #e4e4e4;
border: 0px; }
border: 0px;
}
.list-group-item:first-child {
border-top-left-radius: 0;
border-top-right-radius: 0; }
border-top-right-radius: 0;
}
.list-group-item:last-child {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0; }
border-bottom-left-radius: 0;
}
.list-group-item P {
font-size: 0.7rem; }
font-size: 0.7rem;
}
.service-chart-container {
height: 200px; } }
height: 200px;
}
}
/*# sourceMappingURL=base.css.map */

View File

@ -2,11 +2,7 @@
<div class="card">
<div class="card-body">
{{$message := .}}
{{if ne .Id 0}}
<form class="ajax_form" action="/api/groups/{{.Id}}" data-redirect="/services" method="POST">
{{else}}
<form class="ajax_form" action="/api/groups" data-redirect="/services" method="POST">
{{end}}
<form class="ajax_form" action="/api/groups{{if ne .Id 0}}/{{.Id}}{{end}}" data-redirect="/services" method="POST">
<div class="form-group row">
<label for="username" class="col-sm-4 col-form-label">Group Name</label>
<div class="col-sm-8">

View File

@ -2,11 +2,7 @@
<div class="card">
<div class="card-body">
{{$message := .}}
{{if ne .Id 0}}
<form class="ajax_form" action="/api/messages/{{.Id}}" data-redirect="/messages" method="POST">
{{else}}
<form class="ajax_form" action="/api/messages" data-redirect="/messages" method="POST">
{{end}}
<form class="ajax_form" action="/api/messages{{if ne .Id 0}}/{{.Id}}{{end}}" data-redirect="/messages" method="POST">
<div class="form-group row">
<label for="username" class="col-sm-4 col-form-label">Title</label>
<div class="col-sm-8">

View File

@ -0,0 +1,30 @@
{{define "form_integration"}}
{{$i := .Get}}
<form class="integration_{{underscore $i.ShortName }}" action="/settings/integrator/{{ $i.ShortName }}" method="POST">
<input type="hidden" name="integrator" class="form-control" value="{{ $i.ShortName }}">
{{if $i.ShortName}}<h4 class="text-capitalize">{{$i.ShortName}}</h4>{{end}}
{{if $i.Description}}<p class="small text-muted">{{safe $i.Description}}</p>{{end}}
{{range $i.Fields}}
<div class="form-group">
<label class="text-capitalize" for="{{underscore .Name}}">{{.Name}}</label>
{{if eq .Type "textarea"}}
<textarea rows="3" class="form-control" name="{{underscore .Name}}" id="{{underscore .Name}}">{{ .Value }}</textarea>
{{else if eq .Type "text"}}
<input type="text" name="{{underscore .Name}}" class="form-control" value="{{ .Value }}" id="{{underscore .Name}}">
{{else if eq .Type "integer"}}
<input type="number" name="{{underscore .Name}}" class="form-control" value="{{ .Value }}" id="{{underscore .Name}}">
{{else if eq .Type "file"}}
<input type="file" name="{{underscore .Name}}" class="form-control" value="{{ .Value }}" id="{{underscore .Name}}">
{{end}}
{{if .Description}}
<small class="form-text text-muted">{{safe .Description}}</small>
{{end}}
</div>
{{end}}
<button type="submit" class="btn btn-block btn-info fetch_integrator">Fetch Services</button>
<div class="alert alert-danger d-none" id="integration_alerter" role="alert"></div>
</form>
{{end}}

View File

@ -2,11 +2,7 @@
<div class="card">
<div class="card-body">
{{$message := .}}
{{if ne .Id 0}}
<form class="ajax_form" action="/api/messages/{{.Id}}" data-redirect="/messages" method="POST">
{{else}}
<form class="ajax_form" action="/api/messages" data-redirect="/messages" method="POST">
{{end}}
<form class="ajax_form" action="/api/messages{{if ne .Id 0}}/{{.Id}}{{end}}" data-redirect="/messages" method="POST">
<div class="form-group row">
<label for="username" class="col-sm-4 col-form-label">Title</label>
<div class="col-sm-8">

View File

@ -2,11 +2,7 @@
<div class="card">
<div class="card-body">
{{$s := .}}
{{if ne .Id 0}}
<form class="ajax_form" action="/api/services/{{.Id}}" data-redirect="/services" method="POST">
{{else}}
<form class="ajax_form" action="/api/services" data-redirect="/services" method="POST">
{{end}}
<form class="ajax_form" action="/api/services{{if ne .Id 0}}/{{.Id}}{{end}}" 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>

View File

@ -1,11 +1,7 @@
{{define "form_user"}}
<div class="card">
<div class="card-body">
{{if ne .Id 0}}
<form class="ajax_form" action="/api/users/{{.Id}}" data-redirect="/users" method="POST">
{{else}}
<form class="ajax_form" action="/api/users" data-redirect="/users" method="POST">
{{end}}
<form class="ajax_form" action="/api/users{{if ne .Id 0}}/{{.Id}}{{end}}" data-redirect="/users" method="POST">
<div class="form-group row">
<label for="username" class="col-sm-4 col-form-label">Username</label>
<div class="col-6 col-md-4">

View File

@ -0,0 +1,90 @@
{{define "title"}}Statping | {{.Integrator.Name}} Integration{{end}}
{{define "content"}}
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
{{if Auth}}
{{template "nav"}}
{{end}}
{{$i := .Integrator}}
<div class="col-12">
<h3 class="mb-2 text-muted">{{$i.Name}} Integration</h3>
<p>{{safe $i.Description}}</p>
{{if .Error}}
<div class="alert alert-danger" role="alert">
{{.Error}}
</div>
{{else}}
<table id="integrator_table" class="table table-striped">
<thead>
<tr>
<th scope="col"><input name="all" type="checkbox" checked></th></th>
<th scope="col">Service Name</th>
<th scope="col">Endpoint</th>
<th scope="col">Port</th>
<th scope="col">Type</th>
<th scope="col">Interval</th>
<th scope="col">Timeout</th>
</tr>
</thead>
<tbody id="integrator_services">
{{range .Services}}
<tr id="{{underscore .Name}}">
<th scope="row"><input name="add" type="checkbox" checked></th>
<th><div class="input-group-sm"><input type="text" class="form-control" name="name" value="{{.Name}}"></div></th>
<th><div style="width: 80pt;" class="input-group-sm"><input type="text" class="form-control" name="domain" value="{{.Domain}}"></div></th>
<th><div style="width: 55pt;" class="input-group-sm"><input type="number" class="form-control" name="port" value="{{.Port}}"></div></th>
<th><div style="width: 32pt;" class="input-group-sm"><input type="text" class="form-control" name="type" value="{{.Type}}"></div></th>
<th><div style="width: 40pt;" class="input-group-sm"><input type="text" class="form-control" name="check_interval" value="{{.Interval}}"></div></th>
<th><div style="width: 40pt;" class="input-group-sm"><input type="text" class="form-control" name="timeout" value="{{.Timeout}}"></div></th>
</tr>
{{end}}
</tbody>
</table>
<div id="imported_area"></div>
<button class="btn btn-block btn-primary add_integration_services mb-5" data-id="{{.Integrator.ShortName}}">Add Services</button>
{{end}}
</div>
</div>
{{end}}
{{define "extra_scripts"}}
<script>
$('.add_integration_services').on('click', function(e) {
var table = $(`#integrator_services`);
table.find('tr').each(function() {
var t = $(this).find('input');
var eachService = t.serializeArray();
let newArr = {};
var add = false;
eachService.forEach(function(k, v) {
if (k.value === "on" && k.name === "add") {
add = true
}
if($.isNumeric(k.value)){
k.value = parseInt(k.value)
}
if (add && k.name !== "add") {
newArr[k.name] = k.value;
}
});
let sendData = JSON.stringify(newArr);
$.ajax({
url: "/api/services",
type: "POST",
data: sendData,
success: function (data) {
var box = `<div class="alert alert-success" role="alert">Service '${data.output.name}' Added <a href="/service/${data.output.id}" class="badge badge-secondary mt-1 float-right">View</a></div>`;
$("#imported_area").append(box);
}
});
});
});
</script>
{{end}}

View File

@ -21,6 +21,12 @@
{{ range .Plugins }}
<a class="nav-link text-capitalize" id="v-pills-{{underscore .Name}}-tab" data-toggle="pill" href="#v-pills-{{underscore .Name}}" role="tab" aria-controls="v-pills-profile" aria-selected="false">{{.Name}}</a>
{{end}}
<h6 class="mt-4 text-muted">Integrations</h6>
{{ range .Integrations }}
{{$i := .Get}}
<a class="nav-link text-capitalize" id="v-pills-integration-{{underscore $i.ShortName}}-tab" data-toggle="pill" href="#v-pills-integration-{{underscore $i.ShortName}}" role="tab" aria-controls="v-pills-integration-{{underscore $i.ShortName}}" aria-selected="false">{{$i.Name}}</a>
{{end}}
</div>
</div>
<div class="col-md-9 col-sm-12">
@ -261,6 +267,15 @@
</div>
{{ end }}
{{ range .Integrations }}
{{$i := .Get}}
<div class="tab-pane fade" id="v-pills-integration-{{underscore $i.ShortName}}" role="tabpanel" aria-labelledby="v-pills-integration-{{underscore $i.ShortName}}-tab">
{{template "form_integration" .}}
</div>
{{ end }}
<div class="tab-pane fade" id="v-pills-browse" role="tabpanel" aria-labelledby="v-pills-browse-tab">
{{ range .Repos }}
<div class="card col-6" style="width: 18rem;">

File diff suppressed because one or more lines are too long

View File

@ -51,4 +51,5 @@ type Core struct {
AllPlugins []PluginActions `gorm:"-" json:"-"`
Notifications []AllNotifiers `gorm:"-" json:"-"`
Config *DbConfig `gorm:"-" json:"config"`
Integrations []Integrator `gorm:"-" json:"-"`
}

22
types/integrations.go Normal file
View File

@ -0,0 +1,22 @@
package types
type Integration struct {
ShortName string `json:"name"`
Name string `json:"full_name"`
Description string `json:"description"`
Enabled bool `json:"enabled"`
Fields []*IntegrationField `json:"fields"`
}
type IntegrationField struct {
Name string `json:"name"`
Value interface{} `json:"value"`
Type string `json:"type"`
Description string `json:"description,omitempty"`
MimeType string `json:"mime_type,omitempty"`
}
type Integrator interface {
Get() *Integration
List() ([]*Service, error)
}