Initial commit
This commit is contained in:
commit
0420b3efbd
|
|
@ -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...")
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
|
@ -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 |
File diff suppressed because one or more lines are too long
|
|
@ -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!!!");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -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);
|
||||
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -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;
|
||||
}
|
||||
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}}
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue