Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions cmd/oceanbench/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log"
Expand All @@ -41,7 +42,9 @@ import (

"github.com/ausocean/cloud/gauth"
"github.com/ausocean/cloud/model"
"github.com/ausocean/openfish/datastore"
"github.com/ausocean/utils/nmea"
"github.com/google/uuid"
)

type minimalSite struct {
Expand Down Expand Up @@ -76,6 +79,8 @@ func setupAPIRoutes() {
http.HandleFunc("/api/get/sites/public", wrapAPI(getPublicSitesHandler))
http.HandleFunc("/api/get/sites/user", wrapAPI(getUserSitesHandler))
http.HandleFunc("/api/get/profile/data", wrapAPI(getProfileDataHandler))
http.HandleFunc("/api/get/profile/tv-overview-config", wrapAPI(getProfileTVConfigHandler))
http.HandleFunc("/api/get/broadcast/config", wrapAPI(getBroadcastConfigHandler))
http.HandleFunc("/api/get/vars/site", wrapAPI(getVarsForSiteHandler))
http.HandleFunc("/api/get/sensor/data/", wrapAPI(getSensorDataHandler))
http.HandleFunc("/api/get/gpstrail/", wrapAPI(getGPSTrailHandler))
Expand Down Expand Up @@ -257,6 +262,94 @@ func getProfileDataHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, p.Data)
}

func getProfileTVConfigHandler(w http.ResponseWriter, r *http.Request) {
p := requireProfile(w, r)
if p == nil {
return
}

// The request must be for a superadmin.
if !isSuperAdmin(p.Email) {
writeHttpError(w, http.StatusUnauthorized, "only available to superadmins")
return
}

// The variable scope of the config is the username of the email
// with fullstops replaced.
scope := strings.ReplaceAll(strings.Split(p.Email, "@")[0], ".", "")

ctx := r.Context()
existingCfg := true
configVar, err := model.GetVariable(ctx, settingsStore, 0, scope+".tvOverviewConfig")
if errors.Is(err, datastore.ErrNoSuchEntity) {
// The user doesn't yet have a configuration, so we will need to make a new one.
existingCfg = false
} else if err != nil {
writeHttpError(w, http.StatusInternalServerError, "unable to get config")
return
}

if !existingCfg {
log.Println("no existing tv overview config found, creating blank config")
err = model.PutVariable(ctx, settingsStore, 0, scope+".tvOverviewConfig", "{}")
if err != nil {
writeHttpError(w, http.StatusInternalServerError, "unable to put blank config")
}
configVar = &model.Variable{Name: scope + ".tvOverviewConfig", Value: "{}"}
}

_, err = w.Write([]byte(configVar.Value))
if err != nil {
writeHttpError(w, http.StatusInternalServerError, "unable to write config to response")
return
}
}

// getBroadcastConfigHandler handles requests to get the broadcast config for a given broadcast.
// The requester must have admin access to the site on which the broadcast configuration belongs.
//
// The API is expecting the following structure:
//
// /api/get/broadcast/config
//
// With the following query parameters:
//
// id: uuid of the broadcast to get
func getBroadcastConfigHandler(w http.ResponseWriter, r *http.Request) {
p := requireProfile(w, r)
if p == nil {
return
}

id := r.FormValue("id")
if id == "" {
writeHttpError(w, http.StatusBadRequest, "api/get/broadcast/config got empty id query parameter")
return
}
if err := uuid.Validate(id); err != nil {
writeHttpError(w, http.StatusBadRequest, "invalid id: %v", err)
return
}

ctx := r.Context()
broadcastVar, err := model.GetBroadcastVarByUUID(ctx, settingsStore, id)
if err != nil {
writeHttpError(w, http.StatusInternalServerError, "unable to get broadcast with UUID (%s): %v", id, err)
return
}

// Check that the user has admin privileges to the site the broadcast lives on.
if !isAdmin(ctx, broadcastVar.Skey, p.Email) {
writeHttpError(w, http.StatusUnauthorized, "user does not have admin privileges")
return
}

_, err = w.Write([]byte(broadcastVar.Value))
if err != nil {
writeHttpError(w, http.StatusInternalServerError, "unable to write broadcast config to response: %v", err)
}
}

