-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #313 from nerdalert/patternfly-ui
Add a PatternFly UI for jobs
- Loading branch information
Showing
48 changed files
with
22,329 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,3 +12,10 @@ models | |
generated | ||
.idea | ||
.DS_Store | ||
|
||
# UI assets | ||
**/node_modules | ||
dist | ||
yarn-error.log | ||
yarn.lock | ||
stats.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Editor configuration, see http://editorconfig.org | ||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
indent_style = space | ||
indent_size = 2 | ||
insert_final_newline = true | ||
trim_trailing_whitespace = true | ||
|
||
[*.snap] | ||
max_line_length = off | ||
trim_trailing_whitespace = false | ||
|
||
[*.md] | ||
max_line_length = off | ||
trim_trailing_whitespace = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
module.exports = { | ||
// tells eslint to use the TypeScript parser | ||
"parser": "@typescript-eslint/parser", | ||
// tell the TypeScript parser that we want to use JSX syntax | ||
"parserOptions": { | ||
"tsx": true, | ||
"jsx": true, | ||
"js": true, | ||
"useJSXTextNode": true, | ||
"project": "./tsconfig.json", | ||
"tsconfigRootDir": "." | ||
}, | ||
// we want to use the recommended rules provided from the typescript plugin | ||
"extends": [ | ||
"@redhat-cloud-services/eslint-config-redhat-cloud-services", | ||
"eslint:recommended", | ||
"plugin:react/recommended", | ||
"plugin:@typescript-eslint/recommended" | ||
], | ||
"globals": { | ||
"window": "readonly", | ||
"describe": "readonly", | ||
"test": "readonly", | ||
"expect": "readonly", | ||
"it": "readonly", | ||
"process": "readonly", | ||
"document": "readonly", | ||
"insights": "readonly", | ||
"shallow": "readonly", | ||
"render": "readonly", | ||
"mount": "readonly" | ||
}, | ||
"overrides": [ | ||
{ | ||
"files": ["src/**/*.ts", "src/**/*.tsx"], | ||
"parser": "@typescript-eslint/parser", | ||
"plugins": ["@typescript-eslint"], | ||
"extends": ["plugin:@typescript-eslint/recommended"], | ||
"rules": { | ||
"react/prop-types": "off", | ||
"@typescript-eslint/no-unused-vars": "error" | ||
}, | ||
}, | ||
], | ||
"settings": { | ||
"react": { | ||
"version": "^16.11.0" | ||
} | ||
}, | ||
// includes the typescript specific rules found here: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules | ||
"plugins": [ | ||
"@typescript-eslint", | ||
"react-hooks", | ||
"eslint-plugin-react-hooks" | ||
], | ||
"rules": { | ||
"sort-imports": [ | ||
"error", | ||
{ | ||
"ignoreDeclarationSort": true | ||
} | ||
], | ||
"@typescript-eslint/explicit-function-return-type": "off", | ||
"react-hooks/rules-of-hooks": "error", | ||
"react-hooks/exhaustive-deps": "warn", | ||
"@typescript-eslint/interface-name-prefix": "off", | ||
"prettier/prettier": "off", | ||
"import/no-unresolved": "off", | ||
"import/extensions": "off", | ||
"react/prop-types": "off" | ||
}, | ||
"env": { | ||
"browser": true, | ||
"node": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"singleQuote": true, | ||
"printWidth": 120 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# InstructLab Bot UI | ||
|
||
This is a [Patternfly](https://www.patternfly.org/get-started/develop/) react deployment for front-ending InstructLab Bot jobs. The framework is based off [patternfloy-react-seed](https://github.com/patternfly/patternfly-react-seed) but upgraded to use the latest React v6+. The data is all read only streaming from redis, via the go-streamer service. | ||
|
||
## Quickstart | ||
|
||
- Start the bot [compose stack](../deploy/compose). | ||
- Start go-streamer on the same host as the redis server since it will be connecting to `localhost:6379` by default, but can be set with `--redis-server`. The same applies to the listening websocket port `--listen-address` which defaults to `localhost:3000`. | ||
|
||
```bash | ||
cd ui/go-stream | ||
./go-stream | ||
``` | ||
|
||
- Start [webpack](https://github.com/webpack/webpack). | ||
|
||
```bash | ||
cd ui/ | ||
npm run start:dev | ||
``` | ||
|
||
## Authentication | ||
|
||
Currently, there is no OAuth implementation, this just supports a user/pass defined at runtime. If no `/ui/.env` file is defined, the user/pass is simply admin/password. To change those defaults, create the `/ui/.env` file and fill in the account user/pass with the following. | ||
|
||
```text | ||
REACT_APP_ADMIN_USERNAME=<user> | ||
REACT_APP_ADMIN_PASSWORD=<pass> | ||
``` | ||
|
||
## Development Scripts | ||
|
||
```bash | ||
# Install development/build dependencies | ||
npm install | ||
|
||
# Start the development server | ||
npm run start:dev | ||
|
||
# Run a production build (outputs to "dist" dir) | ||
npm run build | ||
|
||
# Start the express server (run a production build first) | ||
npm run start | ||
|
||
# Start storybook component explorer | ||
npm run storybook | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const indexPath = path.resolve(__dirname, 'dist/index.html'); | ||
const targetFilePath = path.resolve(__dirname, 'dist/200.html'); | ||
// ensure we have bookmarkable url's when publishing to surge | ||
// https://surge.sh/help/adding-a-200-page-for-client-side-routing | ||
fs.createReadStream(indexPath).pipe(fs.createWriteStream(targetFilePath)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/go-redis/redis/v8" | ||
"github.com/gorilla/websocket" | ||
"github.com/spf13/pflag" | ||
"go.uber.org/zap" | ||
) | ||
|
||
var upgrader = websocket.Upgrader{ | ||
CheckOrigin: func(r *http.Request) bool { | ||
return true | ||
}, | ||
} | ||
|
||
var rdb *redis.Client | ||
var ctx = context.Background() | ||
var logger *zap.Logger | ||
|
||
type JobData struct { | ||
JobID string `json:"jobID"` | ||
Duration string `json:"duration"` | ||
Status string `json:"status"` | ||
S3URL string `json:"s3URL"` | ||
ModelName string `json:"modelName"` | ||
RepoOwner string `json:"repoOwner"` | ||
Author string `json:"author"` | ||
PrNumber string `json:"prNumber"` | ||
PrSHA string `json:"prSHA"` | ||
RequestTime string `json:"requestTime"` | ||
Errors string `json:"errors"` | ||
RepoName string `json:"repoName"` | ||
JobType string `json:"jobType"` | ||
InstallationID string `json:"installationID"` | ||
} | ||
|
||
func handleConnections(w http.ResponseWriter, r *http.Request) { | ||
ws, err := upgrader.Upgrade(w, r, nil) | ||
if err != nil { | ||
logger.Error("Error upgrading WebSocket", zap.Error(err)) | ||
return | ||
} | ||
defer ws.Close() | ||
logger.Debug("WebSocket connection successfully upgraded.") | ||
|
||
// Immediately send all jobs in the results queue | ||
sendAllJobs(ws) | ||
|
||
failedPings := 0 | ||
ticker := time.NewTicker(10 * time.Second) | ||
defer ticker.Stop() | ||
|
||
for { | ||
select { | ||
case <-ticker.C: | ||
// Check if the WebSocket connection is closed | ||
if ws == nil || ws.CloseHandler() != nil { | ||
logger.Debug("WebSocket connection appears to be closed.") | ||
return | ||
} | ||
|
||
if err := ws.WriteMessage(websocket.PingMessage, nil); err != nil { | ||
failedPings++ | ||
logger.Info("Ping failed", zap.Int("attempt", failedPings), zap.Error(err)) | ||
if failedPings > 3 { | ||
logger.Info("Closing connection after multiple failed pings.") | ||
return | ||
} | ||
} else { | ||
// Reset failed pings count on successful ping | ||
failedPings = 0 | ||
} | ||
} | ||
} | ||
} | ||
|
||
func sendAllJobs(ws *websocket.Conn) { | ||
jobIDs, err := rdb.LRange(ctx, "results", 0, -1).Result() | ||
if err != nil { | ||
logger.Error("Error retrieving job IDs from Redis", zap.Error(err)) | ||
return | ||
} | ||
logger.Debug("Sending data for jobs.", zap.Int("count", len(jobIDs))) | ||
|
||
for _, jobID := range jobIDs { | ||
jobData := fetchJobData(jobID) | ||
jsonData, err := json.Marshal(jobData) | ||
if err != nil { | ||
logger.Error("Error marshalling job data to JSON", zap.Error(err)) | ||
continue | ||
} | ||
if err := ws.WriteMessage(websocket.TextMessage, jsonData); err != nil { | ||
logger.Error("Error sending job data over WebSocket", zap.Error(err)) | ||
continue | ||
} | ||
logger.Debug("Job data sent.", zap.String("Job ID", jobID)) | ||
} | ||
} | ||
|
||
func fetchJobData(jobID string) JobData { | ||
var jobData JobData | ||
jobData.JobID = jobID | ||
jobData.Duration = rdb.Get(ctx, fmt.Sprintf("jobs:%s:duration", jobID)).Val() | ||
jobData.Status = rdb.Get(ctx, fmt.Sprintf("jobs:%s:status", jobID)).Val() | ||
jobData.S3URL = rdb.Get(ctx, fmt.Sprintf("jobs:%s:s3_url", jobID)).Val() | ||
jobData.ModelName = rdb.Get(ctx, fmt.Sprintf("jobs:%s:model_name", jobID)).Val() | ||
jobData.RepoOwner = rdb.Get(ctx, fmt.Sprintf("jobs:%s:repo_owner", jobID)).Val() | ||
jobData.Author = rdb.Get(ctx, fmt.Sprintf("jobs:%s:author", jobID)).Val() | ||
jobData.PrNumber = rdb.Get(ctx, fmt.Sprintf("jobs:%s:pr_number", jobID)).Val() | ||
jobData.PrSHA = rdb.Get(ctx, fmt.Sprintf("jobs:%s:pr_sha", jobID)).Val() | ||
jobData.RequestTime = rdb.Get(ctx, fmt.Sprintf("jobs:%s:request_time", jobID)).Val() | ||
jobData.Errors = rdb.Get(ctx, fmt.Sprintf("jobs:%s:errors", jobID)).Val() | ||
jobData.RepoName = rdb.Get(ctx, fmt.Sprintf("jobs:%s:repo_name", jobID)).Val() | ||
jobData.JobType = rdb.Get(ctx, fmt.Sprintf("jobs:%s:job_type", jobID)).Val() | ||
jobData.InstallationID = rdb.Get(ctx, fmt.Sprintf("jobs:%s:installation_id", jobID)).Val() | ||
|
||
logger.Debug("Fetched data for job.", zap.String("Job ID", jobID), zap.Any("Data", jobData)) | ||
return jobData | ||
} | ||
|
||
func setupRoutes() { | ||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||
fmt.Fprintln(w, "Redis Queue Dashboard") | ||
}) | ||
http.HandleFunc("/ws", handleConnections) | ||
} | ||
|
||
func setupLogger(debugMode bool) *zap.Logger { | ||
var logLevel zap.AtomicLevel | ||
if debugMode { | ||
logLevel = zap.NewAtomicLevelAt(zap.DebugLevel) | ||
} else { | ||
logLevel = zap.NewAtomicLevelAt(zap.InfoLevel) | ||
} | ||
|
||
loggerConfig := zap.Config{ | ||
Level: logLevel, | ||
Encoding: "console", | ||
EncoderConfig: zap.NewDevelopmentEncoderConfig(), | ||
OutputPaths: []string{"stderr"}, | ||
ErrorOutputPaths: []string{"stderr"}, | ||
} | ||
logger, err := loggerConfig.Build() | ||
if err != nil { | ||
panic(fmt.Sprintf("Cannot build logger: %v", err)) | ||
} | ||
return logger | ||
} | ||
|
||
func main() { | ||
debugFlag := pflag.Bool("debug", false, "Enable debug mode") | ||
listenAddress := pflag.String("listen-address", "localhost:3000", "Address to listen on") | ||
redisAddress := pflag.String("redis-server", "localhost:6379", "Redis server address") | ||
pflag.Parse() | ||
|
||
logger = setupLogger(*debugFlag) | ||
defer logger.Sync() | ||
zap.ReplaceGlobals(logger) | ||
|
||
rdb = redis.NewClient(&redis.Options{ | ||
Addr: *redisAddress, | ||
}) | ||
|
||
setupRoutes() | ||
|
||
logger.Info("Server starting", zap.String("listen-address", *listenAddress)) | ||
err := http.ListenAndServe(*listenAddress, nil) | ||
if err != nil { | ||
logger.Error("ListenAndServe failed", zap.Error(err)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
module github.com/instructlab/instructlab-bot/ui/go-stream | ||
|
||
go 1.22.1 | ||
|
||
require ( | ||
github.com/go-redis/redis/v8 v8.11.5 | ||
github.com/gorilla/websocket v1.5.1 | ||
github.com/spf13/pflag v1.0.5 | ||
go.uber.org/zap v1.27.0 | ||
) | ||
|
||
require ( | ||
github.com/cespare/xxhash/v2 v2.1.2 // indirect | ||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect | ||
go.uber.org/multierr v1.10.0 // indirect | ||
golang.org/x/net v0.17.0 // indirect | ||
) |
Oops, something went wrong.