Initial commit
This commit is contained in:
commit
0420b3efbd
56 files changed
+94426
No files matched your search
@@ -0,0 +1,3 @@
|
|||||||
|
wo-bisch-web
|
||||||
|
nohup.out
|
||||||
|
check_nodes/check_nodes
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# wo-bisch-web - LoraWAN GPS Tracker
|
||||||
|
|
||||||
|
Webapplikation, geschrieben in Golang.
|
||||||
|
|
||||||
|
## Administration
|
||||||
|
|
||||||
|
### Redis Commands
|
||||||
|
|
||||||
|
```
|
||||||
|
$ redis-cli
|
||||||
|
|
||||||
|
Show all keys:
|
||||||
|
127.0.0.1:6379> keys *
|
||||||
|
|
||||||
|
Create new user (devs prefixed with @ are read-only devs):
|
||||||
|
127.0.0.1:6379> HMSET user:joerg.lehmann@nbit.ch my_devs "0002CC01000003E4,@0002CC01000003E5"
|
||||||
|
|
||||||
|
Show all values of a key
|
||||||
|
127.0.0.1:6379> HGETALL user:joerg.lehmann@nbit.ch
|
||||||
|
1) "last_login"
|
||||||
|
2) "2020-04-11 13:41:57"
|
||||||
|
3) "confirm_id"
|
||||||
|
4) ""
|
||||||
|
5) "password"
|
||||||
|
6) "$2a$10$XdDSG2E9SpVuxLE59JWwsO9aJtbrwArSflBGwGVvjWDEQecXEUo06"
|
||||||
|
7) "my_devs"
|
||||||
|
8) "0002CC01000003E4,@0002CC01000003F5,0002CC01000003F1,0002CC01000003F7,0002CC01000003F3,0002CC01000003E1,0002CC01000003D4,0002CC01000003EC,0002CC01000003E2"
|
||||||
|
9) "new_password"
|
||||||
|
10) "$2a$10$XdDSG2E9SpVuxLE59JWwsO9aJtbrwArSflBGwGVvjWDEQecXEUo06"
|
||||||
|
|
||||||
|
Set password (htpasswd in httpd-tools rpm):
|
||||||
|
$ htpasswd -nbBC 5 USER PASSWORD
|
||||||
|
|
||||||
|
|
||||||
|
27.0.0.1:6379> HMSET user:joerg.lehmann@nbit.ch password '$2a$10$TmNA6PDKWBnMw/XcJ0DDi.zpWzZB0RYyrKc8Bh3x6LHAyCxbByhkC'
|
||||||
|
|
||||||
|
Change active_until date:
|
||||||
|
127.0.0.1:6379> HMSET dev:0002CC01000003E2 active_until "01.05.2021"
|
||||||
|
|
||||||
|
show range of last values:
|
||||||
|
127.0.0.1:6379> LRANGE lastvalues:0002CC01000003E4 0 -1
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Autor: Joerg Lehmann, nbit Informatik GmbH
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/securecookie"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// cookie handling
|
||||||
|
|
||||||
|
var cookieHandler = securecookie.New(
|
||||||
|
securecookie.GenerateRandomKey(64),
|
||||||
|
securecookie.GenerateRandomKey(32))
|
||||||
|
|
||||||
|
func getUserName(request *http.Request) (userName string) {
|
||||||
|
if cookie, err := request.Cookie("session"); err == nil {
|
||||||
|
cookieValue := make(map[string]string)
|
||||||
|
if err = cookieHandler.Decode("session", cookie.Value, &cookieValue); err == nil {
|
||||||
|
userName = cookieValue["name"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return userName
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserNameHash(request *http.Request) (userName string) {
|
||||||
|
if cookie, err := request.Cookie("session"); err == nil {
|
||||||
|
cookieValue := make(map[string]string)
|
||||||
|
if err = cookieHandler.Decode("session", cookie.Value, &cookieValue); err == nil {
|
||||||
|
userName = cookieValue["name"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasher := md5.New()
|
||||||
|
hasher.Write([]byte(userName))
|
||||||
|
return hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSession(userName string, response http.ResponseWriter) {
|
||||||
|
value := map[string]string{
|
||||||
|
"name": userName,
|
||||||
|
}
|
||||||
|
if encoded, err := cookieHandler.Encode("session", value); err == nil {
|
||||||
|
cookie := &http.Cookie{
|
||||||
|
Name: "session",
|
||||||
|
Value: encoded,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
http.SetCookie(response, cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearSession(response http.ResponseWriter) {
|
||||||
|
cookie := &http.Cookie{
|
||||||
|
Name: "session",
|
||||||
|
Value: "",
|
||||||
|
Path: "/",
|
||||||
|
MaxAge: -1,
|
||||||
|
}
|
||||||
|
http.SetCookie(response, cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
// login handler
|
||||||
|
|
||||||
|
func loginHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
name := request.FormValue("email")
|
||||||
|
pass := request.FormValue("password")
|
||||||
|
redirectTarget := "/invalid_login.html"
|
||||||
|
// .. check credentials ..
|
||||||
|
if checkLoginCredentials(name, pass) {
|
||||||
|
redirectTarget = "/tracker.html"
|
||||||
|
logit(fmt.Sprintf("loginHandler: successful login for User %s", name))
|
||||||
|
setSession(name, response)
|
||||||
|
updateLoginTime(name)
|
||||||
|
} else {
|
||||||
|
logit(fmt.Sprintf("loginHandler: invalid login for User %s", name))
|
||||||
|
}
|
||||||
|
http.Redirect(response, request, redirectTarget, 302)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resetPassword handler
|
||||||
|
|
||||||
|
func resetPasswordHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
name := request.FormValue("email")
|
||||||
|
pass := request.FormValue("password")
|
||||||
|
redirectTarget := "/wait_for_password_confirmation.html"
|
||||||
|
logit(fmt.Sprintf("resetPasswordHandler: request for User %s", name))
|
||||||
|
if name != "" && pass != "" {
|
||||||
|
if checkUserAvailable(name) {
|
||||||
|
http.Redirect(response, request, "/user_does_not_exist.html", 302)
|
||||||
|
} else {
|
||||||
|
updateUser(name, pass)
|
||||||
|
http.Redirect(response, request, redirectTarget, 302)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.Redirect(response, request, "/error_reset_password.html", 302)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setPassword handler
|
||||||
|
|
||||||
|
func setPasswordHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
name := getUserName(request)
|
||||||
|
pass := request.FormValue("password")
|
||||||
|
if name != "" && pass != "" {
|
||||||
|
if checkUserAvailable(name) {
|
||||||
|
http.Redirect(response, request, "/user_does_not_exist.html", 302)
|
||||||
|
} else {
|
||||||
|
updateUser(name, pass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// logout handler
|
||||||
|
|
||||||
|
func logoutHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
clearSession(response)
|
||||||
|
http.Redirect(response, request, "/", 302)
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm handler
|
||||||
|
|
||||||
|
func confirmHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
confirm_id := request.URL.Query().Get("id")
|
||||||
|
logit(fmt.Sprintf("Confirm ID: %s\n", confirm_id))
|
||||||
|
if confirmUser(confirm_id) {
|
||||||
|
http.Redirect(response, request, "/password_changed.html", 302)
|
||||||
|
} else {
|
||||||
|
http.Redirect(response, request, "/", 302)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,395 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gomodule/redigo/redis"
|
||||||
|
"github.com/jordan-wright/email"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sendEmailAccu(username string, alias string, deveui string, accu_percent string, threshold int, level string) {
|
||||||
|
fmt.Printf("SEND EMAIL ACCU (%s) - %s:%s\n", level, username, deveui)
|
||||||
|
mail_message := `Lieber Benutzer von wo-bisch.ch
|
||||||
|
|
||||||
|
Der Akku von "` + alias + `" (DevEUI: ` + deveui + `) ist noch zu ` + accu_percent + ` Prozent geladen.
|
||||||
|
|
||||||
|
Bitte rechtzeitig wieder laden! Bei 10%-er Ladung erscheint die letzte Warnung.
|
||||||
|
|
||||||
|
Mit freundlichen Grüssen
|
||||||
|
--
|
||||||
|
wo-bisch.ch`
|
||||||
|
|
||||||
|
e := email.NewEmail()
|
||||||
|
e.From = "wo-bisch.ch <info@wo-bisch.ch>"
|
||||||
|
e.To = []string{username}
|
||||||
|
e.Bcc = []string{"joerg.lehmann@nbit.ch"}
|
||||||
|
e.Subject = level + " - wo-bisch.ch: Akku Ladezustand (" + alias + ")"
|
||||||
|
e.Text = []byte(mail_message)
|
||||||
|
e.Send("127.0.0.1:25", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendEmailAbo(username string, alias string, deveui string, days_left int, level string) {
|
||||||
|
fmt.Printf("SEND EMAIL ABO (%s) - %s:%s\n", level, username, deveui)
|
||||||
|
mail_message := `Lieber Benutzer von wo-bisch.ch
|
||||||
|
|
||||||
|
Das Abo von "` + alias + `" (DevEUI: ` + deveui + `) laeuft in ` + strconv.Itoa(days_left) + ` Tagen ab.
|
||||||
|
|
||||||
|
Bitte Abo verlaengern auf https://wo-bisch.ch
|
||||||
|
|
||||||
|
Mit freundlichen Grüssen
|
||||||
|
--
|
||||||
|
wo-bisch.ch`
|
||||||
|
|
||||||
|
e := email.NewEmail()
|
||||||
|
e.From = "wo-bisch.ch <info@wo-bisch.ch>"
|
||||||
|
e.To = []string{username}
|
||||||
|
e.Bcc = []string{"joerg.lehmann@nbit.ch"}
|
||||||
|
e.Subject = level + " - wo-bisch.ch: Abo laeuft ab (" + alias + ")"
|
||||||
|
e.Text = []byte(mail_message)
|
||||||
|
e.Send("127.0.0.1:25", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var globalPool *redis.Pool
|
||||||
|
|
||||||
|
const userPrefix string = "user:"
|
||||||
|
const devPrefix string = "dev:"
|
||||||
|
const confirmPrefix string = "confirm:"
|
||||||
|
|
||||||
|
func newPool() *redis.Pool {
|
||||||
|
return &redis.Pool{
|
||||||
|
// Maximum number of idle connections in the pool.
|
||||||
|
MaxIdle: 80,
|
||||||
|
// max number of connections
|
||||||
|
MaxActive: 12000,
|
||||||
|
// Dial is an application supplied function for creating and
|
||||||
|
// configuring a connection.
|
||||||
|
Dial: func() (redis.Conn, error) {
|
||||||
|
c, err := redis.Dial("tcp", ":6379")
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
return c, err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ping tests connectivity for redis (PONG should be returned)
|
||||||
|
func ping(c redis.Conn) error {
|
||||||
|
// Send PING command to Redis
|
||||||
|
// PING command returns a Redis "Simple String"
|
||||||
|
// Use redis.String to convert the interface type to string
|
||||||
|
s, err := redis.String(c.Do("PING"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logit("PING Response = " + s)
|
||||||
|
// Output: PONG
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dev struct {
|
||||||
|
Deveui string
|
||||||
|
Alias string
|
||||||
|
Alarmactive string
|
||||||
|
Smsnumber string
|
||||||
|
ActiveUntil string // Abo bezahlt bis TT.MM.YYYY
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDB() {
|
||||||
|
|
||||||
|
// newPool returns a pointer to a redis.Pool
|
||||||
|
pool := newPool()
|
||||||
|
// get a connection from the globalPool (redis.Conn)
|
||||||
|
conn := pool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
globalPool = pool
|
||||||
|
|
||||||
|
// wir machen einen Connection Test
|
||||||
|
ping(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeDB() {
|
||||||
|
globalPool.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUsers() []string {
|
||||||
|
res := []string{}
|
||||||
|
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
//logit("getUsers")
|
||||||
|
users, err := redis.Strings(conn.Do("KEYS", userPrefix+"*"))
|
||||||
|
if err == nil {
|
||||||
|
//logit("getUsers successful!")
|
||||||
|
res = users
|
||||||
|
} else {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMyDevs(username string) []string {
|
||||||
|
res := []string{}
|
||||||
|
|
||||||
|
if username == "" {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
//logit("getMyDevs: User: " + username)
|
||||||
|
mydevs, err := redis.String(conn.Do("HGET", userPrefix+username, "my_devs"))
|
||||||
|
if err == nil {
|
||||||
|
//logit("getMyDevs: mydevs: " + mydevs)
|
||||||
|
res = strings.Split(mydevs, ",")
|
||||||
|
} else {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDevAlias(deveui string) string {
|
||||||
|
res := deveui
|
||||||
|
|
||||||
|
if deveui == "" {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
//logit("getDevAlias: Deveui: " + deveui)
|
||||||
|
alias, err := redis.String(conn.Do("HGET", devPrefix+deveui, "alias"))
|
||||||
|
if err == nil {
|
||||||
|
//logit("getDevAlias: alias: " + alias)
|
||||||
|
res = alias
|
||||||
|
} else {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func getActiveUntil(deveui string) string {
|
||||||
|
res := ""
|
||||||
|
|
||||||
|
if deveui == "" {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
//logit("getActiveUntil: Deveui: " + deveui)
|
||||||
|
activeuntil, err := redis.String(conn.Do("HGET", devPrefix+deveui, "active_until"))
|
||||||
|
if err == nil {
|
||||||
|
//logit("getActiveUntil: activeuntil: " + activeuntil)
|
||||||
|
res = activeuntil
|
||||||
|
} else {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func InsertAlert(prefix string, deveui string, email string, threshold int) {
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
_, err := conn.Do("SET", prefix+deveui+":"+email, threshold)
|
||||||
|
if err != nil {
|
||||||
|
logit("InsertAlert: Error inserting: " + prefix + deveui + ":" + email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteAlert(prefix string, deveui string, email string) {
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
exists, _ := redis.Bool(conn.Do("EXISTS", prefix+deveui+":"+email))
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
_, err := conn.Do("DEL", prefix+deveui+":"+email)
|
||||||
|
if err != nil {
|
||||||
|
logit("DeleteAlert: Error deleting: " + prefix + deveui + ":" + email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AlarmNotAlreadySent(prefix string, deveui string, email string, threshold int) bool {
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
exists, _ := redis.Bool(conn.Do("EXISTS", prefix+deveui+":"+email))
|
||||||
|
if !exists {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
alarm_threshold, _ := redis.Int(conn.Do("GET", prefix+deveui+":"+email))
|
||||||
|
return threshold != alarm_threshold
|
||||||
|
}
|
||||||
|
|
||||||
|
type OneMetric struct {
|
||||||
|
Deveui string
|
||||||
|
Alias string
|
||||||
|
Timestamp string
|
||||||
|
BatteryPercent string
|
||||||
|
ActiveUntil string
|
||||||
|
DaysUntilDeactivated int // berechneter Wert
|
||||||
|
}
|
||||||
|
|
||||||
|
func CalcDaysUntil(mydate string) int {
|
||||||
|
var days int
|
||||||
|
layout := "02.01.2006"
|
||||||
|
t, err := time.Parse(layout, mydate)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
days = 0
|
||||||
|
}
|
||||||
|
days = int(t.Sub(time.Now()).Hours() / 24)
|
||||||
|
|
||||||
|
return days
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLastMetrics(deveui string) OneMetric {
|
||||||
|
var res OneMetric
|
||||||
|
|
||||||
|
url := "http://localhost:8086/api/v2/query?org=wobischorg"
|
||||||
|
data := []byte(fmt.Sprintf(`from(bucket:"wobischbucket")
|
||||||
|
|> range(start:-5d)
|
||||||
|
|> filter(fn: (r) => r._measurement == "measurement" and r.deveui == "%s")
|
||||||
|
|> filter(fn: (r) => r._field == "vp")
|
||||||
|
|> last() |> yield(name: "last")`, deveui))
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error reading request. ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
req.Header.Set("Authorization", "Token nKYCoz3TA-LItYXG988DjdiStMhrfKmFXQqzxrjzJJ7Ek_iUttzFSE9lfe3s6q99EMdcrjuGlDAjp4Y0VnNRXw==")
|
||||||
|
req.Header.Set("accept", "application/csv")
|
||||||
|
req.Header.Set("content-type", "application/vnd.flux")
|
||||||
|
|
||||||
|
// Set client timeout
|
||||||
|
client := &http.Client{Timeout: time.Second * 10}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error reading response. ", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
//fmt.Println("response Status:", resp.Status)
|
||||||
|
//fmt.Println("response Headers:", resp.Header)
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error reading body. ", err)
|
||||||
|
}
|
||||||
|
//fmt.Println("response Body:", string(body))
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(string(body)))
|
||||||
|
location, err := time.LoadLocation("Europe/Zurich")
|
||||||
|
for scanner.Scan() {
|
||||||
|
s := strings.Split(scanner.Text(), ",")
|
||||||
|
if (len(s) >= 7) && !(strings.HasPrefix(s[5], "_")) {
|
||||||
|
mytime, err := time.Parse(time.RFC3339, s[5])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
res.Timestamp = mytime.In(location).Format("02.01.2006 15:04")
|
||||||
|
value := s[6]
|
||||||
|
field := s[7]
|
||||||
|
if field == "vp" {
|
||||||
|
res.BatteryPercent = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.Deveui = deveui
|
||||||
|
res.Alias = getDevAlias(deveui)
|
||||||
|
res.ActiveUntil = getActiveUntil(deveui)
|
||||||
|
res.DaysUntilDeactivated = CalcDaysUntil(res.ActiveUntil)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckThreshold(d string, vp int, u2 string, last_metric OneMetric, info_threshold int, warning_threshold int, alert_threshold int) bool {
|
||||||
|
var alias string
|
||||||
|
if vp <= info_threshold {
|
||||||
|
alias = getDevAlias(d)
|
||||||
|
}
|
||||||
|
if vp <= alert_threshold {
|
||||||
|
if AlarmNotAlreadySent("alarm_sent_accu:", d, u2, alert_threshold) {
|
||||||
|
sendEmailAccu(u2, alias, d, last_metric.BatteryPercent, alert_threshold, "ALARM")
|
||||||
|
InsertAlert("alarm_sent_accu:", d , u2, alert_threshold)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if vp <= warning_threshold {
|
||||||
|
if AlarmNotAlreadySent("alarm_sent_accu:", d, u2, warning_threshold) {
|
||||||
|
sendEmailAccu(u2, alias, d, last_metric.BatteryPercent, warning_threshold, "WARNING")
|
||||||
|
InsertAlert("alarm_sent_accu:", d, u2, warning_threshold)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if vp <= info_threshold {
|
||||||
|
if AlarmNotAlreadySent("alarm_sent_accu:", d, u2, info_threshold) {
|
||||||
|
sendEmailAccu(u2, alias, d, last_metric.BatteryPercent, alert_threshold, "INFO")
|
||||||
|
InsertAlert("alarm_sent_accu:", d, u2, info_threshold)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func logit(log_message string) {
|
||||||
|
log.Println(log_message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logit("Starting check_battery...")
|
||||||
|
initDB()
|
||||||
|
defer closeDB()
|
||||||
|
|
||||||
|
users := getUsers()
|
||||||
|
for _, u := range users {
|
||||||
|
//fmt.Println(u)
|
||||||
|
u2 := u[5:]
|
||||||
|
my_devs := getMyDevs(u2)
|
||||||
|
for _, d := range my_devs {
|
||||||
|
//fmt.Printf("%s:%s\n", u2, d)
|
||||||
|
if !strings.HasPrefix(d, "@") {
|
||||||
|
last_metric := getLastMetrics(d)
|
||||||
|
// Zuerst der Batteriealarm
|
||||||
|
if last_metric.BatteryPercent != "" {
|
||||||
|
fmt.Printf("%s:%s:%s Percent:%s:%d\n", u2, d, last_metric.BatteryPercent, last_metric.ActiveUntil, last_metric.DaysUntilDeactivated)
|
||||||
|
vp, _ := strconv.Atoi(last_metric.BatteryPercent)
|
||||||
|
if CheckThreshold(d, vp, u2, last_metric, 50, 20, 10) {
|
||||||
|
DeleteAlert("alarm_sent_accu:", d, u2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Jetzt der Alarm wegen der Abodauer
|
||||||
|
if last_metric.DaysUntilDeactivated < 30 {
|
||||||
|
fmt.Printf("SEND EMAIL %s:%s:%s Percent:%s:%d\n", u2, d, last_metric.BatteryPercent, last_metric.ActiveUntil, last_metric.DaysUntilDeactivated)
|
||||||
|
alias := getDevAlias(d)
|
||||||
|
sendEmailAbo("joerg.lehmann@nbit.ch", alias, d, last_metric.DaysUntilDeactivated, "INFO")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logit("Done with check_battery...")
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Contains tells whether a contains x.
|
||||||
|
func Contains(a []string, x string) bool {
|
||||||
|
log.Println("Search for: " + x)
|
||||||
|
for _, n := range a {
|
||||||
|
log.Println("Piece of Array: " + n)
|
||||||
|
if x == n {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if "@"+x == n {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func logit(log_message string) {
|
||||||
|
log.Println(log_message)
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
"net/smtp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sendEmail(username, confirm_id string) {
|
||||||
|
c, err := smtp.Dial("127.0.0.1:25")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
// Set the sender and recipient.
|
||||||
|
c.Mail("register@wo-bisch.ch")
|
||||||
|
c.Rcpt(username)
|
||||||
|
// Send the email body.
|
||||||
|
wc, err := c.Data()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer wc.Close()
|
||||||
|
mail_message := "To: " + username + `
|
||||||
|
Subject: Passwortaenderung auf https://wo-bisch.ch, bitte bestaetigen
|
||||||
|
|
||||||
|
Lieber Benutzer von wo-bisch.ch
|
||||||
|
|
||||||
|
Sie haben soeben eine Passwortaenderung veranlasst. Bitte klicken Sie folgenden Link,
|
||||||
|
um das neue Passwort zu aktivieren:
|
||||||
|
|
||||||
|
https://wo-bisch.ch/confirm?id=` + confirm_id + `
|
||||||
|
|
||||||
|
Bitte ignorieren Sie diese Meldung, falls die Aenderung nicht von Ihnen angefordert wurde!
|
||||||
|
|
||||||
|
Mit freundlichen Grüssen
|
||||||
|
--
|
||||||
|
wo-bisch.ch`
|
||||||
|
|
||||||
|
buf := bytes.NewBufferString(mail_message)
|
||||||
|
if _, err = buf.WriteTo(wc); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendPaymentConfirmationEmail(username, charge_data string, amount int64) {
|
||||||
|
c, err := smtp.Dial("127.0.0.1:25")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
// Set the sender and recipient.
|
||||||
|
c.Mail("info@wo-bisch.ch")
|
||||||
|
c.Rcpt(username)
|
||||||
|
// Send the email body.
|
||||||
|
wc, err := c.Data()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer wc.Close()
|
||||||
|
mail_message := "To: " + username + `
|
||||||
|
Subject: Zahlungsbestaetigung wo-bisch.ch
|
||||||
|
|
||||||
|
Lieber Benutzer von wo-bisch.ch
|
||||||
|
|
||||||
|
Sie haben soeben erfolgreich folgende Abo-Verlaengerungen bezahlt:
|
||||||
|
|
||||||
|
` + charge_data + `
|
||||||
|
|
||||||
|
Mit freundlichen Grüssen
|
||||||
|
--
|
||||||
|
wo-bisch.ch`
|
||||||
|
|
||||||
|
buf := bytes.NewBufferString(mail_message)
|
||||||
|
if _, err = buf.WriteTo(wc); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func serveTemplate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
logit("Called URL: " + r.URL.Path)
|
||||||
|
// wennn kein File angegeben ist: index.html
|
||||||
|
if r.URL.Path == "/" {
|
||||||
|
r.URL.Path = "/index.html"
|
||||||
|
}
|
||||||
|
lp := path.Join("templates", "layout.html")
|
||||||
|
fp := path.Join("snippets", r.URL.Path)
|
||||||
|
|
||||||
|
// Return a 404 if the template doesn't exist
|
||||||
|
_, err := os.Stat(fp)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
logit("URL not found: " + fp)
|
||||||
|
fp = path.Join("snippets", "404.html")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.ParseFiles(lp, fp)
|
||||||
|
if err != nil {
|
||||||
|
// Log the detailed error
|
||||||
|
logit(err.Error())
|
||||||
|
// Return a generic "Internal Server Error" message
|
||||||
|
http.Error(w, http.StatusText(500), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var userName = getUserName(r)
|
||||||
|
|
||||||
|
t := time.Now()
|
||||||
|
var datetimestring = t.Format("20060102150405")
|
||||||
|
var trackers = getMyDevs(userName)
|
||||||
|
var last_metrics []OneMetric
|
||||||
|
|
||||||
|
query_values := r.URL.Query()
|
||||||
|
|
||||||
|
if r.URL.Path == "/tracker.html" {
|
||||||
|
// wir holen noch die letzten Metriken
|
||||||
|
for _, v := range trackers {
|
||||||
|
deveui := v
|
||||||
|
readonly := false
|
||||||
|
if strings.HasPrefix(deveui, "@") {
|
||||||
|
deveui = deveui[1:]
|
||||||
|
readonly = true
|
||||||
|
}
|
||||||
|
last_metric := getLastMetrics(deveui)
|
||||||
|
last_metric.Readonly = readonly
|
||||||
|
last_metrics = append(last_metrics, last_metric)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
UserName string
|
||||||
|
DateTimeString string
|
||||||
|
Tracker []string
|
||||||
|
LastMetrics []OneMetric
|
||||||
|
QueryValues map[string][]string
|
||||||
|
}{
|
||||||
|
userName,
|
||||||
|
datetimestring,
|
||||||
|
trackers,
|
||||||
|
last_metrics,
|
||||||
|
query_values,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tmpl.ExecuteTemplate(w, "layout", &data); err != nil {
|
||||||
|
logit(err.Error())
|
||||||
|
http.Error(w, http.StatusText(500), 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
initDB()
|
||||||
|
defer closeDB()
|
||||||
|
fs := http.FileServer(http.Dir("static"))
|
||||||
|
http.Handle("/static/", http.StripPrefix("/static/", fs))
|
||||||
|
http.Handle("/favicon.ico", fs)
|
||||||
|
http.HandleFunc("/", serveTemplate)
|
||||||
|
|
||||||
|
http.HandleFunc("/login", loginHandler)
|
||||||
|
http.HandleFunc("/reset_password", resetPasswordHandler)
|
||||||
|
http.HandleFunc("/set_password", setPasswordHandler)
|
||||||
|
http.HandleFunc("/logout", logoutHandler)
|
||||||
|
http.HandleFunc("/confirm", confirmHandler)
|
||||||
|
http.HandleFunc("/metrics", metricsHandler)
|
||||||
|
http.HandleFunc("/lastmetrics", lastmetricsHandler)
|
||||||
|
http.HandleFunc("/save_tracker_settings", save_tracker_settingsHandler)
|
||||||
|
http.HandleFunc("/getstripepaymentintent", getstripepaymentintentHandler)
|
||||||
|
http.HandleFunc("/stripewebhook", stripeWebhookHandler)
|
||||||
|
|
||||||
|
logit("Starting Web Application...")
|
||||||
|
http.ListenAndServe("127.0.0.1:4000", nil)
|
||||||
|
logit("Terminating Web Application...")
|
||||||
|
}
|
||||||
+354
@@ -0,0 +1,354 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OneMetric struct {
|
||||||
|
Deveui string
|
||||||
|
Alias string
|
||||||
|
Readonly bool
|
||||||
|
Timestamp string
|
||||||
|
Lat string
|
||||||
|
Lon string
|
||||||
|
Vbat string
|
||||||
|
Fw string
|
||||||
|
BatteryPercent string
|
||||||
|
ActiveUntil string
|
||||||
|
DaysUntilDeactivated int // berechneter Wert
|
||||||
|
}
|
||||||
|
|
||||||
|
// metrics handler
|
||||||
|
|
||||||
|
func validProperty(prop string) bool {
|
||||||
|
valid_properties := [...]string{"lat", "lon", "vbat", "fw"}
|
||||||
|
for _, p := range valid_properties {
|
||||||
|
if p == prop {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func metricsHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
name := getUserName(request)
|
||||||
|
if name != "" {
|
||||||
|
|
||||||
|
property, ok := request.URL.Query()["property"]
|
||||||
|
if !ok || len(property[0]) < 1 {
|
||||||
|
log.Println("Url Param 'property' is missing")
|
||||||
|
fmt.Fprintf(response, "{ \"msg\": \"error: property must be specified in URL\" }")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validProperty(property[0]) {
|
||||||
|
log.Println("Url Param 'property' is invalid")
|
||||||
|
fmt.Fprintf(response, "{ \"msg\": \"error: invalid property\" }")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deveui, ok := request.URL.Query()["deveui"]
|
||||||
|
|
||||||
|
if !ok || len(deveui[0]) < 1 {
|
||||||
|
log.Println("Url Param 'deveui' is missing")
|
||||||
|
fmt.Fprintf(response, "{ \"msg\": \"error: deveui must be specified in URL\" }")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Query()["deveui"] will return an array of items,
|
||||||
|
// we only want the single item.
|
||||||
|
mydeveui := deveui[0]
|
||||||
|
|
||||||
|
if !(Contains(getMyDevs(name), mydeveui)) {
|
||||||
|
log.Println("specified 'deveui' does not belong to this user")
|
||||||
|
fmt.Fprintf(response, "{ \"msg\": \"error: specified deveui does not belong to this user\" }")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if AboExpired(mydeveui) {
|
||||||
|
log.Println("specified 'deveui' has an expired abo")
|
||||||
|
fmt.Fprintf(response, "{ \"msg\": \"specified deveui has an expired abo\" }")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Url Param 'deveui' is: " + string(mydeveui))
|
||||||
|
|
||||||
|
// Format of start and stop: YYYY-MM-DDTHH:MI:SSZ
|
||||||
|
|
||||||
|
stop, ok := request.URL.Query()["stop"]
|
||||||
|
var mystop string
|
||||||
|
if !ok || len(stop[0]) < 1 {
|
||||||
|
log.Println("Url Param 'stop' is missing, set it to now")
|
||||||
|
mystop = time.Now().Format("2006-01-02T15:04:05Z")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
mystop = stop[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
layout := "2006-01-02T15:04:05Z"
|
||||||
|
stopDate, err := time.Parse(layout, mystop)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
start, ok := request.URL.Query()["start"]
|
||||||
|
var mystart string
|
||||||
|
if !ok || len(start[0]) < 1 {
|
||||||
|
log.Println("Url Param 'start' is missing, set it to stop minus one day")
|
||||||
|
t := stopDate.AddDate(0, 0, -1)
|
||||||
|
mystart = t.Format("2006-01-02T15:04:05Z")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
mystart = start[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
url := "http://localhost:8086/api/v2/query?org=wobischorg"
|
||||||
|
data := []byte(fmt.Sprintf(`from(bucket:"wobischbucket") |> range(start: %s, stop: %s) |> filter(fn: (r) => r._measurement == "measurement") |> filter(fn: (r) => r._field == "%s") |> filter(fn: (r) => r.deveui == "%s")`, mystart, mystop, property[0], mydeveui))
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error reading request. ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
req.Header.Set("Authorization", "Token TQvQxxLLAj1kTKWuEqcx7BA-KfE6WtJUeDlPa_Dnvms6Zqf6uh6lMbpXtzcsCjKO_x3PrpxxGDR5E6YnDB5PFg==")
|
||||||
|
req.Header.Set("accept", "application/csv")
|
||||||
|
req.Header.Set("content-type", "application/vnd.flux")
|
||||||
|
|
||||||
|
// Set client timeout
|
||||||
|
client := &http.Client{Timeout: time.Second * 10}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error reading response. ", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
fmt.Println("response Status:", resp.Status)
|
||||||
|
fmt.Println("response Headers:", resp.Header)
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error reading body. ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(response, "[\n")
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(string(body)))
|
||||||
|
first := true
|
||||||
|
for scanner.Scan() {
|
||||||
|
s := strings.Split(scanner.Text(), ",")
|
||||||
|
if (len(s) >= 7) && !(strings.HasPrefix(s[5], "_")) {
|
||||||
|
t, err := time.Parse(time.RFC3339, s[5])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
a := t.Unix()
|
||||||
|
b := s[6]
|
||||||
|
if !(first) {
|
||||||
|
fmt.Fprintf(response, ",")
|
||||||
|
} else {
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
fmt.Fprintf(response, "[%d000,%s]\n", a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(response, "]\n")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(response, "{ \"msg\": \"Only available for logged in users\" }")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lastmetricsHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
name := getUserName(request)
|
||||||
|
if name != "" {
|
||||||
|
|
||||||
|
deveui, ok := request.URL.Query()["deveui"]
|
||||||
|
|
||||||
|
if !ok || len(deveui[0]) < 1 {
|
||||||
|
log.Println("Url Param 'deveui' is missing")
|
||||||
|
fmt.Fprintf(response, "{ \"msg\": \"deveui must be specified in URL\" }")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Query()["deveui"] will return an array of items,
|
||||||
|
// we only want the single item.
|
||||||
|
mydeveui := deveui[0]
|
||||||
|
|
||||||
|
if !(Contains(getMyDevs(name), mydeveui)) {
|
||||||
|
log.Println("specified 'deveui' does not belong to this user")
|
||||||
|
fmt.Fprintf(response, "{ \"msg\": \"specified deveui does not belong to this user\" }")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Url Param 'deveui' is: " + string(mydeveui))
|
||||||
|
|
||||||
|
url := "http://localhost:8086/api/v2/query?org=wobischorg"
|
||||||
|
data := []byte(fmt.Sprintf(`from(bucket:"wobischbucket")
|
||||||
|
|> range(start:-365d)
|
||||||
|
|> filter(fn: (r) => r._measurement == "measurement" and r.deveui == "%s")
|
||||||
|
|> filter(fn: (r) => r._field == "lat" or r._field == "lon" or r._field == "vbat" or r._field == "fw")
|
||||||
|
|> last(column: "_time") |> yield(name: "last")`, mydeveui))
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error reading request. ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
req.Header.Set("Authorization", "Token TQvQxxLLAj1kTKWuEqcx7BA-KfE6WtJUeDlPa_Dnvms6Zqf6uh6lMbpXtzcsCjKO_x3PrpxxGDR5E6YnDB5PFg==")
|
||||||
|
req.Header.Set("accept", "application/csv")
|
||||||
|
req.Header.Set("content-type", "application/vnd.flux")
|
||||||
|
|
||||||
|
// Set client timeout
|
||||||
|
client := &http.Client{Timeout: time.Second * 10}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error reading response. ", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
fmt.Println("response Status:", resp.Status)
|
||||||
|
fmt.Println("response Headers:", resp.Header)
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error reading body. ", err)
|
||||||
|
}
|
||||||
|
fmt.Println("response Body 1:", string(body))
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(string(body)))
|
||||||
|
ts := ""
|
||||||
|
lat := ""
|
||||||
|
lon := ""
|
||||||
|
vbat := ""
|
||||||
|
fw := ""
|
||||||
|
location, err := time.LoadLocation("Europe/Zurich")
|
||||||
|
for scanner.Scan() {
|
||||||
|
s := strings.Split(scanner.Text(), ",")
|
||||||
|
if (len(s) >= 7) && !(strings.HasPrefix(s[5], "_")) {
|
||||||
|
mytime, err := time.Parse(time.RFC3339, s[5])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ts = mytime.In(location).Format("02.01.2006 15:04")
|
||||||
|
value := s[6]
|
||||||
|
field := s[7]
|
||||||
|
if field == "lat" {
|
||||||
|
lat = value
|
||||||
|
} else if field == "lon" {
|
||||||
|
lon = value
|
||||||
|
} else if field == "vbat" {
|
||||||
|
vbat = value
|
||||||
|
} else if field == "fw" {
|
||||||
|
fw = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(response, `{
|
||||||
|
"ts": "%s",
|
||||||
|
"lat": "%s",
|
||||||
|
"lon": "%s",
|
||||||
|
"vbat": "%s",
|
||||||
|
"fw": "%s"
|
||||||
|
}`, ts, lat, lon, vbat, fw)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(response, "{ \"msg\": \"Only available for logged in users\" }")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CalcDaysUntil(mydate string) int {
|
||||||
|
var days int
|
||||||
|
layout := "02.01.2006"
|
||||||
|
t, err := time.Parse(layout, mydate)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
days = 0
|
||||||
|
}
|
||||||
|
days = int(t.Sub(time.Now()).Hours() / 24)
|
||||||
|
|
||||||
|
return days
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLastMetrics(deveui string) OneMetric {
|
||||||
|
var res OneMetric
|
||||||
|
|
||||||
|
url := "http://localhost:8086/api/v2/query?org=wobischorg"
|
||||||
|
data := []byte(fmt.Sprintf(`from(bucket:"wobischbucket")
|
||||||
|
|> range(start:-365d)
|
||||||
|
|> filter(fn: (r) => r._measurement == "measurement" and r.deveui == "%s")
|
||||||
|
|> filter(fn: (r) => r._field == "lat" or r._field == "lon" or r._field == "vbat" or r._field == "fw")
|
||||||
|
|> last(column: "_time") |> yield(name: "last")`, deveui))
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error reading request. ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
req.Header.Set("Authorization", "Token TQvQxxLLAj1kTKWuEqcx7BA-KfE6WtJUeDlPa_Dnvms6Zqf6uh6lMbpXtzcsCjKO_x3PrpxxGDR5E6YnDB5PFg==")
|
||||||
|
req.Header.Set("accept", "application/csv")
|
||||||
|
req.Header.Set("content-type", "application/vnd.flux")
|
||||||
|
|
||||||
|
// Set client timeout
|
||||||
|
client := &http.Client{Timeout: time.Second * 10}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error reading response. ", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
fmt.Println("response Status:", resp.Status)
|
||||||
|
fmt.Println("response Headers:", resp.Header)
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error reading body. ", err)
|
||||||
|
}
|
||||||
|
fmt.Println("response Body 2:", string(body))
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(string(body)))
|
||||||
|
location, err := time.LoadLocation("Europe/Zurich")
|
||||||
|
for scanner.Scan() {
|
||||||
|
s := strings.Split(scanner.Text(), ",")
|
||||||
|
fmt.Printf("BlaBla: %v\n", s)
|
||||||
|
if (len(s) >= 7) && !(strings.HasPrefix(s[5], "_")) {
|
||||||
|
mytime, err := time.Parse(time.RFC3339, s[5])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
res.Timestamp = mytime.In(location).Format("02.01.2006 15:04")
|
||||||
|
value := s[6]
|
||||||
|
field := s[7]
|
||||||
|
if field == "lat" {
|
||||||
|
res.Lat = value
|
||||||
|
} else if field == "lon" {
|
||||||
|
res.Lon = value
|
||||||
|
} else if field == "vbat" {
|
||||||
|
res.Vbat = value
|
||||||
|
} else if field == "fw" {
|
||||||
|
res.Fw = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.Deveui = deveui
|
||||||
|
res.Alias = getDevAlias(deveui)
|
||||||
|
res.Readonly = false
|
||||||
|
res.ActiveUntil = getActiveUntil(deveui)
|
||||||
|
res.DaysUntilDeactivated = CalcDaysUntil(res.ActiveUntil)
|
||||||
|
return res
|
||||||
|
}
|
||||||
Executable
+29
@@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
#
|
||||||
|
# Minify Font Awesome js.all
|
||||||
|
#
|
||||||
|
# Remove all unused icons
|
||||||
|
#
|
||||||
|
# Pass a list of icon names (remove the "fa-" prefix), comma separated
|
||||||
|
#
|
||||||
|
# Usage example: minify-fa-js.py home,address-card
|
||||||
|
#
|
||||||
|
# Reads from STDIN, writes to STDOUT
|
||||||
|
#
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage (example): minify-fa-js.py home,address-card")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
fontnames = sys.argv[1].split(",")
|
||||||
|
|
||||||
|
for line in sys.stdin:
|
||||||
|
if re.search(r"\"[0-9a-z-]+\": \[", line):
|
||||||
|
for fontname in fontnames:
|
||||||
|
ss = '"'+fontname+'": ['
|
||||||
|
if ss in line:
|
||||||
|
print(line),
|
||||||
|
else:
|
||||||
|
print(line),
|
||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
cat /home/appuser/wo-bisch-web/static/js/fontawesome-5.11.2/all.js |/home/appuser/wo-bisch-web/minify-fa-js.py home,address-card,balance-scale,sign-out-alt,sign-in-alt,calendar,balance-scale,thermometer-half,tint,cloud,battery-three-quarters,envelope,check,lock,exclamation-triangle,cog,plus,shopping-cart,minus,arrow-right,caret-down,map-marked-alt >/home/appuser/wo-bisch-web/static/js/fontawesome-5.11.2/all-minified.js
|
||||||
+362
@@ -0,0 +1,362 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gomodule/redigo/redis"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var globalPool *redis.Pool
|
||||||
|
|
||||||
|
const userPrefix string = "user:"
|
||||||
|
const devPrefix string = "dev:"
|
||||||
|
const confirmPrefix string = "confirm:"
|
||||||
|
|
||||||
|
func newPool() *redis.Pool {
|
||||||
|
return &redis.Pool{
|
||||||
|
// Maximum number of idle connections in the pool.
|
||||||
|
MaxIdle: 80,
|
||||||
|
// max number of connections
|
||||||
|
MaxActive: 12000,
|
||||||
|
// Dial is an application supplied function for creating and
|
||||||
|
// configuring a connection.
|
||||||
|
Dial: func() (redis.Conn, error) {
|
||||||
|
c, err := redis.Dial("tcp", ":6379")
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
return c, err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ping tests connectivity for redis (PONG should be returned)
|
||||||
|
func ping(c redis.Conn) error {
|
||||||
|
// Send PING command to Redis
|
||||||
|
// PING command returns a Redis "Simple String"
|
||||||
|
// Use redis.String to convert the interface type to string
|
||||||
|
s, err := redis.String(c.Do("PING"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logit("PING Response = " + s)
|
||||||
|
// Output: PONG
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dev struct {
|
||||||
|
Deveui string
|
||||||
|
Alias string
|
||||||
|
ActiveUntil string // Abo bezahlt bis TT.MM.YYYY
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDB() {
|
||||||
|
|
||||||
|
// newPool returns a pointer to a redis.Pool
|
||||||
|
pool := newPool()
|
||||||
|
// get a connection from the globalPool (redis.Conn)
|
||||||
|
conn := pool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
globalPool = pool
|
||||||
|
|
||||||
|
// wir machen einen Connection Test
|
||||||
|
ping(conn)
|
||||||
|
|
||||||
|
// Wir legen einen initialen Admin User an, falls es diesen noch nicht gibt
|
||||||
|
if checkUserAvailable("joerg.lehmann@nbit.ch") {
|
||||||
|
insertUser("joerg.lehmann@nbit.ch", "changeme123", "Y")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeDB() {
|
||||||
|
globalPool.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUsers() []string {
|
||||||
|
res := []string{}
|
||||||
|
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
logit("getUsers")
|
||||||
|
users, err := redis.Strings(conn.Do("KEYS", userPrefix+"*"))
|
||||||
|
if err == nil {
|
||||||
|
logit("getUsers successful!")
|
||||||
|
res = users
|
||||||
|
} else {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTrackerSettings(trackerSettings Dev) error {
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// SET object
|
||||||
|
_, err := conn.Do("HMSET", devPrefix+trackerSettings.Deveui, "alias", trackerSettings.Alias)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkUserAvailable(username string) bool {
|
||||||
|
logit("checkUserAvailable: User: " + username)
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
res, err := redis.Int(conn.Do("EXISTS", userPrefix+username))
|
||||||
|
if err == nil {
|
||||||
|
logit("Result of EXISTS: " + strconv.Itoa(res))
|
||||||
|
return res == 0
|
||||||
|
} else {
|
||||||
|
logit("checkUserAvailable: Error to query Key Value Store")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMyDevs(username string) []string {
|
||||||
|
res := []string{}
|
||||||
|
|
||||||
|
if username == "" {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
logit("getMyDevs: User: " + username)
|
||||||
|
mydevs, err := redis.String(conn.Do("HGET", userPrefix+username, "my_devs"))
|
||||||
|
if err == nil {
|
||||||
|
logit("getMyDevs: mydevs: " + mydevs)
|
||||||
|
res = strings.Split(mydevs, ",")
|
||||||
|
} else {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDevAlias(deveui string) string {
|
||||||
|
res := deveui
|
||||||
|
|
||||||
|
if deveui == "" {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
logit("getDevAlias: Deveui: " + deveui)
|
||||||
|
alias, err := redis.String(conn.Do("HGET", devPrefix+deveui, "alias"))
|
||||||
|
if err == nil {
|
||||||
|
logit("getDevAlias: alias: " + alias)
|
||||||
|
res = alias
|
||||||
|
} else {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func getActiveUntil(deveui string) string {
|
||||||
|
res := ""
|
||||||
|
|
||||||
|
if deveui == "" {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
logit("getActiveUntil: Deveui: " + deveui)
|
||||||
|
activeuntil, err := redis.String(conn.Do("HGET", devPrefix+deveui, "active_until"))
|
||||||
|
if err == nil {
|
||||||
|
logit("getActiveUntil: activeuntil: " + activeuntil)
|
||||||
|
res = activeuntil
|
||||||
|
} else {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func AboExpired(deveui string) bool {
|
||||||
|
active_until := getActiveUntil(deveui)
|
||||||
|
|
||||||
|
layout := "02.01.2006"
|
||||||
|
t, _ := time.Parse(layout, active_until)
|
||||||
|
|
||||||
|
return t.Before(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
func prolongActivation(deveui string, years int) (string, error) {
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
active_until_old, err := redis.String(conn.Do("HGET", devPrefix+deveui, "active_until"))
|
||||||
|
if err == nil {
|
||||||
|
logit("prolongActivation: active_until: " + active_until_old)
|
||||||
|
} else {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
layout := "02.01.2006"
|
||||||
|
t, err := time.Parse(layout, active_until_old)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
fmt.Println(t.Unix())
|
||||||
|
|
||||||
|
var t_new time.Time
|
||||||
|
if t.Before(time.Now()) {
|
||||||
|
t_new = time.Now().AddDate(years, 0, 0)
|
||||||
|
} else {
|
||||||
|
t_new = t.AddDate(years, 0, 0)
|
||||||
|
}
|
||||||
|
active_until_new := t_new.Format(layout)
|
||||||
|
|
||||||
|
// SET object
|
||||||
|
_, err1 := conn.Do("HMSET", devPrefix+deveui, "active_until", active_until_new)
|
||||||
|
if err1 != nil {
|
||||||
|
return "", err1
|
||||||
|
}
|
||||||
|
|
||||||
|
return active_until_new, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func randString(n int) string {
|
||||||
|
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
|
var bytes = make([]byte, n)
|
||||||
|
rand.Read(bytes)
|
||||||
|
for i, b := range bytes {
|
||||||
|
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||||
|
}
|
||||||
|
return string(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertUser(username, password, is_admin string) {
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
logit("insertUser: " + username)
|
||||||
|
pwd := []byte(password)
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword(pwd, bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
logit("insertUser: Error with bcrypt.GenerateFromPassword, User: " + username)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
confirm_id := ""
|
||||||
|
|
||||||
|
_, err = conn.Do("HMSET", userPrefix+username, "password", string(hashedPassword), "new_password", string(hashedPassword), "confirm_id", confirm_id, "last_login", "", "my_devs", "")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logit("insertUser: Error inserting User: " + username)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUser(username, password string) {
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
logit("updateUser: " + username)
|
||||||
|
pwd := []byte(password)
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword(pwd, bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
logit("updateUser: Error with bcrypt.GenerateFromPassword, User: " + username)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
confirm_id := randString(30)
|
||||||
|
|
||||||
|
_, err = conn.Do("HMSET", userPrefix+username, "new_password", string(hashedPassword), "confirm_id", confirm_id)
|
||||||
|
if err != nil {
|
||||||
|
logit("updateUser: Error updateing User: " + username)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = conn.Do("SET", confirmPrefix+confirm_id, username)
|
||||||
|
if err != nil {
|
||||||
|
logit("updateUser: Error inserting confirm_id: " + confirm_id + ": " + username)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sendEmail(username, confirm_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkLoginCredentials(username, password string) bool {
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
logit("checkLoginCredentials: called with username,password: " + username + "," + password)
|
||||||
|
pwd, err := redis.String(conn.Do("HGET", userPrefix+username, "password"))
|
||||||
|
if err == nil {
|
||||||
|
logit("checkLoginCredentials: pwd: " + pwd + " CMD: HGET " + userPrefix + username + userPrefix + username + " confirm_id")
|
||||||
|
cid, err := redis.String(conn.Do("HGET", userPrefix+username, "confirm_id"))
|
||||||
|
if err == nil {
|
||||||
|
logit("checkLoginCredentials: cid: " + cid)
|
||||||
|
if !(err != nil && cid != "") {
|
||||||
|
logit("checkLoginCredentials: pwd: " + pwd)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Print(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedPassword := []byte(pwd)
|
||||||
|
err = bcrypt.CompareHashAndPassword(hashedPassword, []byte(password))
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLoginTime(username string) {
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
_, err := conn.Do("HSET", userPrefix+username, "last_login", time.Now().UTC().Format("2006-01-02 15:04:05"))
|
||||||
|
if err != nil {
|
||||||
|
logit("updateUser: Error updateing User: " + username)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func confirmUser(confirm_id string) bool {
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
u, err := redis.String(conn.Do("GET", confirmPrefix+confirm_id))
|
||||||
|
if err != nil {
|
||||||
|
logit("confirmUser: Error with searching confirm_id: " + confirm_id)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
new_password, err := redis.String(conn.Do("HGET", userPrefix+u, "new_password"))
|
||||||
|
if err != nil {
|
||||||
|
logit("confirmUser: Error with getting new_password: " + u)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err = conn.Do("HMSET", userPrefix+u, "confirm_id", "", "password", new_password)
|
||||||
|
if err != nil {
|
||||||
|
logit("confirmUser: Error updateing User: " + u)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err = conn.Do("DEL", confirmPrefix+confirm_id)
|
||||||
|
if err != nil {
|
||||||
|
logit("confirmUser: Error deleting confirm_id: " + confirm_id)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{{define "body_content"}}
|
||||||
|
<div class="notification is-danger">
|
||||||
|
<strong>Diese Seite existiert nicht</strong>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{{define "header_additions"}}
|
||||||
|
<script src="https://js.stripe.com/v3/"></script>
|
||||||
|
{{end}}
|
||||||
|
{{define "body_content"}}
|
||||||
|
<p class="title is-4">Checkout</p>
|
||||||
|
|
||||||
|
<p id="charge_data" hidden>0002CC01000003F7:2</p>
|
||||||
|
|
||||||
|
<div id="card-element">
|
||||||
|
<!-- Elements will create input elements here -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- We'll put the error messages in this element -->
|
||||||
|
<div id="card-errors" role="alert"></div>
|
||||||
|
|
||||||
|
<button id="submit">Pay</button>
|
||||||
|
|
||||||
|
<script src="static/js/checkout.js"></script>
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{{define "body_content"}}
|
||||||
|
<p class="title is-4">Kontakt</p>
|
||||||
|
<p>nbit Informatik GmbH<br />
|
||||||
|
Kirchweg 2<br />
|
||||||
|
3510 Konolfingen<br />
|
||||||
|
<br />
|
||||||
|
+41 31 792 00 40<br />
|
||||||
|
<a href='mailto:i%6Efo@%6Ebit.%63h'>info@nbit.ch</a>
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
{{define "body_content"}}
|
||||||
|
{{ if ne .UserName "" }}
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/momentjs/latest/moment-with-locales.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css" />
|
||||||
|
|
||||||
|
<link href="static/bulma-calendar/css/bulma-calendar.min.css" rel="stylesheet">
|
||||||
|
<script src="static/bulma-calendar/js/bulma-calendar.min.js"></script>
|
||||||
|
|
||||||
|
<div class="columns" id="myselectors">
|
||||||
|
<div class="column is-half py-1">
|
||||||
|
<div id="reportrange" style="background: #fff; cursor: pointer; padding: 5px 10px; border: 1px solid #ccc; width: 100%">
|
||||||
|
<i class="fa fa-calendar"></i>
|
||||||
|
<span></span> <i class="fa fa-caret-down"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-half py-1">
|
||||||
|
<a id="btn_w" class="button">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fa fa-balance-scale"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a id="btn_t" class="button">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fa fa-thermometer-half"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a id="btn_h" class="button">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fa fa-tint"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a id="btn_p" class="button">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fa fa-cloud"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a id="btn_vp" class="button">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fa fa-battery-three-quarters"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="static/js/graph.js"></script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<section id="chart">
|
||||||
|
<!-- Content ... -->
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
|
||||||
|
|
||||||
|
<script src="static/js/chart.js"></script>
|
||||||
|
|
||||||
|
{{ else }}
|
||||||
|
<h4>Bitte zuerst <a href="login.html">einloggen</a></h4>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{{define "header_additions"}}
|
||||||
|
{{end}}
|
||||||
|
{{define "body_content"}}
|
||||||
|
<p class="title is-4">GPS Tracking leichtgemacht...</p>
|
||||||
|
<div class="message is-danger">
|
||||||
|
<div class="message-body">
|
||||||
|
Aktuell noch in Entwicklung, kommen Sie später noch einmal vorbei...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ if ne .UserName "" }}
|
||||||
|
<p>
|
||||||
|
<strong>Ich will weitere bestellen!</strong>
|
||||||
|
<span class="icon"><i class="fa fa-arrow-right"></i></span>
|
||||||
|
Hier geht's zur <a href="/order.html">Bestellung</a>
|
||||||
|
</p>
|
||||||
|
{{ else }}
|
||||||
|
<p>
|
||||||
|
<strong>Ich will auch einen!</strong>
|
||||||
|
<span class="icon"><i class="fa fa-arrow-right"></i></span>
|
||||||
|
Hier geht's zur <a href="/order.html">Bestellung</a>
|
||||||
|
</p>
|
||||||
|
<p> </p>
|
||||||
|
<p>
|
||||||
|
<strong>Ich habe bereits einen (oder mehrere)</strong>
|
||||||
|
<span class="icon"><i class="fa fa-arrow-right"></i></span>
|
||||||
|
Hier geht's zum <a href="/login.html">Login</a>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{{define "body_content"}}
|
||||||
|
<div class="notification is-danger">
|
||||||
|
<strong>Ungültiges Login!</strong>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{{define "body_content"}}
|
||||||
|
{{ if ne .UserName "" }}
|
||||||
|
Sie sind bereits eingeloggt!
|
||||||
|
{{ else }}
|
||||||
|
<form class="form-signin" id="login-form" action="/login">
|
||||||
|
<div class="column is-auto">
|
||||||
|
<div class="columns is-centered">
|
||||||
|
<div class="column is-4">
|
||||||
|
<h1 class="title">Login</h1>
|
||||||
|
<div class="field">
|
||||||
|
<p class="control has-icons-left has-icons-right">
|
||||||
|
<input id="email" name="email" class="input is-success is-focused" type="text" placeholder="E-Mail">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fa fa-envelope"></i>
|
||||||
|
</span>
|
||||||
|
<span class="icon is-small is-right">
|
||||||
|
<i class="fa fa-check"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<p class="control has-icons-left has-icons-right">
|
||||||
|
<input id="password" name="password" class="input" type="password" placeholder="Passwort">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fa fa-lock"></i>
|
||||||
|
</span>
|
||||||
|
<span class="icon is-small is-right">
|
||||||
|
<i class="fa fa-check"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="has-text-centered">
|
||||||
|
<a href="/reset_password.html">Passwort vergessen?</a><br/>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div id="errorbox" class="notification is-danger is-size-7-mobile" style="display: none;">
|
||||||
|
</div>
|
||||||
|
<div class="has-text-centered">
|
||||||
|
<input id="login-button" name="login-button" type="submit" class="form-button button is-primary is-centered" value="Anmelden"></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{{define "body_content"}}
|
||||||
|
<p class="title is-4">Bestellformular</p>
|
||||||
|
<div class="message is-danger">
|
||||||
|
<div class="message-body">
|
||||||
|
Aktuell noch in Entwicklung, kommen Sie später noch einmal vorbei...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{{define "body_content"}}
|
||||||
|
{{ if ne .UserName "" }}
|
||||||
|
Sie sind bereits eingeloggt!
|
||||||
|
{{ else }}
|
||||||
|
<div class="notification is-info">
|
||||||
|
Passwort wurde erfolgreich geändert!
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{{define "body_content"}}
|
||||||
|
{{ if ne .UserName "" }}
|
||||||
|
|
||||||
|
<p class="title is-4">Zahlung abgebrochen!</p>
|
||||||
|
<p>Zahlung wurde abgebrochen</p>
|
||||||
|
{{ else }}
|
||||||
|
<h4>Bitte zuerst <a href="login.html">einloggen</a></h4>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{{define "body_content"}}
|
||||||
|
{{ if ne .UserName "" }}
|
||||||
|
|
||||||
|
<p class="title is-4">Danke schoen!</p>
|
||||||
|
<p>Danke fuer die Zahlung!</p>
|
||||||
|
{{ else }}
|
||||||
|
<h4>Bitte zuerst <a href="login.html">einloggen</a></h4>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{{define "body_content"}}
|
||||||
|
{{ if ne .UserName "" }}
|
||||||
|
Sie sind bereits eingeloggt!
|
||||||
|
{{ else }}
|
||||||
|
<p class="title is-4">Passwort zurücksetzen</p>
|
||||||
|
<div class="notification is-info">
|
||||||
|
<p>Hier können Sie ein neues Passwort setzen. Sie erhalten anschliessend eine Meldung zur Bestätigung zugesendet. Das neue Passwort wird erst gültig, wenn Sie die Bestätigung durchgeführt haben.</p>
|
||||||
|
</div>
|
||||||
|
<form class="form-signin" id="reset-password-form" action="/reset_password">
|
||||||
|
<div class="column is-auto">
|
||||||
|
<div class="columns is-centered">
|
||||||
|
<div class="column is-4">
|
||||||
|
<div class="field">
|
||||||
|
<p class="control has-icons-left has-icons-right">
|
||||||
|
<input id="email" name="email" class="input is-success is-focused" type="text" placeholder="E-Mail">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fa fa-envelope"></i>
|
||||||
|
</span>
|
||||||
|
<span class="icon is-small is-right">
|
||||||
|
<i class="fa fa-check"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<p class="control has-icons-left has-icons-right">
|
||||||
|
<input id="password" name="password" class="input" type="password" placeholder="Password">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fa fa-lock"></i>
|
||||||
|
</span>
|
||||||
|
<span class="icon is-small is-right">
|
||||||
|
<i class="fa fa-check"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div id="errorbox" class="notification is-danger is-size-7-mobile" style="display: none;">
|
||||||
|
</div>
|
||||||
|
<div class="has-text-centered">
|
||||||
|
<input id="login-button" name="login-button" type="submit" class="form-button button is-primary is-centered" value="Passwort zurücksetzen"></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
{{define "body_content"}}
|
||||||
|
{{ if ne .UserName "" }}
|
||||||
|
<div id="modal" class="modal">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">Einstellungen</p>
|
||||||
|
</header>
|
||||||
|
<div class="modal-card-body">
|
||||||
|
<!-- Content ... -->
|
||||||
|
<div class="field"> <label id="label" class="label">Bezeichnung</label>
|
||||||
|
<div class="control has-icons-right">
|
||||||
|
<input id="alias" class="input" type="text" maxlength="25">
|
||||||
|
<span id="alias_exclamation" class="icon is-small is-right">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p id="alias_errormsg" class="help is-danger"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="is-size-7">
|
||||||
|
Device ID: <span id="deveui"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<button id="modal-save" class="button is-success">OK</button>
|
||||||
|
<button id="modal-close" class="button">Cancel</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="cart" class="modal">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">Abo verlängern</p>
|
||||||
|
</header>
|
||||||
|
<p id="charge_data" hidden>0</p>
|
||||||
|
<div class="modal-card-body">
|
||||||
|
<div id="abos_verlaengern">
|
||||||
|
<!-- Content ... -->
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="has-margin-top-20" id="card-element">
|
||||||
|
<!-- Elements will create input elements here -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- We'll put the error messages in this element -->
|
||||||
|
<div class="message is-danger has-margin-top-20" id="card-errors-article" role="alert">
|
||||||
|
<div class="message-body" id="card-errors">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
|
||||||
|
<button id="cart-pay" class="button is-success">Bezahlen</button>
|
||||||
|
<button id="cart-close" class="button">Cancel</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="payment_notifier" class="modal">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">Info: Bezahlung durchgeführt</p>
|
||||||
|
</header>
|
||||||
|
<div class="modal-card-body">
|
||||||
|
<div class="message is-info">
|
||||||
|
<div class="message-body">
|
||||||
|
Sie haben eine Abo Verlängerung bezahlt, besten Dank!
|
||||||
|
|
||||||
|
Sie erhalten eine E-Mail, sobald die Zahlung erfolgreich abgeschlossen ist.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
|
||||||
|
<button id="payment_notifier_close" class="button is-success">Schliessen</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{range .LastMetrics}}
|
||||||
|
<div class="column waage is-full notification is-warning">
|
||||||
|
<p class="is-size-2 is-size-5-mobile has-text-centered has-text-weight-bold" ><span class="alias" id="alias_{{.Deveui}}">{{.Alias}}</span>{{ if not .Readonly }}<a class="show-modal" class="block-link"><span style="float:right;" class="icon is-size-4 is-size-5-mobile"><i class="fa fa-cog"></i></span></a>{{ end }}</p>
|
||||||
|
<p id="lastmeasurement_{{.Deveui}}" class="has-text-centered">letzte übermittelte Messung: {{.Timestamp}}</p>
|
||||||
|
<div id="{{.Deveui}}">
|
||||||
|
<div class="column is-full notification is-warning">
|
||||||
|
<nav class="level">
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<a class="block-link" href="/graph.html?deveui={{.Deveui}}&alias={{.Alias}}&property=t">
|
||||||
|
<div>
|
||||||
|
<p class="icon"><i class="fa fa-thermometer-half"></i></p>
|
||||||
|
<p id="temp_{{.Deveui}}" class="title">Lat {{.Lat}}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<a class="block-link" href="/graph.html?deveui={{.Deveui}}&alias={{.Alias}}&property=h">
|
||||||
|
<div>
|
||||||
|
<p class="icon"><i class="fa fa-tint"></i></p>
|
||||||
|
<p id="humidity_{{.Deveui}}" class="title">Lon {{.Lon}}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{ if (lt .DaysUntilDeactivated 0) }}
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="icon"><i class="fa fa-balance-scale"></i></p>
|
||||||
|
<p id="weight_{{.Deveui}}" class="title is-size-2 has-text-weight-bold has-text-danger">Abo ist abgelaufen</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ else }}
|
||||||
|
{{ end }}
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<a class="block-link" href="/graph.html?deveui={{.Deveui}}&alias={{.Alias}}&property=p">
|
||||||
|
<div>
|
||||||
|
<p class="icon"><i class="fa fa-cloud"></i></p>
|
||||||
|
<p id="pressure_{{.Deveui}}" class="title">VBat {{.Vbat}} mV</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<a class="block-link" href="/graph.html?deveui={{.Deveui}}&alias={{.Alias}}&property=vp">
|
||||||
|
<div>
|
||||||
|
<p class="icon"><i class="fa fa-battery-three-quarters"></i></p>
|
||||||
|
<p id="acculevel_{{.Deveui}}" class="title">Fw {{.Fw}}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="has-text-centered">
|
||||||
|
<span class="is-size-6 has-text-centered">Abo aktiv bis</span>
|
||||||
|
<span class="paid_until is-size-6 has-text-centered">{{.ActiveUntil}}</span>
|
||||||
|
</div>
|
||||||
|
{{ if (lt .DaysUntilDeactivated 1095) }}
|
||||||
|
<div class="has-text-centered is-size-7">
|
||||||
|
<p class="abo_add_years_text" id="abo_add_years_text_{.Deveui}}"> </p>
|
||||||
|
</div>
|
||||||
|
<div class="has-text-centered">
|
||||||
|
<p class="abo_add_years" id="abo_add_years_{{.Deveui}}" hidden>0</p>
|
||||||
|
<a class="block-link abo_plus is-unselectable" id="abo_plus_{{.Deveui}}">
|
||||||
|
<span class="icon is-medium">
|
||||||
|
<i class="fa fa-lg fa-plus"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a class="block-link abo_pay is-unselectable" id="abo_{{.Deveui}}">
|
||||||
|
<span class="icon is-medium">
|
||||||
|
<i class="fa fa-2x fa-shopping-cart"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a class="block-link abo_minus is-unselectable" id="abo_minus_{{.Deveui}}">
|
||||||
|
<span class="icon is-medium">
|
||||||
|
<i class="fa fa-lg fa-minus"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/momentjs/latest/moment-with-locales.min.js"></script>
|
||||||
|
|
||||||
|
<script src="static/js/scales.js"></script>
|
||||||
|
|
||||||
|
{{ else }}
|
||||||
|
<h4>Bitte zuerst <a href="login.html">einloggen</a></h4>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{{define "body_content"}}
|
||||||
|
<div class="notification is-danger">
|
||||||
|
<strong>Benutzer existiert nicht!</strong>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{{define "body_content"}}
|
||||||
|
{{ if ne .UserName "" }}
|
||||||
|
Sie sind bereits eingeloggt!
|
||||||
|
{{ else }}
|
||||||
|
<p class="title is-4">Passwort zurücksetzen - warte auf Bestätigung</p>
|
||||||
|
<div class="notification is-info">
|
||||||
|
<p>Bitte checken Sie Ihre Mailbox. Das neue Passwort wird erst gültig, wenn Sie die Bestätigung durchgeführt haben.</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>wo-bisch.ch - LoraWAN Tracker</title>
|
||||||
|
<link rel="stylesheet" href="/wo-bisch-web.css">
|
||||||
|
<link rel="stylesheet" href="/wo-bisch-web-custom.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="section px-4 py-4">
|
||||||
|
<div class="notification is-danger">
|
||||||
|
<strong>Oops, da ist was schiefgegangen!</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
@-webkit-keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@-webkit-keyframes decrement-prev{from{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes decrement-prev{from{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes decrement-next{from{opacity:0;-webkit-transform:translateY(-50px);transform:translateY(-50px)}}@keyframes decrement-next{from{opacity:0;-webkit-transform:translateY(-50px);transform:translateY(-50px)}}@-webkit-keyframes increment-prev{from{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes increment-prev{from{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes increment-next{from{opacity:0;-webkit-transform:translateY(50px);transform:translateY(50px)}}@keyframes increment-next{from{opacity:0;-webkit-transform:translateY(50px);transform:translateY(50px)}}.datetimepicker{transition:width .8s cubic-bezier(1,-.55,.2,1.37),opacity .2s ease,visibility .2s ease,margin .2s ease;display:none;background:#fff;border-radius:2px;max-width:320px;z-index:1;box-shadow:0 2px 3px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1)}.datetimepicker.is-active{display:block}.datetimepicker .datetimepicker-header{display:flex;flex-direction:column;flex:1;text-align:left;position:relative;padding:.5rem 1rem;font-size:1rem;box-shadow:0 .2rem .2rem -.2rem rgba(10,10,10,.1)}.datetimepicker .datetimepicker-header.is-date-only{flex-direction:row}.datetimepicker .datetimepicker-header.is-date-only .datetimepicker-selection-end .datetimepicker-selection-wrapper{justify-content:flex-end!important}.datetimepicker .datetimepicker-header .datetimepicker-selection-details{flex:1}.datetimepicker .datetimepicker-header .datetimepicker-selection-from,.datetimepicker .datetimepicker-header .datetimepicker-selection-to{font-size:.65rem;color:#b5b5b5;text-transform:uppercase;padding:0}.datetimepicker .datetimepicker-header .datetimepicker-selection-end,.datetimepicker .datetimepicker-header .datetimepicker-selection-start{padding:0;display:flex;flex:1;flex-direction:row}.datetimepicker .datetimepicker-header .datetimepicker-selection-end .datetimepicker-selection-wrapper,.datetimepicker .datetimepicker-header .datetimepicker-selection-start .datetimepicker-selection-wrapper{flex-direction:row;display:flex;min-height:2.5rem;flex:1 1 2rem}.datetimepicker .datetimepicker-header .datetimepicker-selection-end .datetimepicker-selection-day,.datetimepicker .datetimepicker-header .datetimepicker-selection-start .datetimepicker-selection-day{flex:0;text-transform:capitalize;font-size:2rem;line-height:2.5rem;margin-right:.5rem;color:#00d1b2;font-weight:100}.datetimepicker .datetimepicker-header .datetimepicker-selection-end .datetimepicker-selection-date,.datetimepicker .datetimepicker-header .datetimepicker-selection-start .datetimepicker-selection-date{font-size:.85rem;line-height:.85rem;justify-content:center;display:flex;flex-direction:column;font-weight:300}.datetimepicker .datetimepicker-header .datetimepicker-selection-end .datetimepicker-selection-month,.datetimepicker .datetimepicker-header .datetimepicker-selection-start .datetimepicker-selection-month{color:#4a4a4a;text-transform:capitalize}.datetimepicker .datetimepicker-header .datetimepicker-selection-end .datetimepicker-selection-weekday,.datetimepicker .datetimepicker-header .datetimepicker-selection-start .datetimepicker-selection-weekday{font-size:.75em;color:#b6b4b6;text-transform:capitalize;text-align:left}.datetimepicker .datetimepicker-header .datetimepicker-selection-end .datetimepicker-selection-hour,.datetimepicker .datetimepicker-header .datetimepicker-selection-start .datetimepicker-selection-hour{font-size:1.5rem;font-weight:100;flex:1;justify-content:center;align-items:center;text-align:center}.datetimepicker .datetimepicker-header .datetimepicker-selection-end,.datetimepicker .datetimepicker-header .datetimepicker-selection-start{align-items:center}.datetimepicker .datetimepicker-header .datetimepicker-selection-end.is-centered .datetimepicker-selection-wrapper,.datetimepicker .datetimepicker-header .datetimepicker-selection-start.is-centered .datetimepicker-selection-wrapper{justify-content:center;align-items:center}.datetimepicker .datetimepicker-header .datetimepicker-selection-time{flex:1;display:flex}.datetimepicker .datetimepicker-header .datetimepicker-selection-time .datetimepicker-selection-time-icon{display:flex;justify-content:center;align-items:center;padding:0 1rem;color:#b5b5b5}.datetimepicker .datepicker-nav{background:#00d1b2;color:#fff;display:flex;align-items:center;justify-content:space-between;padding:.5em;flex-direction:row}.datetimepicker .datepicker-nav .datepicker-nav-month-year{flex-direction:row;displayLine truncated
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1 @@
|
|||||||
|
!function webpackUniversalModuleDefinition(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.bulmaCalendar=t():e.bulmaCalendar=t()}("undefined"!=typeof self?self:this,function(){return function(n){var o={};function __webpack_require__(e){if(o[e])return o[e].exports;var t=o[e]={i:e,l:!1,exports:{}};return n[e].call(t.exports,t,t.exports,__webpack_require__),t.l=!0,t.exports}return __webpack_require__.m=n,__webpack_require__.c=o,__webpack_require__.d=function(e,t,n){__webpack_require__.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},__webpack_require__.n=function(e){var t=e&&e.__esModule?function getDefault(){return e.default}:function getModuleExports(){return e};return __webpack_require__.d(t,"a",t),t},__webpack_require__.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},__webpack_require__.p="",__webpack_require__(__webpack_require__.s=232)}([function(e,t,n){var d=n(120),l=36e5,f=6e4,h=/[T ]/,m=/:/,p=/^(\d{2})$/,v=[/^([+-]\d{2})$/,/^([+-]\d{3})$/,/^([+-]\d{4})$/],g=/^(\d{4})/,b=[/^([+-]\d{4})/,/^([+-]\d{5})/,/^([+-]\d{6})/],_=/^-(\d{2})$/,M=/^-?(\d{3})$/,k=/^-?(\d{2})-?(\d{2})$/,y=/^-?W(\d{2})$/,x=/^-?W(\d{2})-?(\d{1})$/,D=/^(\d{2}([.,]\d*)?)$/,w=/^(\d{2}):?(\d{2}([.,]\d*)?)$/,j=/^(\d{2}):?(\d{2}):?(\d{2}([.,]\d*)?)$/,T=/([Z+-].*)$/,S=/^(Z)$/,P=/^([+-])(\d{2})$/,H=/^([+-])(\d{2}):?(\d{2})$/;function dayOfISOYear(e,t,n){t=t||0,n=n||0;var o=new Date(0);o.setUTCFullYear(e,0,4);var i=7*t+n+1-(o.getUTCDay()||7);return o.setUTCDate(o.getUTCDate()+i),o}e.exports=function parse(e,t){if(d(e))return new Date(e.getTime());if("string"!=typeof e)return new Date(e);var n=(t||{}).additionalDigits;n=null==n?2:Number(n);var o=function splitDateString(e){var t,n={},o=e.split(h);if(t=m.test(o[0])?(n.date=null,o[0]):(n.date=o[0],o[1])){var i=T.exec(t);i?(n.time=t.replace(i[1],""),n.timezone=i[1]):n.time=t}return n}(e),i=function parseYear(e,t){var n,o=v[t],i=b[t];if(n=g.exec(e)||i.exec(e)){var r=n[1];return{year:parseInt(r,10),restDateString:e.slice(r.length)}}if(n=p.exec(e)||o.exec(e)){var a=n[1];return{year:100*parseInt(a,10),restDateString:e.slice(a.length)}}return{year:null}}(o.date,n),r=i.year,a=function parseDate(e,t){if(null===t)return null;var n,o,i,r;if(0===e.length)return(o=new Date(0)).setUTCFullYear(t),o;if(n=_.exec(e))return o=new Date(0),i=parseInt(n[1],10)-1,o.setUTCFullYear(t,i),o;if(n=M.exec(e)){o=new Date(0);var a=parseInt(n[1],10);return o.setUTCFullYear(t,0,a),o}if(n=k.exec(e)){o=new Date(0),i=parseInt(n[1],10)-1;var s=parseInt(n[2],10);return o.setUTCFullYear(t,i,s),o}if(n=y.exec(e))return r=parseInt(n[1],10)-1,dayOfISOYear(t,r);if(n=x.exec(e)){r=parseInt(n[1],10)-1;var u=parseInt(n[2],10)-1;return dayOfISOYear(t,r,u)}return null}(i.restDateString,r);if(a){var s,u=a.getTime(),c=0;return o.time&&(c=function parseTime(e){var t,n,o;if(t=D.exec(e))return(n=parseFloat(t[1].replace(",",".")))%24*l;if(t=w.exec(e))return n=parseInt(t[1],10),o=parseFloat(t[2].replace(",",".")),n%24*l+o*f;if(t=j.exec(e)){n=parseInt(t[1],10),o=parseInt(t[2],10);var i=parseFloat(t[3].replace(",","."));return n%24*l+o*f+1e3*i}return null}(o.time)),s=o.timezone?function parseTimezone(e){var t,n;return(t=S.exec(e))?0:(t=P.exec(e))?(n=60*parseInt(t[2],10),"+"===t[1]?-n:n):(t=H.exec(e))?(n=60*parseInt(t[2],10)+parseInt(t[3],10),"+"===t[1]?-n:n):0}(o.timezone):(s=new Date(u+c).getTimezoneOffset(),new Date(u+c+s*f).getTimezoneOffset()),new Date(u+c+s*f)}return new Date(e)}},function(e,n,o){var i;!function(t){"use strict";var b={},_="en",M={en:{MMMM:["January","February","March","April","May","June","July","August","September","October","November","December"],MMM:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dddd:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],ddd:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dd:["Su","Mo","Tu","We","Th","Fr","Sa"],A:["a.m.","p.m."],formatter:{YYYY:function(e){return("000"+e.getFullYear()).slice(-4)},YY:function(e){return("0"+e.getFullYear()).slice(-2)},Y:function(e){return""+e.getFullYear()},MMMM:function(e){return this.MMMM[e.getMonth()]},MMM:function(e){return this.MMM[e.getMonth()]},MM:function(e){return("0"+(e.getMonth()+1)).slice(-2)},M:function(e){return""+(e.getMonth()+1)},DD:function(e){return("0"+e.getDate()).slice(-2)},D:function(e){return""+e.getDate()},HH:function(e){return("0"+e.getHours()).slice(-2)},H:function(e){return""+e.getHours()},A:function(e){return this.A[11<e.getHours()|0]},hh:function(e){return("0"+(e.getHours()%12||12)).slice(-2)},h:function(e){return""+(e.getHours()%12||12)},mm:function(e){return("0"+e.getMinutes()).slice(-2)},m:function(e){return""+e.getMinutes()},ss:function(e){return("0"+e.getSeconds()).slice(-2)},s:function(e){return""+e.getSeconds()},SSS:function(e){return("00"+e.getMilliseconds()).slice(-3)},SS:function(e){return("0"+(e.getMilliseconds()/10|0)).slice(-2)},S:fuLine truncated
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
.image.is-10by1 img, .image.is-20by3 img {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image.is-10by1 {
|
||||||
|
padding-top: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image.is-20by3 {
|
||||||
|
padding-top: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr.top {
|
||||||
|
margin: 0 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-box {
|
||||||
|
margin: auto;
|
||||||
|
width: 300px;
|
||||||
|
background: rgba(255,255,255,0.05);
|
||||||
|
border: 1px solid rgba(255,255,255,0.3);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-link {
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apexcharts-title-text {
|
||||||
|
font-weight: 700 !important;
|
||||||
|
font-size: 24px !important;
|
||||||
|
font-family: "Rubik", sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apexcharts-legend {
|
||||||
|
font-family: "Rubik", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* * The CSS shown here will not be introduced in the Quickstart guide, but
|
||||||
|
* * shows how you can use CSS to style your Element's container.
|
||||||
|
* */
|
||||||
|
input,
|
||||||
|
.StripeElement {
|
||||||
|
height: 40px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
|
||||||
|
color: #32325d;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
box-shadow: 0 1px 3px 0 #e6ebf1;
|
||||||
|
-webkit-transition: box-shadow 150ms ease;
|
||||||
|
transition: box-shadow 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
.StripeElement--focus {
|
||||||
|
box-shadow: 0 1px 3px 0 #cfd7df;
|
||||||
|
}
|
||||||
|
|
||||||
|
.StripeElement--invalid {
|
||||||
|
border-color: #fa755a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.StripeElement--webkit-autofill {
|
||||||
|
background-color: #fefde5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkboxes input {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkboxes label span {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 928 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Vendored
+16
@@ -0,0 +1,16 @@
|
|||||||
|
/*!
|
||||||
|
* ApexCharts v3.11.3
|
||||||
|
* (c) 2018-2019 Juned Chhipa
|
||||||
|
* Released under the MIT License.
|
||||||
|
*/
|
||||||
|
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).ApexCharts=e()}(this,(function(){"use strict";function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}function e(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){for(var i=0;i<e.length;i++){var a=e[i];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),Object.defineProperty(t,a.key,a)}}function a(t,e,a){return e&&i(t.prototype,e),a&&i(t,a),t}function s(t,e,i){return e in t?Object.defineProperty(t,e,{value:i,enumerable:!0,configurable:!0,writable:!0}):t[e]=i,t}function n(t,e){var i=Object.keys(t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);e&&(a=a.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),i.push.apply(i,a)}return i}function r(t){for(var e=1;e<arguments.length;e++){var i=null!=arguments[e]?arguments[e]:{};e%2?n(Object(i),!0).forEach((function(e){s(t,e,i[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(i)):n(Object(i)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(i,e))}))}return t}function o(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&h(t,e)}function l(t){return(l=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function h(t,e){return(h=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function c(t,e){return!e||"object"!=typeof e&&"function"!=typeof e?function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(t):e}function d(t){return function(t){if(Array.isArray(t)){for(var e=0,i=new Array(t.length);e<t.length;e++)i[e]=t[e];return i}}(t)||function(t){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t))return Array.from(t)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance")}()}var u=function(){function i(){e(this,i)}return a(i,[{key:"shadeRGBColor",value:function(t,e){var i=e.split(","),a=t<0?0:255,s=t<0?-1*t:t,n=parseInt(i[0].slice(4),10),r=parseInt(i[1],10),o=parseInt(i[2],10);return"rgb("+(Math.round((a-n)*s)+n)+","+(Math.round((a-r)*s)+r)+","+(Math.round((a-o)*s)+o)+")"}},{key:"shadeHexColor",value:function(t,e){var i=parseInt(e.slice(1),16),a=t<0?0:255,s=t<0?-1*t:t,n=i>>16,r=i>>8&255,o=255&i;return"#"+(16777216+65536*(Math.round((a-n)*s)+n)+256*(Math.round((a-r)*s)+r)+(Math.round((a-o)*s)+o)).toString(16).slice(1)}},{key:"shadeColor",value:function(t,e){return e.length>7?this.shadeRGBColor(t,e):this.shadeHexColor(t,e)}}],[{key:"bind",value:function(t,e){return function(){return t.apply(e,arguments)}}},{key:"isObject",value:function(e){return e&&"object"===t(e)&&!Array.isArray(e)&&null!=e}},{key:"listToArray",value:function(t){var e,i=[];for(e=0;e<t.length;e++)i[e]=t[e];return i}},{key:"extend",value:function(t,e){var i=this;"function"!=typeof Object.assign&&(Object.assign=function(t){if(null==t)throw new TypeError("Cannot convert undefined or null to object");for(var e=Object(t),i=1;i<arguments.length;i++){var a=arguments[i];if(null!=a)for(var s in a)a.hasOwnProperty(s)&&(e[s]=a[s])}return e});var a=Object.assign({},t);return this.isObject(t)&&this.isObject(e)&&Object.keys(e).forEach((function(n){i.isObject(e[n])&&n in t?a[n]=i.extend(t[n],e[n]):Object.assign(a,s({},n,e[n]))})),a}},{key:"extendArray",value:function(t,e){var a=[];return t.map((function(t){a.push(i.extend(e,t))})),t=a}},{key:"monthMod",value:function(t){return t%12}},{key:"clone",value:function(e){if("[object Array]"===Object.prototype.toString.call(e)){for(var i=[],a=0;a<e.length;a++)i[a]=this.clone(e[a]);return i}if("object"===t(e)){var s={};for(var n in e)e.hasOwnProperty(n)&&(s[n]=this.clone(e[n]));return s}return e}},{key:"log10",value:function(t){return Math.log(t)/Math.LN10}},{key:"roundToBase10",value:function(t){return Math.pow(10,Math.floor(Math.log10(t)))}},{key:"roundToBase",value:function(t,e){return Math.pow(e,Math.floor(Math.log(t)/Math.log(e)))}},{key:"parseNumber",value:function(t){return null===t?t:parseFloat(t)}},{key:"randomId",value:function(){return(Math.random()+1).toString(36).substring(4)}},{key:"noExponents",value:function(t){var e=String(t).split(/[eE]/);if(1===e.length)return e[0];var i="",a=t<0?"-":"",s=e[0].replace(".",""),n=Number(e[1])+1;if(n<0){for(i=a+"0.";n++;)i+="0";return i+s.replace(/^-/,"")}for(n-=s.length;n--;)i+="0";return s+i}},{key:"getDimensions",value:function(t){var e=getComputedStyle(t),i=[],Line truncated
|
||||||
|
/*! svg.filter.js - v2.0.2 - 2016-02-24
|
||||||
|
* https://github.com/wout/svg.filter.js
|
||||||
|
* Copyright (c) 2016 Wout Fierens; Licensed MIT */
|
||||||
|
function(){SVG.Filter=SVG.invent({create:"filter",inherit:SVG.Parent,extend:{source:"SourceGraphic",sourceAlpha:"SourceAlpha",background:"BackgroundImage",backgroundAlpha:"BackgroundAlpha",fill:"FillPaint",stroke:"StrokePaint",autoSetIn:!0,put:function(t,e){return this.add(t,e),!t.attr("in")&&this.autoSetIn&&t.attr("in",this.source),t.attr("result")||t.attr("result",t),t},blend:function(t,e,i){return this.put(new SVG.BlendEffect(t,e,i))},colorMatrix:function(t,e){return this.put(new SVG.ColorMatrixEffect(t,e))},convolveMatrix:function(t){return this.put(new SVG.ConvolveMatrixEffect(t))},componentTransfer:function(t){return this.put(new SVG.ComponentTransferEffect(t))},composite:function(t,e,i){return this.put(new SVG.CompositeEffect(t,e,i))},flood:function(t,e){return this.put(new SVG.FloodEffect(t,e))},offset:function(t,e){return this.put(new SVG.OffsetEffect(t,e))},image:function(t){return this.put(new SVG.ImageEffect(t))},merge:function(){var t=[void 0];for(var e in arguments)t.push(arguments[e]);return this.put(new(SVG.MergeEffect.bind.apply(SVG.MergeEffect,t)))},gaussianBlur:function(t,e){return this.put(new SVG.GaussianBlurEffect(t,e))},morphology:function(t,e){return this.put(new SVG.MorphologyEffect(t,e))},diffuseLighting:function(t,e,i){return this.put(new SVG.DiffuseLightingEffect(t,e,i))},displacementMap:function(t,e,i,a,s){return this.put(new SVG.DisplacementMapEffect(t,e,i,a,s))},specularLighting:function(t,e,i,a){return this.put(new SVG.SpecularLightingEffect(t,e,i,a))},tile:function(){return this.put(new SVG.TileEffect)},turbulence:function(t,e,i,a,s){return this.put(new SVG.TurbulenceEffect(t,e,i,a,s))},toString:function(){return"url(#"+this.attr("id")+")"}}}),SVG.extend(SVG.Defs,{filter:function(t){var e=this.put(new SVG.Filter);return"function"==typeof t&&t.call(e,e),e}}),SVG.extend(SVG.Container,{filter:function(t){return this.defs().filter(t)}}),SVG.extend(SVG.Element,SVG.G,SVG.Nested,{filter:function(t){return this.filterer=t instanceof SVG.Element?t:this.doc().filter(t),this.doc()&&this.filterer.doc()!==this.doc()&&this.doc().defs().add(this.filterer),this.attr("filter",this.filterer),this.filterer},unfilter:function(t){return this.filterer&&!0===t&&this.filterer.remove(),delete this.filterer,this.attr("filter",null)}}),SVG.Effect=SVG.invent({create:function(){this.constructor.call(this)},inherit:SVG.Element,extend:{in:function(t){return null==t?this.parent()&&this.parent().select('[result="'+this.attr("in")+'"]').get(0)||this.attr("in"):this.attr("in",t)},result:function(t){return null==t?this.attr("result"):this.attr("result",t)},toString:function(){return this.result()}}}),SVG.ParentEffect=SVG.invent({create:function(){this.constructor.call(this)},inherit:SVG.Parent,extend:{in:function(t){return null==t?this.parent()&&this.parent().select('[result="'+this.attr("in")+'"]').get(0)||this.attr("in"):this.attr("in",t)},result:function(t){return null==t?this.attr("result"):this.attr("result",t)},toString:function(){return this.result()}}});var t={blend:function(t,e){return this.parent()&&this.parent().blend(this,t,e)},colorMatrix:function(t,e){return this.parent()&&this.parent().colorMatrix(t,e).in(this)},convolveMatrix:function(t){return this.parent()&&this.parent().convolveMatrix(t).in(this)},componentTransfer:function(t){return this.parent()&&this.parent().componentTransfer(t).in(this)},composite:function(t,e){return this.parent()&&this.parent().composite(this,t,e)},flood:function(t,e){return this.parent()&&this.parent().flood(t,e)},offset:function(t,e){return this.parent()&&this.parent().offset(t,e).in(this)},image:function(t){return this.parent()&&this.parent().image(t)},merge:function(){return this.parent()&&this.parent().merge.apply(this.parent(),[this].concat(arguments))},gaussianBlur:function(t,e){return this.parent()&&this.parent().gaussianBlur(t,e).in(this)},morphology:function(t,e){return this.parent()&&this.parent().morphology(t,e).in(this)},diffuseLighting:function(t,e,i){return this.parent()&&this.parent().diffuseLighting(t,e,i).in(this)},displacementMap:function(t,e,i,a){return this.parent()&&this.parent().displacementMap(this,t,e,i,a)},specularLighting:function(t,e,i,a){return this.parent()&&this.parent().specularLighting(t,e,i,a).in(this)},tile:function(){return this.parent()&&this.parent().tile().in(this)},turbulence:function(t,e,i,a,s){return this.parent()&&this.parent().turbulence(t,e,i,a,s).in(this)}};SVG.extend(SVG.Effect,t),SVG.extend(SVG.ParentEffect,t),SVG.ChildEffect=SVG.invent({create:function(){this.constructor.call(this)},inherit:SVG.Element,extend:{in:function(t){this.attr("in",t)}}});var e={blend:function(t,e,i){this.attr({in:t,in2:e,mode:i||"normal"})},colorMatrix:function(t,e){"matrix"==t&&(e=s(e)),this.attr({type:t,values:void 0===e?null:e})},convolveMatrix:function(t){t=s(t),this.attr({order:Math.sqrt(t.split(" ").length),kernelMatrix:t})},composite:function(t,e,i){this.attr({in:t,in2:e,operator:i})},flood:function(t,e){this.attr("flood-color",t),null!Line truncated
|
||||||
|
/*! svg.draggable.js - v2.2.2 - 2019-01-08
|
||||||
|
* https://github.com/svgdotjs/svg.draggable.js
|
||||||
|
* Copyright (c) 2019 Wout Fierens; Licensed MIT */
|
||||||
|
function(){function t(t){t.remember("_draggable",this),this.el=t}t.prototype.init=function(t,e){var i=this;this.constraint=t,this.value=e,this.el.on("mousedown.drag",(function(t){i.start(t)})),this.el.on("touchstart.drag",(function(t){i.start(t)}))},t.prototype.transformPoint=function(t,e){var i=(t=t||window.event).changedTouches&&t.changedTouches[0]||t;return this.p.x=i.clientX-(e||0),this.p.y=i.clientY,this.p.matrixTransform(this.m)},t.prototype.getBBox=function(){var t=this.el.bbox();return this.el instanceof SVG.Nested&&(t=this.el.rbox()),(this.el instanceof SVG.G||this.el instanceof SVG.Use||this.el instanceof SVG.Nested)&&(t.x=this.el.x(),t.y=this.el.y()),t},t.prototype.start=function(t){if("click"!=t.type&&"mousedown"!=t.type&&"mousemove"!=t.type||1==(t.which||t.buttons)){var e=this;if(this.el.fire("beforedrag",{event:t,handler:this}),!this.el.event().defaultPrevented){t.preventDefault(),t.stopPropagation(),this.parent=this.parent||this.el.parent(SVG.Nested)||this.el.parent(SVG.Doc),this.p=this.parent.node.createSVGPoint(),this.m=this.el.node.getScreenCTM().inverse();var i,a=this.getBBox();if(this.el instanceof SVG.Text)switch(i=this.el.node.getComputedTextLength(),this.el.attr("text-anchor")){case"middle":i/=2;break;case"start":i=0}this.startPoints={point:this.transformPoint(t,i),box:a,transform:this.el.transform()},SVG.on(window,"mousemove.drag",(function(t){e.drag(t)})),SVG.on(window,"touchmove.drag",(function(t){e.drag(t)})),SVG.on(window,"mouseup.drag",(function(t){e.end(t)})),SVG.on(window,"touchend.drag",(function(t){e.end(t)})),this.el.fire("dragstart",{event:t,p:this.startPoints.point,m:this.m,handler:this})}}},t.prototype.drag=function(t){var e=this.getBBox(),i=this.transformPoint(t),a=this.startPoints.box.x+i.x-this.startPoints.point.x,s=this.startPoints.box.y+i.y-this.startPoints.point.y,n=this.constraint,r=i.x-this.startPoints.point.x,o=i.y-this.startPoints.point.y;if(this.el.fire("dragmove",{event:t,p:i,m:this.m,handler:this}),this.el.event().defaultPrevented)return i;if("function"==typeof n){var l=n.call(this.el,a,s,this.m);"boolean"==typeof l&&(l={x:l,y:l}),!0===l.x?this.el.x(a):!1!==l.x&&this.el.x(l.x),!0===l.y?this.el.y(s):!1!==l.y&&this.el.y(l.y)}else"object"==typeof n&&(null!=n.minX&&a<n.minX?r=(a=n.minX)-this.startPoints.box.x:null!=n.maxX&&a>n.maxX-e.width&&(r=(a=n.maxX-e.width)-this.startPoints.box.x),null!=n.minY&&s<n.minY?o=(s=n.minY)-this.startPoints.box.y:null!=n.maxY&&s>n.maxY-e.height&&(o=(s=n.maxY-e.height)-this.startPoints.box.y),null!=n.snapToGrid&&(a-=a%n.snapToGrid,s-=s%n.snapToGrid,r-=r%n.snapToGrid,o-=o%n.snapToGrid),this.el instanceof SVG.G?this.el.matrix(this.startPoints.transform).transform({x:r,y:o},!0):this.el.move(a,s));return i},t.prototype.end=function(t){var e=this.drag(t);this.el.fire("dragend",{event:t,p:e,m:this.m,handler:this}),SVG.off(window,"mousemove.drag"),SVG.off(window,"touchmove.drag"),SVG.off(window,"mouseup.drag"),SVG.off(window,"touchend.drag")},SVG.extend(SVG.Element,{draggable:function(e,i){"function"!=typeof e&&"object"!=typeof e||(i=e,e=!0);var a=this.remember("_draggable")||new t(this);return(e=void 0===e||e)?a.init(i||{},e):(this.off("mousedown.drag"),this.off("touchstart.drag")),this}})}.call(void 0),function(){function t(t){this.el=t,t.remember("_selectHandler",this),this.pointSelection={isSelected:!1},this.rectSelection={isSelected:!1}}t.prototype.init=function(t,e){var i=this.el.bbox();for(var a in this.options={},this.el.selectize.defaults)this.options[a]=this.el.selectize.defaults[a],void 0!==e[a]&&(this.options[a]=e[a]);this.parent=this.el.parent(),this.nested=this.nested||this.parent.group(),this.nested.matrix(new SVG.Matrix(this.el).translate(i.x,i.y)),this.options.deepSelect&&-1!==["line","polyline","polygon"].indexOf(this.el.type)?this.selectPoints(t):this.selectRect(t),this.observe(),this.cleanup()},t.prototype.selectPoints=function(t){return this.pointSelection.isSelected=t,this.pointSelection.set?this:(this.pointSelection.set=this.parent.set(),this.drawCircles(),this)},t.prototype.getPointArray=function(){var t=this.el.bbox();return this.el.array().valueOf().map((function(e){return[e[0]-t.x,e[1]-t.y]}))},t.prototype.drawCircles=function(){for(var t=this,e=this.getPointArray(),i=0,a=e.length;i<a;++i){var s=function(e){return function(i){(i=i||window.event).preventDefault?i.preventDefault():i.returnValue=!1,i.stopPropagation();var a=i.pageX||i.touches[0].pageX,s=i.pageY||i.touches[0].pageY;t.el.fire("point",{x:a,y:s,i:e,event:i})}}(i);this.pointSelection.set.add(this.nested.circle(this.options.radius).center(e[i][0],e[i][1]).addClass(this.options.classPoints).addClass(this.options.classPoints+"_point").on("touchstart",s).on("mousedown",s))}},t.prototype.updatePointSelection=function(){var t=this.getPointArray();this.pointSelection.set.each((function(e){this.cx()===t[e][0]&&this.cy()===t[e][1]||this.center(t[e][0],t[e][1])}))},t.prototype.updateRectSelection=function(){var t=this.el.bbox();this.rectSelection.set.get(0).attr(Line truncated
|
||||||
|
/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */
|
||||||
|
"document"in self&&("classList"in document.createElement("_")&&(!document.createElementNS||"classList"in document.createElementNS("http://www.w3.org/2000/svg","g"))||function(t){if("Element"in t){var e=t.Element.prototype,i=Object,a=String.prototype.trim||function(){return this.replace(/^\s+|\s+$/g,"")},s=Array.prototype.indexOf||function(t){for(var e=0,i=this.length;e<i;e++)if(e in this&&this[e]===t)return e;return-1},n=function(t,e){this.name=t,this.code=DOMException[t],this.message=e},r=function(t,e){if(""===e)throw new n("SYNTAX_ERR","The token must not be empty.");if(/\s/.test(e))throw new n("INVALID_CHARACTER_ERR","The token must not contain space characters.");return s.call(t,e)},o=function(t){for(var e=a.call(t.getAttribute("class")||""),i=e?e.split(/\s+/):[],s=0,n=i.length;s<n;s++)this.push(i[s]);this._updateClassName=function(){t.setAttribute("class",this.toString())}},l=o.prototype=[],h=function(){return new o(this)};if(n.prototype=Error.prototype,l.item=function(t){return this[t]||null},l.contains=function(t){return~r(this,t+"")},l.add=function(){var t,e=arguments,i=0,a=e.length,s=!1;do{t=e[i]+"",~r(this,t)||(this.push(t),s=!0)}while(++i<a);s&&this._updateClassName()},l.remove=function(){var t,e,i=arguments,a=0,s=i.length,n=!1;do{for(t=i[a]+"",e=r(this,t);~e;)this.splice(e,1),n=!0,e=r(this,t)}while(++a<s);n&&this._updateClassName()},l.toggle=function(t,e){var i=this.contains(t),a=i?!0!==e&&"remove":!1!==e&&"add";return a&&this[a](t),!0===e||!1===e?e:!i},l.replace=function(t,e){var i=r(t+"");~i&&(this.splice(i,1,e),this._updateClassName())},l.toString=function(){return this.join(" ")},i.defineProperty){var c={get:h,enumerable:!0,configurable:!0};try{i.defineProperty(e,"classList",c)}catch(t){void 0!==t.number&&-2146823252!==t.number||(c.enumerable=!1,i.defineProperty(e,"classList",c))}}else i.prototype.__defineGetter__&&e.__defineGetter__("classList",h)}}(self),function(){var t=document.createElement("_");if(t.classList.add("c1","c2"),!t.classList.contains("c2")){var e=function(t){var e=DOMTokenList.prototype[t];DOMTokenList.prototype[t]=function(t){var i,a=arguments.length;for(i=0;i<a;i++)t=arguments[i],e.call(this,t)}};e("add"),e("remove")}if(t.classList.toggle("c3",!1),t.classList.contains("c3")){var i=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(t,e){return 1 in arguments&&!this.contains(t)==!e?e:i.call(this,t)}}"replace"in document.createElement("_").classList||(DOMTokenList.prototype.replace=function(t,e){var i=this.toString().split(" "),a=i.indexOf(t+"");~a&&(i=i.slice(a),this.remove.apply(this,i),this.add(e),this.add.apply(this,i.slice(1)))}),t=null}()),function(){function t(t){var e=t.__resizeTriggers__,i=e.firstElementChild,a=e.lastElementChild,s=i.firstElementChild;a.scrollLeft=a.scrollWidth,a.scrollTop=a.scrollHeight,s.style.width=i.offsetWidth+1+"px",s.style.height=i.offsetHeight+1+"px",i.scrollLeft=i.scrollWidth,i.scrollTop=i.scrollHeight}function e(e){var i=this;t(this),this.__resizeRAF__&&n(this.__resizeRAF__),this.__resizeRAF__=s((function(){(function(t){return t.offsetWidth!=t.__resizeLast__.width||t.offsetHeight!=t.__resizeLast__.height})(i)&&(i.__resizeLast__.width=i.offsetWidth,i.__resizeLast__.height=i.offsetHeight,i.__resizeListeners__.forEach((function(t){t.call(e)})))}))}var i,a,s=(i=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||function(t){return window.setTimeout(t,20)},function(t){return i(t)}),n=(a=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.clearTimeout,function(t){return a(t)}),r=!1,o="animationstart",l="Webkit Moz O ms".split(" "),h="webkitAnimationStart animationstart oAnimationStart MSAnimationStart".split(" "),c=document.createElement("fakeelement");if(void 0!==c.style.animationName&&(r=!0),!1===r)for(var d=0;d<l.length;d++)if(void 0!==c.style[l[d]+"AnimationName"]){o=h[d];break}window.addResizeListener=function(i,a){i.__resizeTriggers__||("static"==getComputedStyle(i).position&&(i.style.position="relative"),i.__resizeLast__={},i.__resizeListeners__=[],(i.__resizeTriggers__=document.createElement("div")).className="resize-triggers",i.__resizeTriggers__.innerHTML='<div class="expand-trigger"><div></div></div><div class="contract-trigger"></div>',i.appendChild(i.__resizeTriggers__),t(i),i.addEventListener("scroll",e,!0),o&&i.__resizeTriggers__.addEventListener(o,(function(e){"resizeanim"==e.animationName&&t(i)}))),i.__resizeListeners__.push(a)},window.removeResizeListener=function(t,i){t&&(t.__resizeListeners__.splice(t.__resizeListeners__.indexOf(i),1),t.__resizeListeners__.length||(t.removeEventListener("scroll",e),t.__resizeTriggers__=!t.removeChild(t.__resizeTriggers__)))}}(),window.Apex={},function(){function i(t,a){e(this,i),this.opts=a,this.ctx=this,this.w=new q(a).init(),this.el=t,this.w.globals.cuid=u.randomId(),this.w.globals.chartID=this.w.config.chart.id?this.w.config.chart.id:this.w.globals.cuid,this.eventList=["clicLine truncated
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
// Set your publishable key: remember to change this to your live publishable key in production
|
||||||
|
// See your keys here: https://dashboard.stripe.com/account/apikeys
|
||||||
|
var stripe = Stripe('pk_test_YkSGqH3Tk9WKK9HrlY63GhAg');
|
||||||
|
var elements = stripe.elements();
|
||||||
|
|
||||||
|
// Set up Stripe.js and Elements to use in checkout form
|
||||||
|
var style = {
|
||||||
|
base: {
|
||||||
|
color: "#32325d",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var card = elements.create("card", { style: style });
|
||||||
|
card.mount("#card-element");
|
||||||
|
|
||||||
|
|
||||||
|
card.addEventListener('change', ({error}) => {
|
||||||
|
const displayError = document.getElementById('card-errors');
|
||||||
|
if (error) {
|
||||||
|
displayError.textContent = error.message;
|
||||||
|
} else {
|
||||||
|
displayError.textContent = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var submitButton = document.getElementById('submit');
|
||||||
|
|
||||||
|
function GetClientSecret() {
|
||||||
|
var result = "";
|
||||||
|
$.ajax({
|
||||||
|
async: false,
|
||||||
|
url: "getstripepaymentintent",
|
||||||
|
type: "get", //send it through get method
|
||||||
|
dataType: "json",
|
||||||
|
data: {
|
||||||
|
charge_data: $("#charge_data").html()
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
console.log('pay success');
|
||||||
|
console.log(response.stripesessionid);
|
||||||
|
console.log('rc: '+response.rc);
|
||||||
|
if (response.rc == 0) {
|
||||||
|
result = response.stripeclientsecret;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr) {
|
||||||
|
console.log('getstripepaymentintent error');
|
||||||
|
//Do Something to handle error
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
submitButton.addEventListener('click', function(ev) {
|
||||||
|
var clientSecret = GetClientSecret();
|
||||||
|
stripe.confirmCardPayment(clientSecret, {
|
||||||
|
payment_method: {
|
||||||
|
card: card,
|
||||||
|
billing_details: {
|
||||||
|
name: 'Jenny Rosen'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).then(function(result) {
|
||||||
|
if (result.error) {
|
||||||
|
// Show error to your customer (e.g., insufficient funds)
|
||||||
|
console.log(result.error.message);
|
||||||
|
} else {
|
||||||
|
// The payment has been processed!
|
||||||
|
if (result.paymentIntent.status === 'succeeded') {
|
||||||
|
// Show a success message to your customer
|
||||||
|
// There's a risk of the customer closing the window before callback
|
||||||
|
// execution. Set up a webhook or plugin to listen for the
|
||||||
|
// payment_intent.succeeded event that handles any business critical
|
||||||
|
// post-payment actions.
|
||||||
|
alert("Payment succeeded!!!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/*!
|
||||||
|
* Font Awesome Free 5.1.0 by @fontawesome - https://fontawesome.com
|
||||||
|
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||||
|
*/
|
||||||
|
!function(){"use strict";var c={};try{"undefined"!=typeof window&&(c=window)}catch(c){}var l=(c.navigator||{}).userAgent,h=void 0===l?"":l,z=c,v=(~h.indexOf("MSIE")||h.indexOf("Trident/"),"___FONT_AWESOME___"),m=function(){try{return!0}catch(c){return!1}}(),s=[1,2,3,4,5,6,7,8,9,10],e=s.concat([11,12,13,14,15,16,17,18,19,20]);["xs","sm","lg","fw","ul","li","border","pull-left","pull-right","spin","pulse","rotate-90","rotate-180","rotate-270","flip-horizontal","flip-vertical","stack","stack-1x","stack-2x","inverse","layers","layers-text","layers-counter"].concat(s.map(function(c){return c+"x"})).concat(e.map(function(c){return"w-"+c}));var a=z||{};a[v]||(a[v]={}),a[v].styles||(a[v].styles={}),a[v].hooks||(a[v].hooks={}),a[v].shims||(a[v].shims=[]);var t=a[v],M=Object.assign||function(c){for(var l=1;l<arguments.length;l++){var h=arguments[l];for(var z in h)Object.prototype.hasOwnProperty.call(h,z)&&(c[z]=h[z])}return c};var f={"500px":[448,512,[],"f26e","M103.3 344.3c-6.5-14.2-6.9-18.3 7.4-23.1 25.6-8 8 9.2 43.2 49.2h.3v-93.9c1.2-50.2 44-92.2 97.7-92.2 53.9 0 97.7 43.5 97.7 96.8 0 63.4-60.8 113.2-128.5 93.3-10.5-4.2-2.1-31.7 8.5-28.6 53 0 89.4-10.1 89.4-64.4 0-61-77.1-89.6-116.9-44.6-23.5 26.4-17.6 42.1-17.6 157.6 50.7 31 118.3 22 160.4-20.1 24.8-24.8 38.5-58 38.5-93 0-35.2-13.8-68.2-38.8-93.3-24.8-24.8-57.8-38.5-93.3-38.5s-68.8 13.8-93.5 38.5c-.3.3-16 16.5-21.2 23.9l-.5.6c-3.3 4.7-6.3 9.1-20.1 6.1-6.9-1.7-14.3-5.8-14.3-11.8V20c0-5 3.9-10.5 10.5-10.5h241.3c8.3 0 8.3 11.6 8.3 15.1 0 3.9 0 15.1-8.3 15.1H130.3v132.9h.3c104.2-109.8 282.8-36 282.8 108.9 0 178.1-244.8 220.3-310.1 62.8zm63.3-260.8c-.5 4.2 4.6 24.5 14.6 20.6C306 56.6 384 144.5 390.6 144.5c4.8 0 22.8-15.3 14.3-22.8-93.2-89-234.5-57-238.3-38.2zM393 414.7C283 524.6 94 475.5 61 310.5c0-12.2-30.4-7.4-28.9 3.3 24 173.4 246 256.9 381.6 121.3 6.9-7.8-12.6-28.4-20.7-20.4zM213.6 306.6c0 4 4.3 7.3 5.5 8.5 3 3 6.1 4.4 8.5 4.4 3.8 0 2.6.2 22.3-19.5 19.6 19.3 19.1 19.5 22.3 19.5 5.4 0 18.5-10.4 10.7-18.2L265.6 284l18.2-18.2c6.3-6.8-10.1-21.8-16.2-15.7L249.7 268c-18.6-18.8-18.4-19.5-21.5-19.5-5 0-18 11.7-12.4 17.3L234 284c-18.1 17.9-20.4 19.2-20.4 22.6z"],"accessible-icon":[448,512,[],"f368","M423.9 255.8L411 413.1c-3.3 40.7-63.9 35.1-60.6-4.9l10-122.5-41.1 2.3c10.1 20.7 15.8 43.9 15.8 68.5 0 41.2-16.1 78.7-42.3 106.5l-39.3-39.3c57.9-63.7 13.1-167.2-74-167.2-25.9 0-49.5 9.9-67.2 26L73 243.2c22-20.7 50.1-35.1 81.4-40.2l75.3-85.7-42.6-24.8-51.6 46c-30 26.8-70.6-18.5-40.5-45.4l68-60.7c9.8-8.8 24.1-10.2 35.5-3.6 0 0 139.3 80.9 139.5 81.1 16.2 10.1 20.7 36 6.1 52.6L285.7 229l106.1-5.9c18.5-1.1 33.6 14.4 32.1 32.7zm-64.9-154c28.1 0 50.9-22.8 50.9-50.9C409.9 22.8 387.1 0 359 0c-28.1 0-50.9 22.8-50.9 50.9 0 28.1 22.8 50.9 50.9 50.9zM179.6 456.5c-80.6 0-127.4-90.6-82.7-156.1l-39.7-39.7C36.4 287 24 320.3 24 356.4c0 130.7 150.7 201.4 251.4 122.5l-39.7-39.7c-16 10.9-35.3 17.3-56.1 17.3z"],accusoft:[640,512,[],"f369","M482.2 372.1C476.5 365.2 250 75 242.3 65.5c-13.7-17.2 0-16.8 19.2-16.9 9.7-.1 106.3-.6 116.5-.6 24.1-.1 28.7.6 38.4 12.8 2.1 2.7 205.1 245.8 207.2 248.3 5.5 6.7 15.2 19.1 7.2 23.4-2.4 1.3-114.6 47.7-117.8 48.9-10.1 4-17.5 6.8-30.8-9.3m114.7-5.6s-115 50.4-117.5 51.6c-16 7.3-26.9-3.2-36.7-14.6l-57.1-74c-5.4-.9-60.4-9.6-65.3-9.3-3.1.2-9.6.8-14.4 2.9-4.9 2.1-145.2 52.8-150.2 54.7-5.1 2-11.4 3.6-11.1 7.6.2 2.5 2 2.6 4.6 3.5 2.7.8 300.9 67.6 308 69.1 15.6 3.3 38.5 10.5 53.6 1.7 2.1-1.2 123.8-76.4 125.8-77.8 5.4-4 4.3-6.8-1.7-8.2-2.3-.3-24.6-4.7-38-7.2m-326-181.3s-12 1.6-25 15.1c-9 9.3-242.1 239.1-243.4 240.9-7 10 1.6 6.8 15.7 1.7.8 0 114.5-36.6 114.5-36.6.5-.6-.1-.1.6-.6-.4-5.1-.8-26.2-1-27.7-.6-5.2 2.2-6.9 7-8.9l92.6-33.8c.6-.8 88.5-81.7 90.2-83.3v-1l-51.2-65.8"],adn:[496,512,[],"f170","M248 167.5l64.9 98.8H183.1l64.9-98.8zM496 256c0 136.9-111.1 248-248 248S0 392.9 0 256 111.1 8 248 8s248 111.1 248 248zm-99.8 82.7L248 115.5 99.8 338.7h30.4l33.6-51.7h168.6l33.6 51.7h30.2z"],adversal:[512,512,[],"f36a","M482.1 32H28.7C5.8 32 0 37.9 0 60.9v390.2C0 474.4 5.8 480 28.7 480h453.4c24.4 0 29.9-5.2 29.9-29.7V62.2c0-24.6-5.4-30.2-29.9-30.2zM178.4 220.3c-27.5-20.2-72.1-8.7-84.2 23.4-4.3 11.1-9.3 9.5-17.5 8.3-9.7-1.5-17.2-3.2-22.5-5.5-28.8-11.4 8.6-55.3 24.9-64.3 41.1-21.4 83.4-22.2 125.3-4.8 40.9 16.8 34.5 59.2 34.5 128.5 2.7 25.8-4.3 58.3 9.3 88.8 1.9 4.4.4 7.9-2.7 10.7-8.4 6.7-39.3 2.2-46.6-7.4-1.9-2.2-1.8-3.6-3.9-6.2-3.6-3.9-7.3-2.2-11.9 1-57.4 36.4-140.3 21.4-147-43.3-3.1-29.3 12.4-57.1 39.6-71 38.2-19.5 112.2-11.8 114-30.9 1.1-10.2-1.9-20.1-11.3-27.3zm286.7 222c0 15.1-11.1 9.9-17.8 9.9H52.4c-7.4 0-18.2 4.8-17.8-10.7.4-13.9 10.5-9.1 17.1-9.1 132.3-.4 264.5-.4 396.8 0 6.8 0 16.6-4.4 16.6 9.9zm3.8-340.5v291c0 5.7-.7 13.9-8.1 13.9-12.4-.4-27.5 7.1-36.1-5.6-5.8-8.7-7.8-4-12.4-1.2-53.4 29.7-128.1 7.1-144.4-85.2-6.1-33.4-.7-67.1 15.7-100 11.8-23.9 56.9-76.1 136.1-30.5v-71c0-26.2-.1-26.2 26-26.2 3.1 0 6.6.4 9.7 0 10.1-.8 13.6 4.4 13.6 14.3-.1.2-.1.3-.1.5zm-51.5 232.3c-19.5 47.6-72.9 43.3-90 5.2-15.1-33.3-15.5-68.2.4-101.5 16.3-34.1 59.7-35.7 81.5-4.8 20.6 28.8 14.9 84.6 8.1 101.1zm-2Line truncated
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/*!
|
||||||
|
* Font Awesome Free 5.1.0 by @fontawesome - https://fontawesome.com
|
||||||
|
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||||
|
*/
|
||||||
|
!function(){"use strict";var c={};try{"undefined"!=typeof window&&(c=window)}catch(c){}var l=(c.navigator||{}).userAgent,h=void 0===l?"":l,z=c,v=(~h.indexOf("MSIE")||h.indexOf("Trident/"),"___FONT_AWESOME___"),m=function(){try{return!0}catch(c){return!1}}(),s=[1,2,3,4,5,6,7,8,9,10],e=s.concat([11,12,13,14,15,16,17,18,19,20]);["xs","sm","lg","fw","ul","li","border","pull-left","pull-right","spin","pulse","rotate-90","rotate-180","rotate-270","flip-horizontal","flip-vertical","stack","stack-1x","stack-2x","inverse","layers","layers-text","layers-counter"].concat(s.map(function(c){return c+"x"})).concat(e.map(function(c){return"w-"+c}));var a=z||{};a[v]||(a[v]={}),a[v].styles||(a[v].styles={}),a[v].hooks||(a[v].hooks={}),a[v].shims||(a[v].shims=[]);var t=a[v],M=Object.assign||function(c){for(var l=1;l<arguments.length;l++){var h=arguments[l];for(var z in h)Object.prototype.hasOwnProperty.call(h,z)&&(c[z]=h[z])}return c};var f={"500px":[448,512,[],"f26e","M103.3 344.3c-6.5-14.2-6.9-18.3 7.4-23.1 25.6-8 8 9.2 43.2 49.2h.3v-93.9c1.2-50.2 44-92.2 97.7-92.2 53.9 0 97.7 43.5 97.7 96.8 0 63.4-60.8 113.2-128.5 93.3-10.5-4.2-2.1-31.7 8.5-28.6 53 0 89.4-10.1 89.4-64.4 0-61-77.1-89.6-116.9-44.6-23.5 26.4-17.6 42.1-17.6 157.6 50.7 31 118.3 22 160.4-20.1 24.8-24.8 38.5-58 38.5-93 0-35.2-13.8-68.2-38.8-93.3-24.8-24.8-57.8-38.5-93.3-38.5s-68.8 13.8-93.5 38.5c-.3.3-16 16.5-21.2 23.9l-.5.6c-3.3 4.7-6.3 9.1-20.1 6.1-6.9-1.7-14.3-5.8-14.3-11.8V20c0-5 3.9-10.5 10.5-10.5h241.3c8.3 0 8.3 11.6 8.3 15.1 0 3.9 0 15.1-8.3 15.1H130.3v132.9h.3c104.2-109.8 282.8-36 282.8 108.9 0 178.1-244.8 220.3-310.1 62.8zm63.3-260.8c-.5 4.2 4.6 24.5 14.6 20.6C306 56.6 384 144.5 390.6 144.5c4.8 0 22.8-15.3 14.3-22.8-93.2-89-234.5-57-238.3-38.2zM393 414.7C283 524.6 94 475.5 61 310.5c0-12.2-30.4-7.4-28.9 3.3 24 173.4 246 256.9 381.6 121.3 6.9-7.8-12.6-28.4-20.7-20.4zM213.6 306.6c0 4 4.3 7.3 5.5 8.5 3 3 6.1 4.4 8.5 4.4 3.8 0 2.6.2 22.3-19.5 19.6 19.3 19.1 19.5 22.3 19.5 5.4 0 18.5-10.4 10.7-18.2L265.6 284l18.2-18.2c6.3-6.8-10.1-21.8-16.2-15.7L249.7 268c-18.6-18.8-18.4-19.5-21.5-19.5-5 0-18 11.7-12.4 17.3L234 284c-18.1 17.9-20.4 19.2-20.4 22.6z"],"accessible-icon":[448,512,[],"f368","M423.9 255.8L411 413.1c-3.3 40.7-63.9 35.1-60.6-4.9l10-122.5-41.1 2.3c10.1 20.7 15.8 43.9 15.8 68.5 0 41.2-16.1 78.7-42.3 106.5l-39.3-39.3c57.9-63.7 13.1-167.2-74-167.2-25.9 0-49.5 9.9-67.2 26L73 243.2c22-20.7 50.1-35.1 81.4-40.2l75.3-85.7-42.6-24.8-51.6 46c-30 26.8-70.6-18.5-40.5-45.4l68-60.7c9.8-8.8 24.1-10.2 35.5-3.6 0 0 139.3 80.9 139.5 81.1 16.2 10.1 20.7 36 6.1 52.6L285.7 229l106.1-5.9c18.5-1.1 33.6 14.4 32.1 32.7zm-64.9-154c28.1 0 50.9-22.8 50.9-50.9C409.9 22.8 387.1 0 359 0c-28.1 0-50.9 22.8-50.9 50.9 0 28.1 22.8 50.9 50.9 50.9zM179.6 456.5c-80.6 0-127.4-90.6-82.7-156.1l-39.7-39.7C36.4 287 24 320.3 24 356.4c0 130.7 150.7 201.4 251.4 122.5l-39.7-39.7c-16 10.9-35.3 17.3-56.1 17.3z"],accusoft:[640,512,[],"f369","M482.2 372.1C476.5 365.2 250 75 242.3 65.5c-13.7-17.2 0-16.8 19.2-16.9 9.7-.1 106.3-.6 116.5-.6 24.1-.1 28.7.6 38.4 12.8 2.1 2.7 205.1 245.8 207.2 248.3 5.5 6.7 15.2 19.1 7.2 23.4-2.4 1.3-114.6 47.7-117.8 48.9-10.1 4-17.5 6.8-30.8-9.3m114.7-5.6s-115 50.4-117.5 51.6c-16 7.3-26.9-3.2-36.7-14.6l-57.1-74c-5.4-.9-60.4-9.6-65.3-9.3-3.1.2-9.6.8-14.4 2.9-4.9 2.1-145.2 52.8-150.2 54.7-5.1 2-11.4 3.6-11.1 7.6.2 2.5 2 2.6 4.6 3.5 2.7.8 300.9 67.6 308 69.1 15.6 3.3 38.5 10.5 53.6 1.7 2.1-1.2 123.8-76.4 125.8-77.8 5.4-4 4.3-6.8-1.7-8.2-2.3-.3-24.6-4.7-38-7.2m-326-181.3s-12 1.6-25 15.1c-9 9.3-242.1 239.1-243.4 240.9-7 10 1.6 6.8 15.7 1.7.8 0 114.5-36.6 114.5-36.6.5-.6-.1-.1.6-.6-.4-5.1-.8-26.2-1-27.7-.6-5.2 2.2-6.9 7-8.9l92.6-33.8c.6-.8 88.5-81.7 90.2-83.3v-1l-51.2-65.8"],adn:[496,512,[],"f170","M248 167.5l64.9 98.8H183.1l64.9-98.8zM496 256c0 136.9-111.1 248-248 248S0 392.9 0 256 111.1 8 248 8s248 111.1 248 248zm-99.8 82.7L248 115.5 99.8 338.7h30.4l33.6-51.7h168.6l33.6 51.7h30.2z"],adversal:[512,512,[],"f36a","M482.1 32H28.7C5.8 32 0 37.9 0 60.9v390.2C0 474.4 5.8 480 28.7 480h453.4c24.4 0 29.9-5.2 29.9-29.7V62.2c0-24.6-5.4-30.2-29.9-30.2zM178.4 220.3c-27.5-20.2-72.1-8.7-84.2 23.4-4.3 11.1-9.3 9.5-17.5 8.3-9.7-1.5-17.2-3.2-22.5-5.5-28.8-11.4 8.6-55.3 24.9-64.3 41.1-21.4 83.4-22.2 125.3-4.8 40.9 16.8 34.5 59.2 34.5 128.5 2.7 25.8-4.3 58.3 9.3 88.8 1.9 4.4.4 7.9-2.7 10.7-8.4 6.7-39.3 2.2-46.6-7.4-1.9-2.2-1.8-3.6-3.9-6.2-3.6-3.9-7.3-2.2-11.9 1-57.4 36.4-140.3 21.4-147-43.3-3.1-29.3 12.4-57.1 39.6-71 38.2-19.5 112.2-11.8 114-30.9 1.1-10.2-1.9-20.1-11.3-27.3zm286.7 222c0 15.1-11.1 9.9-17.8 9.9H52.4c-7.4 0-18.2 4.8-17.8-10.7.4-13.9 10.5-9.1 17.1-9.1 132.3-.4 264.5-.4 396.8 0 6.8 0 16.6-4.4 16.6 9.9zm3.8-340.5v291c0 5.7-.7 13.9-8.1 13.9-12.4-.4-27.5 7.1-36.1-5.6-5.8-8.7-7.8-4-12.4-1.2-53.4 29.7-128.1 7.1-144.4-85.2-6.1-33.4-.7-67.1 15.7-100 11.8-23.9 56.9-76.1 136.1-30.5v-71c0-26.2-.1-26.2 26-26.2 3.1 0 6.6.4 9.7 0 10.1-.8 13.6 4.4 13.6 14.3-.1.2-.1.3-.1.5zm-51.5 232.3c-19.5 47.6-72.9 43.3-90 5.2-15.1-33.3-15.5-68.2.4-101.5 16.3-34.1 59.7-35.7 81.5-4.8 20.6 28.8 14.9 84.6 8.1 101.1zm-2Line truncated
|
||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,105 @@
|
|||||||
|
$(function() {
|
||||||
|
|
||||||
|
var start = moment({hour: 0});
|
||||||
|
var end = moment();
|
||||||
|
var s_start = start.utc().format('YYYY-MM-DDTHH:mm:ss[Z]');
|
||||||
|
var s_end = end.utc().format('YYYY-MM-DDTHH:mm:ss[Z]');
|
||||||
|
|
||||||
|
console.log("XXX: "+start.format());
|
||||||
|
|
||||||
|
function change_property(new_property) {
|
||||||
|
if (new_property != property) {
|
||||||
|
property = new_property;
|
||||||
|
|
||||||
|
if (history.pushState) {
|
||||||
|
var newurl = window.location.href.replace(/property=[a-z]+/,'property='+new_property);
|
||||||
|
window.history.pushState({path:newurl},'',newurl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cb(start, end) {
|
||||||
|
$('#reportrange span').html(start.locale('de').format('D. MMM YYYY') + ' - ' + end.locale('de').format('D. MMM YYYY'));
|
||||||
|
console.log("A new date selection was made: " + start.format() + ' to ' + end.format());
|
||||||
|
s_start = start.utc().format('YYYY-MM-DDTHH:mm:ss[Z]');
|
||||||
|
s_end = end.utc().format('YYYY-MM-DDTHH:mm:ss[Z]');
|
||||||
|
console.log("Start: " + s_start + ' End: ' + s_end + ' Property: '+ property);
|
||||||
|
drawGraph(deveui, alias, property, s_start,s_end, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#btn_w').on("click", function() {
|
||||||
|
change_property('w');
|
||||||
|
drawGraph(deveui, alias, property, s_start, s_end, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#btn_t').on("click", function() {
|
||||||
|
change_property('t');
|
||||||
|
drawGraph(deveui, alias, property, s_start, s_end, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#btn_h').on("click", function() {
|
||||||
|
change_property('h');
|
||||||
|
drawGraph(deveui, alias, property, s_start, s_end, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#btn_p').on("click", function() {
|
||||||
|
change_property('p');
|
||||||
|
drawGraph(deveui, alias, property, s_start, s_end, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#btn_vp').on("click", function() {
|
||||||
|
change_property('vp');
|
||||||
|
drawGraph(deveui, alias, property, s_start, s_end, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#reportrange').daterangepicker({
|
||||||
|
startDate: start.local(),
|
||||||
|
endDate: end.local(),
|
||||||
|
regional: [ "de" ],
|
||||||
|
"locale": {
|
||||||
|
format: 'DD.MM.YYYY',
|
||||||
|
"separator": " - ",
|
||||||
|
"applyLabel": "Anwenden",
|
||||||
|
"cancelLabel": "Abbrechen",
|
||||||
|
"fromLabel": "Von",
|
||||||
|
"toLabel": "Bis",
|
||||||
|
customRangeLabel: 'Benutzerdefiniert',
|
||||||
|
"weekLabel": "W",
|
||||||
|
"daysOfWeek": [
|
||||||
|
"So",
|
||||||
|
"Mo",
|
||||||
|
"Di",
|
||||||
|
"Mi",
|
||||||
|
"Do",
|
||||||
|
"Fr",
|
||||||
|
"Sa"
|
||||||
|
],
|
||||||
|
"monthNames": [
|
||||||
|
"Jan",
|
||||||
|
"Feb",
|
||||||
|
"Mär",
|
||||||
|
"Apr",
|
||||||
|
"Mai",
|
||||||
|
"Jun",
|
||||||
|
"Jul",
|
||||||
|
"Aug",
|
||||||
|
"Sep",
|
||||||
|
"Okt",
|
||||||
|
"Nov",
|
||||||
|
"Dez"
|
||||||
|
],
|
||||||
|
"firstDay": 1
|
||||||
|
},
|
||||||
|
ranges: {
|
||||||
|
'Heute': [moment(), moment()],
|
||||||
|
'Gestern': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
|
||||||
|
'Letzte 7 Tage': [moment().subtract(6, 'days'), moment()],
|
||||||
|
'Letzte 30 Tage': [moment().subtract(29, 'days'), moment()],
|
||||||
|
'Diesen Monat': [moment().startOf('month'), moment().endOf('month')],
|
||||||
|
'Letzten Monat': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
|
||||||
|
}
|
||||||
|
}, cb);
|
||||||
|
|
||||||
|
//cb(start, end);
|
||||||
|
|
||||||
|
});
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
/*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */
|
||||||
|
!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var b="3.3.1",w=function(e,t){return new w.fn.init(e,t)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:"3.3.1",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:s,sort:n.sort,splice:n.splice},w.extend=w.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||g(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)n=a[t],a!==(r=e[t])&&(l&&r&&(w.isPlainObject(r)||(i=Array.isArray(r)))?(i?(i=!1,o=n&&Array.isArray(n)?n:[]):o=n&&w.isPlainObject(n)?n:{},a[t]=w.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},w.extend({expando:"jQuery"+("3.3.1"+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==c.call(e))&&(!(t=i(e))||"function"==typeof(n=f.call(t,"constructor")&&t.constructor)&&p.call(n)===d)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e){m(e)},each:function(e,t){var n,r=0;if(C(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},trim:function(e){return null==e?"":(e+"").replace(T,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(C(Object(e))?w.merge(n,"string"==typeof e?[e]:e):s.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:u.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r,i=[],o=0,a=e.length,s=!n;o<a;o++)(r=!t(e[o],o))!==s&&i.push(e[o]);return i},map:function(e,t,n){var r,i,o=0,s=[];if(C(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&s.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&s.push(i);return a.apply([],s)},guid:1,support:h}),"function"==typeof Symbol&&(w.fn[Symbol.iterator]=n[Symbol.iterator]),w.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function C(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!g(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},P="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",I="\\["+M+"*("+R+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+R+"))|)"+M+"*\\]",W=":("+R+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+I+")*)|.*)\\)|)",$=new RegExp(M+"+","g"),B=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),F=new RegExp("^"+M+"*,"+M+"*"),_=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)Line truncated
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
function validate(what, text) {
|
||||||
|
if (what == 'alias') {
|
||||||
|
var re = /^[a-zA-Z0-9 ]{1,25}$/;
|
||||||
|
} else if (what == 'smsnumber') {
|
||||||
|
var re = /^\+[0-9]{11,11}$/;
|
||||||
|
}
|
||||||
|
return re.test(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A $( document ).ready() block.
|
||||||
|
$( document ).ready(function() {
|
||||||
|
$(".show-modal").click(function() {
|
||||||
|
$("#alias_exclamation").hide();
|
||||||
|
$("#smsnumber_exclamation").hide();
|
||||||
|
var alias = $(this).prev().html();
|
||||||
|
var deveui = $(this).prev().attr('id').replace("alias_","");
|
||||||
|
$('#deveui').html(deveui);
|
||||||
|
var alarmactive = $('#alarmactive_'+deveui).html();
|
||||||
|
var smsnumber = $('#smsnumber_'+deveui).html();
|
||||||
|
|
||||||
|
$("#alias").val(alias);
|
||||||
|
console.log(alarmactive);
|
||||||
|
if (alarmactive == "1") {
|
||||||
|
$('#checkbox').prop('checked', true);
|
||||||
|
} else {
|
||||||
|
$('#checkbox').prop('checked', false);
|
||||||
|
}
|
||||||
|
$("#smsnumber").val(smsnumber);
|
||||||
|
$("#modal").addClass("is-active");
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#cart-close").click(function() {
|
||||||
|
$("#cart").removeClass("is-active");
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#payment_notifier_close").click(function() {
|
||||||
|
$("#payment_notifier").removeClass("is-active");
|
||||||
|
location.reload(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".abo_plus").click(function() {
|
||||||
|
console.log("abo_plus");
|
||||||
|
el = $(this).parent().find(".abo_add_years");
|
||||||
|
el_text = $(this).parent().parent().find(".abo_add_years_text");
|
||||||
|
counter = Number(el.html());
|
||||||
|
if (counter < 3) {
|
||||||
|
counter = counter + 1;
|
||||||
|
el.html(counter);
|
||||||
|
if (counter == 1) {
|
||||||
|
el_text.html("+" + counter + " Jahr");
|
||||||
|
} else {
|
||||||
|
el_text.html("+" + counter + " Jahre");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".abo_minus").click(function() {
|
||||||
|
console.log("abo_minus");
|
||||||
|
el = $(this).parent().find(".abo_add_years");
|
||||||
|
el_text = $(this).parent().parent().find(".abo_add_years_text");
|
||||||
|
counter = Number(el.html());
|
||||||
|
if (counter > 0) {
|
||||||
|
counter = counter - 1;
|
||||||
|
el.html(counter);
|
||||||
|
if (counter == 0) {
|
||||||
|
el_text.html(" ");
|
||||||
|
} else if (counter == 1) {
|
||||||
|
el_text.html("+" + counter + " Jahr");
|
||||||
|
} else {
|
||||||
|
el_text.html("+" + counter + " Jahre");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function add_years(dt,n)
|
||||||
|
{
|
||||||
|
return new Date(dt.setFullYear(dt.getFullYear() + n));
|
||||||
|
}
|
||||||
|
|
||||||
|
$(".abo_pay").click(function() {
|
||||||
|
console.log("pay...");
|
||||||
|
loadStripeLibrary();
|
||||||
|
counter = 0;
|
||||||
|
charge_data = '';
|
||||||
|
abo_table = '<table class="table is-bordered is-fullwidth" >';
|
||||||
|
abo_table += '<tr><th class="is-2">Alias</th><th>verlängern bis</th><th class="has-text-right">Betrag</th></tr>';
|
||||||
|
$(".waage").each(function( index ) {
|
||||||
|
console.log( index + ": " + $( this ).find(".alias").html() );
|
||||||
|
this_count = Number($( this ).find(".abo_add_years").html());
|
||||||
|
if (this_count > 0) {
|
||||||
|
counter += this_count;
|
||||||
|
paid_until = $( this ).find(".paid_until").html();
|
||||||
|
if (moment(paid_until,'DD.MM.YYYY') < moment()) {
|
||||||
|
this_date = moment().format('DD.MM.YYYY');
|
||||||
|
} else {
|
||||||
|
this_date = paid_until;
|
||||||
|
}
|
||||||
|
abo_table += '<tr><td>' + $( this ).find('.alias').html() + '</td><td>' + moment(this_date,'DD.MM.YYYY').add('years', this_count).format('DD.MM.YYYY') + '</td><td class="has-text-right">' + (this_count * 24).toFixed(2) + '</td></tr>';
|
||||||
|
if (charge_data == '') {
|
||||||
|
charge_data = $( this ).find("div").first().attr('id') + ":" + this_count;
|
||||||
|
} else {
|
||||||
|
charge_data += "," + $( this ).find("div").first().attr('id') + ":" + this_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log( counter );
|
||||||
|
});
|
||||||
|
abo_table += '<tr><td>Total CHF</td><td></td><td class="has-text-right">' + (counter * 24).toFixed(2) + '</td></tr>';
|
||||||
|
console.log("Counter: "+counter);
|
||||||
|
abo_table += "</table>";
|
||||||
|
if (counter > 0) {
|
||||||
|
console.log(abo_table);
|
||||||
|
console.log("charge_data: "+charge_data);
|
||||||
|
$("#abos_verlaengern").html(abo_table);
|
||||||
|
$("#charge_data").html(charge_data);
|
||||||
|
$("#cart").addClass("is-active");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#modal-close").click(function() {
|
||||||
|
console.log("blabla");
|
||||||
|
$("#modal").removeClass("is-active");
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#modal-save").click(function() {
|
||||||
|
var alarmactive = "0";
|
||||||
|
if ($('#checkbox').prop('checked')) {
|
||||||
|
alarmactive = "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation Code
|
||||||
|
var is_valid = true;
|
||||||
|
if (!validate('alias',$('#alias').val())) {
|
||||||
|
$('#alias_errormsg').html('Ungültige Bezeichnung; erlaubte Zeichen A-Z, 0-9 und Leerschlag');
|
||||||
|
$("#alias").addClass("is-danger");
|
||||||
|
$("#alias_exclamation").show();
|
||||||
|
is_valid = false;
|
||||||
|
} else {
|
||||||
|
$('#alias_errormsg').html('');
|
||||||
|
$("#alias").removeClass("is-danger");
|
||||||
|
$("#alias_exclamation").hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validate('smsnumber',$('#smsnumber').val())) {
|
||||||
|
$('#smsnumber_errormsg').html('Beispiel einer gültigen SMS Nummer: +41761234567');
|
||||||
|
$("#smsnumber").addClass("is-danger");
|
||||||
|
$("#smsnumber_exclamation").show();
|
||||||
|
is_valid = false;
|
||||||
|
} else {
|
||||||
|
$('#smsnumber_errormsg').html('');
|
||||||
|
$("#smsnumber").removeClass("is-danger");
|
||||||
|
$("#smsnumber_exclamation").hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(is_valid)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "save_scale_settings",
|
||||||
|
type: "get", //send it through get method
|
||||||
|
dataType: "json",
|
||||||
|
data: {
|
||||||
|
deveui: $('#deveui').html(),
|
||||||
|
alias: $('#alias').val(),
|
||||||
|
smsnumber: $("#smsnumber").val(),
|
||||||
|
alarmactive: alarmactive
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
console.log('save success');
|
||||||
|
if (response.rc == 0) {
|
||||||
|
$('#alias_'+$('#deveui').html()).html($('#alias').val());
|
||||||
|
var alarmactive = "0";
|
||||||
|
if ($('#checkbox').prop('checked')) {
|
||||||
|
alarmactive = "1";
|
||||||
|
}
|
||||||
|
$('#alarmactive_'+$('#deveui').html()).html(alarmactive);
|
||||||
|
$('#smsnumber_'+$('#deveui').html()).html($('#smsnumber').val());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr) {
|
||||||
|
console.log('save error');
|
||||||
|
//Do Something to handle error
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("save");
|
||||||
|
$("#modal").removeClass("is-active");
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadStripeLibrary() {
|
||||||
|
$.ajax({
|
||||||
|
url: "https://js.stripe.com/v3/",
|
||||||
|
dataType: "script",
|
||||||
|
async: false, // <-- This is the key
|
||||||
|
success: function () {
|
||||||
|
// all good...
|
||||||
|
console.log("loadStripeLibrary called...");
|
||||||
|
SetupStripe();
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
throw new Error("Could not load script " + script);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetupStripe() {
|
||||||
|
console.log("SetupStripe");
|
||||||
|
// Set your publishable key: remember to change this to your live publishable key in production
|
||||||
|
// See your keys here: https://dashboard.stripe.com/account/apikeys
|
||||||
|
stripe = Stripe('pk_test_YkSGqH3Tk9WKK9HrlY63GhAg');
|
||||||
|
elements = stripe.elements({ locale: "de" });
|
||||||
|
|
||||||
|
// Set up Stripe.js and Elements to use in checkout form
|
||||||
|
style = {
|
||||||
|
base: {
|
||||||
|
color: "#32325d",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
card = elements.create("card", { style: style });
|
||||||
|
card.mount("#card-element");
|
||||||
|
$("#card-errors-article").hide();
|
||||||
|
|
||||||
|
card.addEventListener('change', ({error}) => {
|
||||||
|
if (error) {
|
||||||
|
$("#card-errors").text(error.message);
|
||||||
|
$("#card-errors-article").show();
|
||||||
|
} else {
|
||||||
|
$("#card-errors").text("");
|
||||||
|
$("#card-errors-article").hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConfirmPayment(clientSecret) {
|
||||||
|
stripe.confirmCardPayment(clientSecret, {
|
||||||
|
payment_method: {
|
||||||
|
card: card,
|
||||||
|
billing_details: {
|
||||||
|
name: '{{ .UserName }}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).then(function(result) {
|
||||||
|
if (result.error) {
|
||||||
|
// Show error to your customer (e.g., insufficient funds)
|
||||||
|
console.log(result.error.message);
|
||||||
|
$('#card-errors').text(result.error.message);
|
||||||
|
$('#card-errors-article').show();
|
||||||
|
//Do Something to handle error
|
||||||
|
EndPaymentProgress();
|
||||||
|
} else {
|
||||||
|
// The payment has been processed!
|
||||||
|
if (result.paymentIntent.status === 'succeeded') {
|
||||||
|
// Show a success message to your customer
|
||||||
|
// There's a risk of the customer closing the window before callback
|
||||||
|
// execution. Set up a webhook or plugin to listen for the
|
||||||
|
// payment_intent.succeeded event that handles any business critical
|
||||||
|
// post-payment actions.
|
||||||
|
console.log("Payment succeeded!!!");
|
||||||
|
$('#card-errors').text("");
|
||||||
|
$('#card-errors-article').hide();
|
||||||
|
$("#cart").removeClass("is-active");
|
||||||
|
EndPaymentProgress();
|
||||||
|
$('#payment_notifier').addClass('is-active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function StartPaymentProgress() {
|
||||||
|
$("#cart-pay").attr("disabled", true).addClass("is-loading");
|
||||||
|
$("#cart-close").attr("disabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EndPaymentProgress() {
|
||||||
|
$("#cart-pay").attr("disabled", false).removeClass("is-loading");
|
||||||
|
$("#cart-close").attr("disabled", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PayMe() {
|
||||||
|
$.ajax({
|
||||||
|
url: "getstripepaymentintent",
|
||||||
|
type: "get", //send it through get method
|
||||||
|
dataType: "json",
|
||||||
|
data: {
|
||||||
|
charge_data: $("#charge_data").html()
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
console.log('pay success');
|
||||||
|
console.log('rc: '+response.rc);
|
||||||
|
if (response.rc == 0) {
|
||||||
|
ConfirmPayment(response.stripeclientsecret);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr) {
|
||||||
|
console.log('getstripepaymentintent error');
|
||||||
|
//Do Something to handle error
|
||||||
|
EndPaymentProgress();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#cart-pay').on('click', function(ev) {
|
||||||
|
StartPaymentProgress();
|
||||||
|
PayMe();
|
||||||
|
});
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
$("#email").focus();
|
||||||
|
|
||||||
|
// Check for click events on the navbar burger icon
|
||||||
|
$(".navbar-burger").click(function() {
|
||||||
|
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
||||||
|
$(".navbar-burger").toggleClass("is-active");
|
||||||
|
$(".navbar-menu").toggleClass("is-active");
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Login Button
|
||||||
|
$("#login-button").click(function(e){
|
||||||
|
//alert(true);
|
||||||
|
$("#login-form").submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Login Form
|
||||||
|
$("#login-form-blabla").submit(function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
var formData = {
|
||||||
|
next: $("#email").val(),
|
||||||
|
email: $("#email").val(),
|
||||||
|
password: $("#password").val(),
|
||||||
|
csrf_token: $("#csrf_token").val(),
|
||||||
|
next: $("#next").val()
|
||||||
|
};
|
||||||
|
|
||||||
|
//console.log(formData);
|
||||||
|
// send ajax
|
||||||
|
$.ajax({
|
||||||
|
url: '/login', // url where to submit the request
|
||||||
|
type : "POST", // type of action POST || GET
|
||||||
|
dataType : 'json', // data type
|
||||||
|
contentType: 'application/json',
|
||||||
|
data : JSON.stringify(formData), // post data || get data
|
||||||
|
success : function(result) {
|
||||||
|
// you can see the result from the console
|
||||||
|
// tab of the developer tools
|
||||||
|
console.log('SUCCESS');
|
||||||
|
console.log(result);
|
||||||
|
window.location.replace("/");
|
||||||
|
},
|
||||||
|
error: function(result) {
|
||||||
|
//console.log(xhr, resp, text);
|
||||||
|
console.log('ERROR');
|
||||||
|
console.log(result);
|
||||||
|
var errortext = '<ul style="list-style-type:disc">';
|
||||||
|
a = result.responseJSON.response.errors.email;
|
||||||
|
if (a != undefined) {
|
||||||
|
for (i=0; i < a.length; ++i) {
|
||||||
|
errortext = errortext + "<li>" + a[i] + "</li>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a = result.responseJSON.response.errors.password;
|
||||||
|
if (a != undefined) {
|
||||||
|
for (i=0; i < a.length; ++i) {
|
||||||
|
errortext = errortext + "<li>" + a[i] + "</li>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errortext = errortext + "</ul>";
|
||||||
|
$('#errorbox').html(errortext);
|
||||||
|
$('#errorbox').show();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
.image.is-10by1 img, .image.is-20by3 img {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image.is-10by1 {
|
||||||
|
padding-top: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image.is-20by3 {
|
||||||
|
padding-top: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-box {
|
||||||
|
margin: auto;
|
||||||
|
width: 300px;
|
||||||
|
background: rgba(255,255,255,0.05);
|
||||||
|
border: 1px solid rgba(255,255,255,0.3);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-link {
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apexcharts-title-text {
|
||||||
|
font-weight: 700 !important;
|
||||||
|
font-size: 24px !important;
|
||||||
|
font-family: "Rubik", sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apexcharts-legend {
|
||||||
|
font-family: "Rubik", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* * The CSS shown here will not be introduced in the Quickstart guide, but
|
||||||
|
* * shows how you can use CSS to style your Element's container.
|
||||||
|
* */
|
||||||
|
input,
|
||||||
|
.StripeElement {
|
||||||
|
height: 40px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
|
||||||
|
color: #32325d;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
box-shadow: 0 1px 3px 0 #e6ebf1;
|
||||||
|
-webkit-transition: box-shadow 150ms ease;
|
||||||
|
transition: box-shadow 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
.StripeElement--focus {
|
||||||
|
box-shadow: 0 1px 3px 0 #cfd7df;
|
||||||
|
}
|
||||||
|
|
||||||
|
.StripeElement--invalid {
|
||||||
|
border-color: #fa755a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.StripeElement--webkit-autofill {
|
||||||
|
background-color: #fefde5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkboxes input {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkboxes label span {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
+20353
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,125 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/stripe/stripe-go"
|
||||||
|
"github.com/stripe/stripe-go/paymentintent"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getStripeKey() string {
|
||||||
|
return "sk_test_GJbXPD0IAFNvvGpNEpaeDfhl"
|
||||||
|
}
|
||||||
|
|
||||||
|
func getstripepaymentintentHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
name := getUserName(request)
|
||||||
|
if name != "" {
|
||||||
|
|
||||||
|
charge_data, ok := request.URL.Query()["charge_data"]
|
||||||
|
if !ok || len(charge_data[0]) < 1 {
|
||||||
|
log.Println("Url Param 'charge_data' is missing")
|
||||||
|
fmt.Fprintf(response, "{ \"rc\": 1, \"msg\": \"charge_data must be specified in URL\" }")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
trackers := strings.Split(charge_data[0], ",")
|
||||||
|
|
||||||
|
var abo_years = 0
|
||||||
|
var items []string
|
||||||
|
for _, tracker := range trackers {
|
||||||
|
items = strings.Split(tracker, ":")
|
||||||
|
if len(items) == 2 {
|
||||||
|
abo_count, err := strconv.Atoi(items[1])
|
||||||
|
if err == nil {
|
||||||
|
abo_years += abo_count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
abo_amount := int64(abo_years * 2400)
|
||||||
|
|
||||||
|
stripe.Key = getStripeKey()
|
||||||
|
|
||||||
|
params := &stripe.PaymentIntentParams{
|
||||||
|
Amount: stripe.Int64(abo_amount),
|
||||||
|
Currency: stripe.String(string(stripe.CurrencyCHF)),
|
||||||
|
ReceiptEmail: stripe.String(name),
|
||||||
|
}
|
||||||
|
params.AddMetadata("charge_data", charge_data[0])
|
||||||
|
paymentintent, err := paymentintent.New(params)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(response, "{ \"rc\": 5, \"stripeclientsecret\": \"%s\" }\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(response, "{ \"rc\": 0, \"stripeclientsecret\": \"%s\" }\n", paymentintent.ClientSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(response, "{ \"rc\": 6, \"msg\": \"Only available for logged in users\" }")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandlePayment(user string, charge_data string, amount int64) {
|
||||||
|
fmt.Printf("HandlePayment for %s (charge_data: %s, amount: %d)!\n", user, charge_data, amount)
|
||||||
|
charge_data_email_text := fmt.Sprintf("%-30s %20s %10s\n", "Alias", "verlängern bis", "Betrag")
|
||||||
|
charge_data_email_text = charge_data_email_text + strings.Repeat("-", 62) + "\n"
|
||||||
|
for _, token := range strings.Split(charge_data, ",") {
|
||||||
|
res := strings.Split(token, ":")
|
||||||
|
if (len(res)) == 2 {
|
||||||
|
deveui := res[0]
|
||||||
|
years, _ := strconv.Atoi(res[1])
|
||||||
|
fmt.Printf("prolongActivation %s: %d\n", deveui, years)
|
||||||
|
prolongActivation(deveui, years)
|
||||||
|
line := fmt.Sprintf("%-30s %20s %10.2f\n", getDevAlias(deveui), getActiveUntil(deveui), float64(24*years))
|
||||||
|
charge_data_email_text = charge_data_email_text + line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
charge_data_email_text = charge_data_email_text + strings.Repeat("-", 62) + "\n"
|
||||||
|
charge_data_email_text = charge_data_email_text + fmt.Sprintf("%-30s %20s %10.2f\n", "Total CHF", "", float64(amount/100))
|
||||||
|
sendPaymentConfirmationEmail(user, charge_data_email_text, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripeWebhookHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
const MaxBodyBytes = int64(65536)
|
||||||
|
req.Body = http.MaxBytesReader(w, req.Body, MaxBodyBytes)
|
||||||
|
payload, err := ioutil.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error reading request body: %v\n", err)
|
||||||
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event := stripe.Event{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(payload, &event); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to parse webhook body json: %v\n", err.Error())
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal the event data into an appropriate struct depending on its Type
|
||||||
|
switch event.Type {
|
||||||
|
case "payment_intent.succeeded":
|
||||||
|
var paymentIntent stripe.PaymentIntent
|
||||||
|
err := json.Unmarshal(event.Data.Raw, &paymentIntent)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("PaymentIntent was successful (charge_data: %s, amount: %d)!\n", paymentIntent.Metadata["charge_data"], paymentIntent.Amount)
|
||||||
|
HandlePayment(paymentIntent.ReceiptEmail, paymentIntent.Metadata["charge_data"], paymentIntent.Amount)
|
||||||
|
// ... handle other event types
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "Unexpected event type: %s\n", event.Type)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=wo-bisch web service
|
||||||
|
After=syslog.target
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=appuser
|
||||||
|
Group=appuser
|
||||||
|
WorkingDirectory=/home/appuser/wo-bisch-web
|
||||||
|
ExecStart=/home/appuser/wo-bisch-web/wo-bisch-web
|
||||||
|
Restart=always
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
{{define "header_additions"}}{{end}}
|
||||||
|
{{define "layout"}}<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
<title>wo-bisch.ch - der smarte GPS Tracker</title>
|
||||||
|
<link rel="stylesheet" href="static/css/wo-bisch-web.css">
|
||||||
|
<link rel="stylesheet" href="static/css/wo-bisch-web-custom.css">
|
||||||
|
|
||||||
|
<script src="/static/js/fontawesome-5.11.2/all-minified.js"></script>
|
||||||
|
<script src="/static/js/jquery-3.3.1/jquery.min.js"></script>
|
||||||
|
{{template "header_additions" . }}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="section px-4 py-4">
|
||||||
|
<div class="container">
|
||||||
|
<hr class="top" />
|
||||||
|
<nav class="navbar" aria-label="main navigation">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item" href="/">
|
||||||
|
<img src="/static/images/wo-bisch-logo-28x28.png" alt="wo-bisch Logo" width="28" height="28">
|
||||||
|
</a>
|
||||||
|
<a role="button" class="navbar-burger" data-target="navMenu" aria-label="menu" aria-expanded="false">
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-menu" id="navMenu">
|
||||||
|
<div class="navbar-start">
|
||||||
|
<a class="navbar-item" href="/">
|
||||||
|
<div style="position:relative">
|
||||||
|
<span class="icon"><i class="fa fa-home"></i></span>
|
||||||
|
<span>Home</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" href="/contact.html">
|
||||||
|
<div style="position:relative">
|
||||||
|
<span class="icon"><i class="fa fa-address-card"></i></span>
|
||||||
|
<span>Kontakt</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{{ if ne .UserName "" }}
|
||||||
|
<a class="navbar-item" href="/tracker.html">
|
||||||
|
<div style="position:relative">
|
||||||
|
<span class="icon"><i class="fa fa-map-marked-alt"></i></span>
|
||||||
|
<span>Meine GPS Tracker</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<div class="navbar-end">
|
||||||
|
{{ if ne .UserName "" }}
|
||||||
|
<a class="navbar-item" href="/logout">
|
||||||
|
<div style="position:relative">
|
||||||
|
<span class="icon"><i class="fa fa-sign-out-alt"></i></span>
|
||||||
|
<span>Logout {{ .UserName }}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{{ else }}
|
||||||
|
<a class="navbar-item" href="/login.html">
|
||||||
|
<div style="position:relative">
|
||||||
|
<span class="icon"><i class="fa fa-sign-in-alt"></i></span>
|
||||||
|
<span>Login</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<hr />
|
||||||
|
{{template "body_content" . }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="/static/js/wo-bisch-web.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{{end}}
|
||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tracker handler
|
||||||
|
|
||||||
|
func save_tracker_settingsHandler(response http.ResponseWriter, request *http.Request) {
|
||||||
|
name := getUserName(request)
|
||||||
|
if name != "" {
|
||||||
|
|
||||||
|
deveui, ok := request.URL.Query()["deveui"]
|
||||||
|
|
||||||
|
if !ok || len(deveui[0]) < 1 {
|
||||||
|
log.Println("Url Param 'deveui' is missing")
|
||||||
|
fmt.Fprintf(response, "{ \"rc\": 1, \"msg\": \"deveui must be specified in URL\" }")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Query()["deveui"] will return an array of items,
|
||||||
|
// we only want the single item.
|
||||||
|
mydeveui := deveui[0]
|
||||||
|
|
||||||
|
if len(mydeveui) != 16 {
|
||||||
|
log.Println("specified 'deveui' has invalid length")
|
||||||
|
fmt.Fprintf(response, "{ \"rc\": 8, \"msg\": \"specified deveui has invalid length\" }")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(Contains(getMyDevs(name), mydeveui)) {
|
||||||
|
log.Println("specified 'deveui' does not belong to this user")
|
||||||
|
fmt.Fprintf(response, "{ \"rc\": 2, \"msg\": \"specified deveui does not belong to this user\" }")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Url Param 'deveui' is: " + string(mydeveui))
|
||||||
|
|
||||||
|
alias, ok2 := request.URL.Query()["alias"]
|
||||||
|
|
||||||
|
if !ok2 || len(alias[0]) < 1 {
|
||||||
|
log.Println("Url Param 'alias' is missing")
|
||||||
|
fmt.Fprintf(response, "{ \"rc\": 3, \"msg\": \"alias must be specified in URL\" }")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
myalias := alias[0]
|
||||||
|
|
||||||
|
// validate alias
|
||||||
|
match, _ := regexp.MatchString("^[a-zA-Z0-9 ]{1,25}$", myalias)
|
||||||
|
if !(match) {
|
||||||
|
log.Println("Url Param 'alias' is not valid")
|
||||||
|
fmt.Fprintf(response, "{ \"rc\": 9, \"msg\": \"alias is not valid\" }")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var mydev Dev
|
||||||
|
mydev.Deveui = mydeveui
|
||||||
|
mydev.Alias = myalias
|
||||||
|
|
||||||
|
// now we try to save the settings
|
||||||
|
err := updateTrackerSettings(mydev)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error to Update Device Settings")
|
||||||
|
fmt.Fprintf(response, "{ \"rc\": 6, \"msg\": \"error with saving device settings\" }")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(response, "{ \"rc\": 0, \"msg\": \"SUCCESS\" }")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(response, "{ \"rc\": 7, \"msg\": \"Only available for logged in users\" }")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
To generate CSS:
|
||||||
|
|
||||||
|
`npm run css-build`
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "wo-bisch-sass",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"description": "SASS for wo-bisch-web",
|
||||||
|
"main": "enter sass/wo-bisch-web.scss",
|
||||||
|
"scripts": {
|
||||||
|
"css-build": "node-sass --omit-source-map-url sass/wo-bisch-web.scss css/wo-bisch-web.css",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "Joerg Lehmann",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"bulma": "^0.9.0",
|
||||||
|
"bulma-helpers": "^0.3.12",
|
||||||
|
"node-sass": "^4.14.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
@charset "utf-8";
|
||||||
|
|
||||||
|
// Import a Google Font
|
||||||
|
@import url('https://fonts.googleapis.com/css?family=Rubik:400,700');
|
||||||
|
|
||||||
|
// Set nbit Style...
|
||||||
|
$family-sans-serif: "Rubik", sans-serif;
|
||||||
|
|
||||||
|
@import "../node_modules/bulma/bulma.sass";
|
||||||
|
@import "../node_modules/bulma-helpers/bulma-helpers.sass";
|
||||||
|
|
||||||
|
$navbar-height = 6.5rem
|
||||||
Reference in new issue
Block a user