func getVarsForSiteHandler(w http.ResponseWriter, r *http.Request) {
p := requireProfile(w, r)
if p == nil {
Expand Down
14 changes: 11 additions & 3 deletions cmd/oceanbench/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ import (
)

const (
version = "v0.35.1"
version = "v0.36.0"
localSite = "localhost"
localDevice = "localdevice"
localEmail = "localuser@localhost"
Expand Down Expand Up @@ -107,6 +107,7 @@ type commonData struct {
Pages []page
PageData interface{}
Profile *gauth.Profile
SuperAdmin bool
LoginURL string
LogoutURL string
Users []model.User
Expand Down Expand Up @@ -236,6 +237,7 @@ func main() {
http.HandleFunc("/admin/user/delete", adminHandler)
http.HandleFunc("/admin/site", adminHandler)
http.HandleFunc("/admin/broadcast", adminHandler)
http.HandleFunc("/admin/tv-overview", tvOverviewHandler)
http.HandleFunc("/admin/sandbox", sandboxHandler)
http.HandleFunc("/admin/sandbox/configure", configDevicesHandler)
http.HandleFunc("/admin/utils", adminHandler)
Expand Down Expand Up @@ -505,6 +507,7 @@ func hasPermission(ctx context.Context, p *gauth.Profile, mid, perm int64) (bool
// writeTemplate writes the given template with the supplied data,
// populating some common properties.
func writeTemplate(w http.ResponseWriter, r *http.Request, name string, data interface{}, msg string) {
profile, _ := getProfile(w, r)
v := reflect.Indirect(reflect.ValueOf(data))
p := v.FieldByName("Standalone")
if p.IsValid() {
Expand All @@ -524,9 +527,14 @@ func writeTemplate(w http.ResponseWriter, r *http.Request, name string, data int
}
p = v.FieldByName("Profile")
if p.IsValid() {
profile, _ := getProfile(w, r)
p.Set(reflect.ValueOf(profile))
}
p = v.FieldByName("SuperAdmin")
if p.IsValid() {
log.Println("p is valid, checking email:", profile.Email)
log.Println("I am superAdmin:", isSuperAdmin(profile.Email))
p.SetBool(isSuperAdmin(profile.Email))
}
p = v.FieldByName("LoginURL")
if p.IsValid() {
p.Set(reflect.ValueOf("/login?redirect=" + r.URL.RequestURI()))
Expand All @@ -538,7 +546,7 @@ func writeTemplate(w http.ResponseWriter, r *http.Request, name string, data int

const footer = "footer.html"
var b bytes.Buffer
err := templates.ExecuteTemplate(&b, footer, nil)
err := templates.ExecuteTemplate(&b, footer, data)
if err != nil {
log.Fatalf("ExecuteTemplate failed on %s: %v", footer, err)
}
Expand Down
18 changes: 17 additions & 1 deletion cmd/oceanbench/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,21 @@ export default [
resolve(),
typescript()
]
},
{
input: 'ts/tv-overview.ts',
output: {
file: 's/lit/tv-overview.js',
format: 'iife',
name: 'liveOverview',
globals: {
lit: 'lit',
'lit/decorators.js': 'decorators_js'
}
},
plugins: [
resolve(),
typescript()
]
}
];
];
3 changes: 3 additions & 0 deletions cmd/oceanbench/t/footer.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@
<a rel="license" href="https://www.ausocean.org/license">License</a>
)
</p>
{{if .SuperAdmin}}
<a href="/admin/tv-overview">TV Overview</a>
{{end}}
</footer>
40 changes: 40 additions & 0 deletions cmd/oceanbench/t/tv-overview.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!doctype html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous" />
<link href="/s/main.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<title>CloudBlue | TV Overview</title>
<script type="module" src="/s/lit/header-group.js"></script>
<script type="module" src="/s/lit/tv-overview.js"></script>
<script type="text/javascript" src="/s/main.js"></script>
</head>
<body>
<header-group id="header" class="header" version="{{ .Version }}" {{ if .Profile }}auth="true" {{ end }}>
<nav-menu id="nav-menu" slot="nav-menu">
{{ range .Pages -}}
<li data-perm="{{ .Perm }}" class="indent-{{ .Level }}">
<a {{ if .URL }}href="{{ .URL }}" {{ end }}{{ if .Selected }}class="selected" {{ end }}>{{ .Name }}</a>
</li>
{{- end }}
</nav-menu>
<site-menu id="sitemenu" {{ if .Profile }}selected-data="{{ .Profile.Data }}" {{ end }} slot="site-menu">
{{ range .Users -}}
<option style="display: none" slot="{{ .PermissionText }}" value="{{ .Skey }}"></option>
{{- end }}
</site-menu>
</header-group>
<section id="main" class="main">
{{if .Msg}}
<div class="red">{{.Msg}}</div>
<br />
{{end}}

<h1 class="container-md">TV Overview</h1>
<tv-overview></tv-overview>
</section>
{{ .Footer }}
</body>
</html>
Loading
Loading