airshipui/pkg/ctl/history.go
Schiefelbein, Andrew c004a202dc CTL command history page
This adds a history page, it could be argued that it should be
a popup or an accordion on each sub page but this seemed
to work the best.

It may require the existing sqlite database to be delete

Change-Id: Id48d513ab2d25e60351bd34e8ba84db80d266d87
2020-11-09 11:12:34 -06:00

140 lines
3.4 KiB
Go
Executable File

/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ctl
import (
"fmt"
"strings"
"opendev.org/airship/airshipui/pkg/configs"
"opendev.org/airship/airshipui/pkg/log"
"opendev.org/airship/airshipui/pkg/statistics"
)
// This is close to but not exactly a transaction structure from statistics, it's redone here because reasons
type record struct {
SubComponent configs.WsSubComponentType
User *string
ActionType *string
Target *string
Success bool
Started int64
Elapsed int64
Stopped int64
}
// HandleHistoryRequest will flop between requests so we don't have to have them all mapped as function calls
// This will wait for the sub component to complete before responding. The assumption is this is an async request
func HandleHistoryRequest(user *string, request configs.WsMessage) configs.WsMessage {
response := configs.WsMessage{
Type: configs.CTL,
Component: configs.History,
SubComponent: request.SubComponent,
}
var err error
subComponent := request.SubComponent
switch subComponent {
case configs.GetDefaults:
response.Data, err = getData(nil, nil)
default:
err = fmt.Errorf("Subcomponent %s not found", request.SubComponent)
}
if err != nil {
e := err.Error()
response.Error = &e
}
return response
}
// getData will return all rows within a specific date range
func getData(notBefore *int64, notAfter *int64) (map[string][]record, error) {
var wherePstmt strings.Builder
where := false
// because we may want data within a range add range slice where statements
if notBefore != nil {
where = true
wherePstmt.WriteString(fmt.Sprintf(" where started >= %d", notBefore))
}
if notAfter != nil {
if where {
wherePstmt.WriteString(fmt.Sprintf(" and stopped <= %d", notAfter))
} else {
wherePstmt.WriteString(fmt.Sprintf(" where stopped <= %d", notAfter))
}
}
data := map[string][]record{}
for _, table := range statistics.Tables {
// create a basic prepared statement to get data
// Why a prepared statement? Little Bobby Tables is why:
// https://xkcd.com/327/
pstmt := fmt.Sprintf("select * from %s", table)
if where {
pstmt += wherePstmt.String()
}
// Dark Helmet: Why are we always preparing? Just go!
stmt, err := statistics.DB.Prepare(pstmt)
if err != nil {
log.Error(err)
return nil, err
}
// Get the rows back from the query
rows, err := stmt.Query()
if err != nil {
log.Error(err)
return nil, err
}
defer rows.Close()
records := []record{}
for rows.Next() {
var r record
err = rows.Scan(
&r.SubComponent,
&r.User,
&r.ActionType,
&r.Target,
&r.Success,
&r.Started,
&r.Elapsed,
&r.Stopped,
)
if err != nil {
log.Error(err)
return nil, err
}
records = append(records, r)
}
err = rows.Err()
if err != nil {
log.Error(err)
return nil, err
}
if len(records) > 0 {
data[table] = records
}
}
return data, nil
}