Skip to content

Commit

Permalink
Add inspection to general events, fix regression stopping admins from…
Browse files Browse the repository at this point in the history
… logging in
  • Loading branch information
NHAS committed Nov 24, 2024
1 parent c8b6426 commit fde2813
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 49 deletions.
1 change: 0 additions & 1 deletion adminui/frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { useAuthStore } from '@/stores/auth'
import { useDevicesStore } from '@/stores/devices'
import { useUsersStore } from '@/stores/users'
const router = useRouter()
const authStore = useAuthStore()
const devicesStore = useDevicesStore()
Expand Down
14 changes: 13 additions & 1 deletion adminui/frontend/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,20 @@ export interface EventErrorDTO {
time: string
}

export interface EventState {
current: string
previous: string
}

export interface GeneralEvent {
type: string
key: string
time: string
state: EventState
}

export interface ClusterEvents {
events: string[]
events: GeneralEvent[]
errors: EventErrorDTO[]
}

Expand Down
89 changes: 79 additions & 10 deletions adminui/frontend/src/pages/ClusterEvents.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { useInstanceDetailsStore } from '@/stores/serverInfo'
import { Icons } from '@/util/icons'
import { type AcknowledgeErrorResponseDTO, type EventErrorDTO } from '@/api'
import { type AcknowledgeErrorResponseDTO, type EventErrorDTO, type GeneralEvent } from '@/api'
const instanceDetails = useInstanceDetailsStore()
instanceDetails.load(false)
Expand Down Expand Up @@ -62,25 +62,32 @@ async function acknowledgeError(error: EventErrorDTO) {
} else {
toast.success('error acknowledged')
refresh()
isInspectionModalOpen.value = false
isErrorInspectionModalOpen.value = false
}
} catch (e) {
catcher(e, 'failed to acknowledged error: ')
}
}
const isInspectionModalOpen = ref(false)
const isErrorInspectionModalOpen = ref(false)
const inspectedError = ref<EventErrorDTO>({} as EventErrorDTO)
function openInspectionModal(error: EventErrorDTO) {
function openErrorInspectionModal(error: EventErrorDTO) {
inspectedError.value = error
isErrorInspectionModalOpen.value = true
}
const isInspectionModalOpen = ref(false)
const inspectedEvent = ref<GeneralEvent>({state: {previous: "", current: ""}} as GeneralEvent)
function openEventInspectionModal(error: GeneralEvent) {
inspectedEvent.value = error
isInspectionModalOpen.value = true
}
</script>

<template>
<main class="w-full p-4">
<Modal v-model:isOpen="isInspectionModalOpen">
<Modal v-model:isOpen="isErrorInspectionModalOpen">
<div class="w-screen max-w-[600px]">
<h3 class="text-lg font-bold">Error {{ inspectedError.error_id }}</h3>
<div class="mt-8">
Expand Down Expand Up @@ -113,7 +120,41 @@ function openInspectionModal(error: EventErrorDTO) {

<div class="flex flex-grow"></div>

<button class="btn btn-secondary" @click="() => (isInspectionModalOpen = false)">Cancel</button>
<button class="btn btn-secondary" @click="() => (isErrorInspectionModalOpen = false)">Cancel</button>
</span>
</div>
</div>
</Modal>
<Modal v-model:isOpen="isInspectionModalOpen">
<div class="w-screen max-w-[600px]">
<h3 class="text-lg font-bold overflow-hidden text-ellipsis whitespace-nowrap">Event Key: {{ inspectedEvent.key }}</h3>
<div class="mt-8">
<p>
<span class="font-medium text-gray-900 pt-6">Time:</span>
{{
new Date(inspectedEvent.time).toLocaleString(undefined, {
weekday: 'short',
hour: '2-digit',
minute: '2-digit'
})
}}
</p>

<label for="members" class="block font-medium text-gray-900 pt-6">New Key Value JSON:</label>
<textarea class="disabled textarea textarea-bordered w-full font-mono" rows="3" v-model="inspectedEvent.state.current"></textarea>

<div v-if="inspectedEvent.state.previous.length > 0">
<label for="members" class="block font-medium text-gray-900 pt-6">Previous Key Value JSON:</label>
<textarea
class="disabled textarea textarea-bordered w-full font-mono"
rows="3"
v-model="inspectedEvent.state.previous"
></textarea>
</div>

<span class="mt-4 flex">
<div class="flex flex-grow"></div>
<button class="btn btn-secondary" @click="() => (isInspectionModalOpen = false)">Close</button>
</span>
</div>
</div>
Expand All @@ -128,9 +169,32 @@ function openInspectionModal(error: EventErrorDTO) {
<h2 class="card-title">General</h2>
<table class="table table-fixed">
<tbody>
<tr class="hover" v-for="(line, index) in currentEvents" :key="'cluster-events-' + index">
<tr
class="hover"
v-for="(event, index) in currentEvents"
:key="'cluster-events-' + index"
v-on:dblclick="openEventInspectionModal(event)"
>
<td class="overflow-hidden text-ellipsis whitespace-nowrap w-[145px]">
{{
new Date(event.time).toLocaleString(undefined, {
weekday: 'short',
hour: '2-digit',
minute: '2-digit'
})
}}
</td>
<td class="overflow-hidden text-ellipsis whitespace-nowrap">
{{ line }}
<div class="font-medium">{{ event.key }}</div>
</td>
<td class="relative overflow-hidden text-ellipsis whitespace-nowrap">
<div class="font-medium">{{ event.type }}</div>
<button
@click="openEventInspectionModal(event)"
class="absolute right-9 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
>
<font-awesome-icon :icon="Icons.Inspect" class="text-secondary hover:text-secondary-focus" />
</button>
</td>
</tr>
</tbody>
Expand All @@ -151,7 +215,12 @@ function openInspectionModal(error: EventErrorDTO) {
<h2 class="card-title">Errors</h2>
<table class="table table-fixed">
<tbody>
<tr class="hover group" v-for="error in currentErrors" :key="error.error_id" v-on:dblclick="openInspectionModal(error)">
<tr
class="hover group"
v-for="error in currentErrors"
:key="error.error_id"
v-on:dblclick="openErrorInspectionModal(error)"
>
<!-- Time -->
<td class="overflow-hidden text-ellipsis whitespace-nowrap w-[145px]">
{{
Expand All @@ -168,7 +237,7 @@ function openInspectionModal(error: EventErrorDTO) {
<td class="relative overflow-hidden text-ellipsis whitespace-nowrap">
<div class="font-medium">{{ error.error }}</div>
<button
@click="openInspectionModal(error)"
@click="openErrorInspectionModal(error)"
class="absolute right-9 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
>
<font-awesome-icon :icon="Icons.Inspect" class="text-secondary hover:text-secondary-focus" />
Expand Down
6 changes: 4 additions & 2 deletions adminui/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ func (au *AdminUI) serverInfo(w http.ResponseWriter, r *http.Request) {
}

func (au *AdminUI) consoleLog(w http.ResponseWriter, r *http.Request) {
d := LogLinesDTO{
LogItems: au.logQueue.ReadAll(),
d := LogLinesDTO{}

for _, li := range au.logQueue.ReadAll() {
d.LogItems = append(d.LogItems, string(li))
}

w.Header().Set("content-type", "application/json")
Expand Down
4 changes: 2 additions & 2 deletions adminui/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ type LoginResponsetDTO struct {
}

type EventsResponseDTO struct {
EventLog []string `json:"events"`
Errors []data.EventError `json:"errors"`
EventLog []data.GeneralEvent `json:"events"`
Errors []data.EventError `json:"errors"`
}

type GenericResponseDTO struct {
Expand Down
4 changes: 2 additions & 2 deletions adminui/ui_webserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type AdminUI struct {

oidcProvider rp.RelyingParty

logQueue *queue.Queue
logQueue *queue.Queue[[]byte]

https, http *http.Server

Expand All @@ -57,7 +57,7 @@ func New(firewall *router.Firewall, errs chan<- error) (ui *AdminUI, err error)

var adminUI AdminUI
adminUI.firewall = firewall
adminUI.logQueue = queue.NewQueue(40)
adminUI.logQueue = queue.NewQueue[[]byte](40)

adminUI.ctrl = wagctl.NewControlClient(config.Values.Socket)

Expand Down
46 changes: 32 additions & 14 deletions internal/data/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ var (
clusterHealthLck sync.RWMutex
clusterHealthListeners = map[string]func(string){}

EventsQueue = queue.NewQueue(40)
EventsQueue = queue.NewQueue[GeneralEvent](40)
exit = make(chan bool)
)

Expand Down Expand Up @@ -139,21 +139,13 @@ func RegisterEventListener[T any](path string, isPrefix bool, f func(key string,
return
}

switch state {
case DELETED, CREATED:
EventsQueue.Write([]byte(fmt.Sprintf("%s[%s]", key, state)))

case MODIFIED:

previous := "nil"
if prevKv != nil {
previous = string(prevKv.Value)
}

EventsQueue.Write([]byte(fmt.Sprintf("%s[%s]: %s -> %s", key, state, previous, string(value))))

previous := ""
if prevKv != nil {
previous = string(prevKv.Value)
}

EventsQueue.Write(NewGeneralEvent(state, string(key), string(value), previous))

}(event.Kv.Key, event.PrevKv)

}
Expand All @@ -163,6 +155,32 @@ func RegisterEventListener[T any](path string, isPrefix bool, f func(key string,
return key, nil
}

type GeneralEvent struct {
Type string `json:"type"`
Key string `json:"key"`
Time time.Time `json:"time"`

State struct {
Current string `json:"current"`
Previous string `json:"previous"`
} `json:"state"`
}

func NewGeneralEvent(eType EventType, key, currentState, previousState string) GeneralEvent {
return GeneralEvent{
Type: eType.String(),
Key: key,
Time: time.Now(),
State: struct {
Current string "json:\"current\""
Previous string "json:\"previous\""
}{
Current: currentState,
Previous: previousState,
},
}
}

func RegisterClusterHealthListener(f func(status string)) (string, error) {
clusterHealthLck.Lock()
defer clusterHealthLck.Unlock()
Expand Down
2 changes: 1 addition & 1 deletion internal/data/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (

const (
minPasswordLength = 14
saltLength = 32
saltLength = 8
LocalUser = "local"
OidcUser = "oidc"

Expand Down
5 changes: 0 additions & 5 deletions internal/data/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package data

import (
"context"
"crypto/sha1"
"encoding/json"
"errors"
"fmt"
Expand All @@ -21,10 +20,6 @@ type UserModel struct {
Enforcing bool
}

func (um *UserModel) GetID() [20]byte {
return sha1.Sum([]byte(um.Username))
}

// IncrementAuthenticationAttempt Make sure that the attempts is always incremented first to stop race condition attacks
func IncrementAuthenticationAttempt(username, device string) error {
return doSafeUpdate(context.Background(), deviceKey(username, device), false, func(gr *clientv3.GetResponse) (value string, err error) {
Expand Down
24 changes: 13 additions & 11 deletions pkg/queue/queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,35 @@ import (
"sync"
)

type Queue struct {
type Queue[T any] struct {
sync.Mutex
max int
items []string
items []T
}

func NewQueue(max int) *Queue {
return &Queue{max: max, items: make([]string, 0, max)}
func NewQueue[T any](max int) *Queue[T] {
return &Queue[T]{
max: max,
items: make([]T, 0, max),
}
}

func (q *Queue) Write(line []byte) (int, error) {
func (q *Queue[T]) Write(item T) (int, error) {
q.Lock()
defer q.Unlock()

if len(q.items) < q.max {
q.items = append([]string{string(line)}, q.items...)
return len(line), nil
q.items = append([]T{item}, q.items...)
return 1, nil
}

q.items = q.items[:len(q.items)-1]
q.items = append([]string{string(line)}, q.items...)
return len(line), nil
q.items = append([]T{item}, q.items...)
return 1, nil
}

func (q *Queue) ReadAll() []string {
func (q *Queue[T]) ReadAll() []T {
q.Lock()
defer q.Unlock()

return q.items
}

0 comments on commit fde2813

Please sign in to comment.