sermoni

"Service monitor" / cronjob status service
Log | Files | Refs

commit bd996eec0a07992ae60d86323e47ba3b485ba11b
parent 8d4945b5fa3063cd3c629a2355be9ffa7142ac0b
Author: Vetle Haflan <vetle.haflan@luxsave.com>
Date:   Sun, 24 May 2020 20:44:31 +0200

Service deletion and minor fixes

Closes #4

Diffstat:
Minternal/events/events.go | 4++--
Minternal/http/events.go | 1+
Minternal/http/services.go | 7++++++-
Minternal/services/services.go | 44+++++++++++++++++++++++++++++++++++++-------
Mui/src/Events.vue | 4++--
Mui/src/Services.vue | 27++++++++++++++++++++++++++-
Mui/src/TimePicker.vue | 1-
Mui/src/requests.js | 13++++++++-----
8 files changed, 82 insertions(+), 19 deletions(-)

diff --git a/internal/events/events.go b/internal/events/events.go @@ -43,7 +43,7 @@ func GetAll() []*Event { return b.ForEach(func(id, _ []byte) error { eb := b.Bucket(id) event := new(Event) - if err := event.fromBucket(eb); err != nil { + if err := event.FromBucket(eb); err != nil { return err } events = append(events, event) @@ -125,7 +125,7 @@ func (event *Event) toBucket(eb *bbolt.Bucket) error { // Reads data from the given bucket into the fields of event // Returns error if any of the fields cannot be found -func (event *Event) fromBucket(eb *bbolt.Bucket) error { +func (event *Event) FromBucket(eb *bbolt.Bucket) error { var id, service, timestamp []byte var status, title, details []byte err := errors.New("missing field from database") diff --git a/internal/http/events.go b/internal/http/events.go @@ -109,6 +109,7 @@ func reportEvent(w http.ResponseWriter, r *http.Request) { events.Delete(firstEventID) log.Printf("MaxNumberEvents reached for service %v. Deleting first event, %v", service.ID, firstEventID) } + w.WriteHeader(http.StatusCreated) } func generateLateEvent(s *services.Service) *events.Event { diff --git a/internal/http/services.go b/internal/http/services.go @@ -3,6 +3,7 @@ package http import ( "encoding/json" "io/ioutil" + "log" "net/http" "sermoni/internal/services" "strconv" @@ -16,6 +17,7 @@ func getServices(w http.ResponseWriter, r *http.Request) { check(err) w.Write(data) } + func postService(w http.ResponseWriter, r *http.Request) { content, err := ioutil.ReadAll(r.Body) check(err) @@ -24,13 +26,16 @@ func postService(w http.ResponseWriter, r *http.Request) { err = json.Unmarshal(content, service) check(err) services.Add(service) + w.WriteHeader(http.StatusCreated) } + func deleteService(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id, _ := strconv.ParseUint(vars["id"], 10, 64) err := services.Delete(id) if err != nil { - // TODO: Non-existing error is not an internal server error + // TODO: "Non-existing" error is not an internal server error + log.Printf("deleteService error: %v\n", err) w.WriteHeader(http.StatusInternalServerError) } } diff --git a/internal/services/services.go b/internal/services/services.go @@ -2,8 +2,10 @@ package services import ( "errors" + "fmt" "log" "sermoni/internal/database" + "sermoni/internal/events" "strconv" "go.etcd.io/bbolt" @@ -73,26 +75,54 @@ func GetAll() []*Service { return services } -// Delete deletes the given service if it exists +// Delete deletes the given service if it exists, and all events for said service, if any func Delete(intID uint64) error { db := database.GetDB() serviceID := []byte(strconv.FormatUint(intID, 10)) - return db.Update(func(tx *bbolt.Tx) error { - var b, stb *bbolt.Bucket - if b = tx.Bucket(database.BucketKeyServices); b == nil { + return db.Update(func(tx *bbolt.Tx) (err error) { + sb := tx.Bucket(database.BucketKeyServices) + stb := tx.Bucket(database.BucketKeyServiceTokens) + eb := tx.Bucket(database.BucketKeyEvents) + if sb == nil { log.Panic("The services bucket does not exist") } - if stb = tx.Bucket(database.BucketKeyServiceTokens); b == nil { + if stb == nil { log.Panic("The service-tokens bucket does not exist") } + if eb == nil { + log.Panic("The event bucket does not exist") + } // Delete the entry from root services bucket - if b.Bucket(serviceID) == nil { + if sb.Bucket(serviceID) == nil { return errors.New("no service for the given id") } - if err := b.DeleteBucket(serviceID); err != nil { + if err = sb.DeleteBucket(serviceID); err != nil { + return err + } + + // Delete all events for the service + var eventsToDeleteIDs [][]byte + err = eb.ForEach(func(id, _ []byte) error { + evb := eb.Bucket(id) + event := new(events.Event) + if err := event.FromBucket(evb); err != nil { + return err + } + if event.Service == intID { + eventsToDeleteIDs = append(eventsToDeleteIDs, id) + } + return nil + }) + if err != nil { return err } + for _, id := range eventsToDeleteIDs { + if err = eb.DeleteBucket(id); err != nil { + fmt.Printf("Error deleting event with ID %v\n", id) + return err + } + } // Find the token entry and delete it from service-tokens bucket c := stb.Cursor() diff --git a/ui/src/Events.vue b/ui/src/Events.vue @@ -1,6 +1,6 @@ <template> <div class="events-wrapper"> - <div v-for="e in events"> + <div v-for="e in events" :key="e.id"> <div class="event" style="display: flex;" :style="e.style"> @@ -50,7 +50,7 @@ ); }, error => { - console.error(error) + console.error(error); this.$emit("error"); } ); diff --git a/ui/src/Services.vue b/ui/src/Services.vue @@ -17,6 +17,9 @@ <span>Expectation period</span> <time-picker :value="service.period"/> <br/> + + <button @click="deletionID = service.id">Delete</button> + <button @click="updateService(service.id)">Update</button> </div> <input :type="showPasswords ? 'text' : 'password'" v-model="newService.token" placeholder="Token"> <br/> @@ -26,6 +29,10 @@ <time-picker v-model="newService.period" placeholder="Expectation Period"/> <br/> <button @click="addService">Add service</button> + <div v-show="deletionID" style="position: fixed; bottom: 15px; right: 15px;"> + <button @click="deletionID = 0">Cancel</button> + <button @click="deleteService()">Confirm</button> + </div> </div> </template> @@ -45,7 +52,8 @@ period: {"number": 0, "scalar": 0}, maxevents: 0 }, - showPasswords: true + showPasswords: true, + deletionID: 0, } }, methods: { @@ -69,6 +77,23 @@ } ); }, + updateService(id) { + alert("Not implemented!"); + }, + deleteService() { + api.deleteService(this.deletionID, + success => { + this.services = this.services.filter( + s => s.id !== this.deletionID + ); + this.deletionID = 0; + }, + error => { + console.error(error); + this.$emit("error"); + } + ); + }, getExpectations(unixMilliTime) { const units = [ { "unit": "weeks", "scalar": 604800000 }, diff --git a/ui/src/TimePicker.vue b/ui/src/TimePicker.vue @@ -43,7 +43,6 @@ }, methods: { update(prop, e) { - console.log(e) const newValue = this.value; newValue[prop] = e.target.value; this.$emit("input", newValue); diff --git a/ui/src/requests.js b/ui/src/requests.js @@ -58,25 +58,28 @@ function getServices(successHandler, errorHandler) { }); } -// TODO: Expected status 201 (and set 201 server-side) function postService(service, successHandler, errorHandler) { request({ url: "/services", method: "POST", + expectedStatus: 201, data: service, success: successHandler, error: errorHandler }); } -function deleteService(id) { - +function deleteService(id, successHandler, errorHandler) { + request({ + url: "/services/" + id, + method: "DELETE", + success: successHandler, + error: errorHandler + }); } /***** Event management *****/ -// (TODO) - function getEvents(successHandler, errorHandler) { request({ url: "/events",