mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-12-29 21:40:57 +00:00
Merge pull request #105 from mstg/frontend-minimal
Re-add apollo proto and UI
This commit is contained in:
commit
0b9174bfcd
21 changed files with 1998 additions and 10 deletions
65
apollo/proto/v1/BUILD.bazel
Normal file
65
apollo/proto/v1/BUILD.bazel
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
load("@rules_proto//proto:defs.bzl", "proto_library")
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
||||||
|
load("@com_github_grpc_ecosystem_grpc_gateway_v2//protoc-gen-openapiv2:defs.bzl", "protoc_gen_openapiv2")
|
||||||
|
load("@openapi_tools_generator_bazel//:defs.bzl", "openapi_generator")
|
||||||
|
|
||||||
|
proto_library(
|
||||||
|
name = "apollopb_proto",
|
||||||
|
srcs = [
|
||||||
|
"advisory.proto",
|
||||||
|
"affected_product.proto",
|
||||||
|
"apollo.proto",
|
||||||
|
"build.proto",
|
||||||
|
"cve.proto",
|
||||||
|
"fix.proto",
|
||||||
|
"short_code.proto",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"@com_envoyproxy_protoc_gen_validate//validate:validate_proto",
|
||||||
|
"@com_google_protobuf//:timestamp_proto",
|
||||||
|
"@com_google_protobuf//:wrappers_proto",
|
||||||
|
"@go_googleapis//google/api:annotations_proto",
|
||||||
|
"@go_googleapis//google/api:httpbody_proto",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_proto_library(
|
||||||
|
name = "apollopb_go_proto",
|
||||||
|
compilers = [
|
||||||
|
"//:go_apiv2",
|
||||||
|
"//:go_grpc",
|
||||||
|
"//:go_validate",
|
||||||
|
"@com_github_grpc_ecosystem_grpc_gateway_v2//protoc-gen-grpc-gateway:go_gen_grpc_gateway",
|
||||||
|
],
|
||||||
|
importpath = "peridot.resf.org/apollo/pb",
|
||||||
|
proto = ":apollopb_proto",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"@com_envoyproxy_protoc_gen_validate//validate:validate_go_proto",
|
||||||
|
"@go_googleapis//google/api:annotations_go_proto",
|
||||||
|
"@go_googleapis//google/api:httpbody_go_proto",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "pb",
|
||||||
|
embed = [":apollopb_go_proto"],
|
||||||
|
importpath = "peridot.resf.org/apollo/pb",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
protoc_gen_openapiv2(
|
||||||
|
name = "openapi",
|
||||||
|
proto = ":apollopb_proto",
|
||||||
|
simple_operation_ids = True,
|
||||||
|
single_output = True,
|
||||||
|
)
|
||||||
|
|
||||||
|
openapi_generator(
|
||||||
|
name = "client_typescript",
|
||||||
|
generator = "typescript-fetch",
|
||||||
|
spec = ":openapi",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
112
apollo/proto/v1/advisory.proto
Normal file
112
apollo/proto/v1/advisory.proto
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package resf.apollo.v1;
|
||||||
|
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "google/protobuf/wrappers.proto";
|
||||||
|
import "apollo/proto/v1/cve.proto";
|
||||||
|
import "apollo/proto/v1/fix.proto";
|
||||||
|
|
||||||
|
option go_package = "peridot.resf.org/apollo/pb;apollopb";
|
||||||
|
|
||||||
|
message RPMs {
|
||||||
|
repeated string nvras = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advisory
|
||||||
|
//
|
||||||
|
// Product advisory
|
||||||
|
message Advisory {
|
||||||
|
enum Type {
|
||||||
|
TYPE_UNKNOWN = 0;
|
||||||
|
TYPE_SECURITY = 1;
|
||||||
|
TYPE_BUGFIX = 2;
|
||||||
|
TYPE_ENHANCEMENT = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type
|
||||||
|
//
|
||||||
|
// Type of advisory
|
||||||
|
Type type = 1;
|
||||||
|
|
||||||
|
// Short code
|
||||||
|
//
|
||||||
|
// Errata prefix or short code
|
||||||
|
// Example: RLBA, RLEA, RLSA
|
||||||
|
string short_code = 2;
|
||||||
|
|
||||||
|
// Name
|
||||||
|
//
|
||||||
|
// Full errata name
|
||||||
|
// Example: RLBA-2021:0001, RLSA-2021:0002
|
||||||
|
string name = 3;
|
||||||
|
|
||||||
|
// Synopsis
|
||||||
|
//
|
||||||
|
// Short description of advisory
|
||||||
|
string synopsis = 4;
|
||||||
|
|
||||||
|
enum Severity {
|
||||||
|
SEVERITY_UNKNOWN = 0;
|
||||||
|
SEVERITY_LOW = 1;
|
||||||
|
SEVERITY_MODERATE = 2;
|
||||||
|
SEVERITY_IMPORTANT = 3;
|
||||||
|
SEVERITY_CRITICAL = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Severity
|
||||||
|
//
|
||||||
|
// Severity of advisory. Used only for security advisories
|
||||||
|
Severity severity = 5;
|
||||||
|
|
||||||
|
// Topic
|
||||||
|
//
|
||||||
|
// Topic of advisory
|
||||||
|
// Example: An update for the go-toolset:rhel8 module is now available for Rocky Linux 8.
|
||||||
|
string topic = 6;
|
||||||
|
|
||||||
|
// Description
|
||||||
|
//
|
||||||
|
// Description of advisory. Contains information about changes and package.
|
||||||
|
string description = 7;
|
||||||
|
|
||||||
|
// Solution
|
||||||
|
//
|
||||||
|
// How to solve the advisory. Contains information about how to apply the advisory changes
|
||||||
|
google.protobuf.StringValue solution = 8;
|
||||||
|
|
||||||
|
// Affected products
|
||||||
|
//
|
||||||
|
// A list of affected products
|
||||||
|
repeated string affected_products = 9;
|
||||||
|
|
||||||
|
// Fixes
|
||||||
|
//
|
||||||
|
// A list of tickets from upstream bug trackers
|
||||||
|
repeated Fix fixes = 10;
|
||||||
|
|
||||||
|
// CVEs
|
||||||
|
//
|
||||||
|
// A list of CVEs assigned to this advisory
|
||||||
|
repeated CVE cves = 11;
|
||||||
|
|
||||||
|
// References
|
||||||
|
//
|
||||||
|
// General references used in this advisory
|
||||||
|
repeated string references = 12;
|
||||||
|
|
||||||
|
// Published at
|
||||||
|
//
|
||||||
|
// Timestamp the advisory is published at
|
||||||
|
google.protobuf.Timestamp published_at = 13;
|
||||||
|
|
||||||
|
// RPMs
|
||||||
|
//
|
||||||
|
// Affected RPMs
|
||||||
|
map<string, RPMs> rpms = 14;
|
||||||
|
|
||||||
|
// Reboot suggested
|
||||||
|
//
|
||||||
|
// Whether a system reboot should be suggested after applying this advisory
|
||||||
|
bool reboot_suggested = 15;
|
||||||
|
}
|
41
apollo/proto/v1/affected_product.proto
Normal file
41
apollo/proto/v1/affected_product.proto
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package resf.apollo.v1;
|
||||||
|
|
||||||
|
import "google/protobuf/wrappers.proto";
|
||||||
|
|
||||||
|
option go_package = "peridot.resf.org/apollo/pb;apollopb";
|
||||||
|
|
||||||
|
message AffectedProduct {
|
||||||
|
int64 product_id = 1;
|
||||||
|
google.protobuf.StringValue cve_id = 2;
|
||||||
|
string version = 3;
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
STATE_UNKNOWN = 0;
|
||||||
|
// CVE only affects downstream
|
||||||
|
STATE_UNDER_INVESTIGATION_DOWNSTREAM = 1;
|
||||||
|
// CVE affecting upstream and a fix still hasn't been issued
|
||||||
|
STATE_UNDER_INVESTIGATION_UPSTREAM = 2;
|
||||||
|
// CVE has been fixed upstream
|
||||||
|
STATE_FIXED_UPSTREAM = 3;
|
||||||
|
// CVE has been fixed downstream
|
||||||
|
// At this stage the CVE can be included in errata
|
||||||
|
STATE_FIXED_DOWNSTREAM = 4;
|
||||||
|
// CVE will NOT be fixed upstream
|
||||||
|
STATE_WILL_NOT_FIX_UPSTREAM = 5;
|
||||||
|
// CVE will NOT be fixed downstream
|
||||||
|
// This will probably never happen with Core, but may happen for SIGs
|
||||||
|
STATE_WILL_NOT_FIX_DOWNSTREAM = 6;
|
||||||
|
// CVE is out of support scope
|
||||||
|
STATE_OUT_OF_SUPPORT_SCOPE = 7;
|
||||||
|
// CVE affects product and upstream is working on a fix
|
||||||
|
STATE_AFFECTED_UPSTREAM = 8;
|
||||||
|
// CVE affects product and a fix is being worked out
|
||||||
|
STATE_AFFECTED_DOWNSTREAM = 9;
|
||||||
|
}
|
||||||
|
State state = 4;
|
||||||
|
|
||||||
|
string package = 5;
|
||||||
|
google.protobuf.StringValue advisory = 6;
|
||||||
|
}
|
167
apollo/proto/v1/apollo.proto
Normal file
167
apollo/proto/v1/apollo.proto
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package resf.apollo.v1;
|
||||||
|
|
||||||
|
import "google/api/annotations.proto";
|
||||||
|
import "google/api/httpbody.proto";
|
||||||
|
import "google/protobuf/wrappers.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "validate/validate.proto";
|
||||||
|
import "apollo/proto/v1/advisory.proto";
|
||||||
|
|
||||||
|
option go_package = "peridot.resf.org/apollo/pb;apollopb";
|
||||||
|
|
||||||
|
service ApolloService {
|
||||||
|
// ListAdvisories
|
||||||
|
//
|
||||||
|
// Return a list of advisories by given filters.
|
||||||
|
// No filters returns all advisories
|
||||||
|
// This method is paginated
|
||||||
|
rpc ListAdvisories (ListAdvisoriesRequest) returns (ListAdvisoriesResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/v2/advisories"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAdvisoriesRSS
|
||||||
|
//
|
||||||
|
// Same as ListAdvisories but returns an RSS feed
|
||||||
|
// Only returns 25 latest advisories
|
||||||
|
// Supports filters
|
||||||
|
rpc ListAdvisoriesRSS (ListAdvisoriesRSSRequest) returns (google.api.HttpBody) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/v2/advisories:rss"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdvisory
|
||||||
|
//
|
||||||
|
// Returns an advisory with given ID if found, else returns NotFound
|
||||||
|
rpc GetAdvisory (GetAdvisoryRequest) returns (GetAdvisoryResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/v2/advisories/{id=*}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message AdvisoryFilters {
|
||||||
|
// Product
|
||||||
|
//
|
||||||
|
// The product to fetch advisories for
|
||||||
|
// For example: Rocky Linux 8
|
||||||
|
google.protobuf.StringValue product = 1;
|
||||||
|
|
||||||
|
// Before
|
||||||
|
//
|
||||||
|
// Advisories published before timestamp
|
||||||
|
google.protobuf.Timestamp before = 2;
|
||||||
|
|
||||||
|
// After
|
||||||
|
//
|
||||||
|
// Advisories published after timestamp
|
||||||
|
google.protobuf.Timestamp after = 3;
|
||||||
|
|
||||||
|
// Include unpublished
|
||||||
|
//
|
||||||
|
// Whether to include unpublished advisories
|
||||||
|
// apollo/impl never respects this, but internal services
|
||||||
|
// may rely on this
|
||||||
|
google.protobuf.BoolValue include_unpublished = 4;
|
||||||
|
|
||||||
|
// CVE
|
||||||
|
//
|
||||||
|
// Only return advisories with given CVE
|
||||||
|
google.protobuf.StringValue cve = 5;
|
||||||
|
|
||||||
|
// Synopsis
|
||||||
|
//
|
||||||
|
// Only return advisories if synopsis contains given text
|
||||||
|
google.protobuf.StringValue synopsis = 6;
|
||||||
|
|
||||||
|
// Include RPMs
|
||||||
|
//
|
||||||
|
// Includes RPMs in list response (slow)
|
||||||
|
google.protobuf.BoolValue include_rpms = 7;
|
||||||
|
|
||||||
|
// Keyword
|
||||||
|
//
|
||||||
|
// Searches all fields for given keyword
|
||||||
|
google.protobuf.StringValue keyword = 8;
|
||||||
|
|
||||||
|
// Severity
|
||||||
|
//
|
||||||
|
// Only return advisories with given severity
|
||||||
|
Advisory.Severity severity = 9;
|
||||||
|
|
||||||
|
// Type
|
||||||
|
//
|
||||||
|
// Only return advisories with given type
|
||||||
|
Advisory.Type type = 10;
|
||||||
|
|
||||||
|
// Fetch related
|
||||||
|
//
|
||||||
|
// Fetch all information related to the advisory (slow)
|
||||||
|
// Default: true
|
||||||
|
google.protobuf.BoolValue fetch_related = 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAdvisoriesRequest
|
||||||
|
//
|
||||||
|
// Request body for `ListAdvisories`
|
||||||
|
// All fields are optional
|
||||||
|
message ListAdvisoriesRequest {
|
||||||
|
// Filters for the given query
|
||||||
|
// No filters returns all advisories
|
||||||
|
AdvisoryFilters filters = 1;
|
||||||
|
|
||||||
|
int32 page = 2;
|
||||||
|
int32 limit = 3 [(validate.rules).int32.lte = 100];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAdvisoriesResponse
|
||||||
|
//
|
||||||
|
// Response body for `ListAdvisories`
|
||||||
|
message ListAdvisoriesResponse {
|
||||||
|
repeated Advisory advisories = 1;
|
||||||
|
|
||||||
|
// Total packages from server
|
||||||
|
int64 total = 2;
|
||||||
|
|
||||||
|
// Limit from request
|
||||||
|
int32 size = 3;
|
||||||
|
|
||||||
|
// Current page
|
||||||
|
int32 page = 4;
|
||||||
|
|
||||||
|
// Last updated
|
||||||
|
google.protobuf.Timestamp last_updated = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAdvisoriesRSSRequest
|
||||||
|
// Request body for `ListAdvisoriesRSS`
|
||||||
|
// All fields are optional
|
||||||
|
message ListAdvisoriesRSSRequest {
|
||||||
|
// Filters for the given query
|
||||||
|
// No filters returns all advisories
|
||||||
|
AdvisoryFilters filters = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdvisoryRequest
|
||||||
|
//
|
||||||
|
// Request body for `GetAdvisory`
|
||||||
|
message GetAdvisoryRequest {
|
||||||
|
// ID
|
||||||
|
//
|
||||||
|
// Errata ID
|
||||||
|
// Example: RLSA:2021-1515
|
||||||
|
string id = 1 [(validate.rules).string = {
|
||||||
|
pattern: "^(.+)([SEB]A)-([0-9]{4}):([0-9]+)$",
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdvisoryResponse
|
||||||
|
//
|
||||||
|
// Response body for `GetAdvisory`
|
||||||
|
message GetAdvisoryResponse {
|
||||||
|
Advisory advisory = 1;
|
||||||
|
}
|
13
apollo/proto/v1/build.proto
Normal file
13
apollo/proto/v1/build.proto
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package resf.apollo.v1;
|
||||||
|
|
||||||
|
option go_package = "peridot.resf.org/apollo/pb;apollopb";
|
||||||
|
|
||||||
|
enum BuildStatus {
|
||||||
|
BUILD_STATUS_UNKNOWN = 0;
|
||||||
|
BUILD_STATUS_FIXED = 1;
|
||||||
|
BUILD_STATUS_NOT_FIXED = 2;
|
||||||
|
BUILD_STATUS_WILL_NOT_FIX = 3;
|
||||||
|
BUILD_STATUS_SKIP = 4;
|
||||||
|
}
|
28
apollo/proto/v1/cve.proto
Normal file
28
apollo/proto/v1/cve.proto
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package resf.apollo.v1;
|
||||||
|
|
||||||
|
import "google/protobuf/wrappers.proto";
|
||||||
|
|
||||||
|
option go_package = "peridot.resf.org/apollo/pb;apollopb";
|
||||||
|
|
||||||
|
message CVE {
|
||||||
|
string name = 1;
|
||||||
|
|
||||||
|
google.protobuf.StringValue source_by = 2;
|
||||||
|
google.protobuf.StringValue source_link = 3;
|
||||||
|
|
||||||
|
google.protobuf.StringValue cvss3_scoring_vector = 4;
|
||||||
|
google.protobuf.StringValue cvss3_base_score = 5;
|
||||||
|
google.protobuf.StringValue cwe = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListUnresolvedCVEsRequest {}
|
||||||
|
message ListUnresolvedCVEsResponse {
|
||||||
|
repeated CVE cves = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListFixedCVEsRequest {}
|
||||||
|
message ListFixedCVEsResponse {
|
||||||
|
repeated CVE cves = 1;
|
||||||
|
}
|
14
apollo/proto/v1/fix.proto
Normal file
14
apollo/proto/v1/fix.proto
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package resf.apollo.v1;
|
||||||
|
|
||||||
|
import "google/protobuf/wrappers.proto";
|
||||||
|
|
||||||
|
option go_package = "peridot.resf.org/apollo/pb;apollopb";
|
||||||
|
|
||||||
|
message Fix {
|
||||||
|
google.protobuf.StringValue ticket = 1;
|
||||||
|
google.protobuf.StringValue source_by = 2;
|
||||||
|
google.protobuf.StringValue source_link = 3;
|
||||||
|
google.protobuf.StringValue description = 4;
|
||||||
|
}
|
29
apollo/proto/v1/short_code.proto
Normal file
29
apollo/proto/v1/short_code.proto
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package resf.apollo.v1;
|
||||||
|
|
||||||
|
option go_package = "peridot.resf.org/apollo/pb;apollopb";
|
||||||
|
|
||||||
|
message ShortCode {
|
||||||
|
// Code
|
||||||
|
//
|
||||||
|
// Full short code
|
||||||
|
string code = 1;
|
||||||
|
|
||||||
|
enum Mode {
|
||||||
|
MODE_UNKNOWN = 0;
|
||||||
|
MODE_PUBLISH = 1;
|
||||||
|
MODE_MIRROR = 2;
|
||||||
|
}
|
||||||
|
// Mode
|
||||||
|
//
|
||||||
|
// Mode for short code
|
||||||
|
// Currently only publish and mirror is supported
|
||||||
|
Mode mode = 2;
|
||||||
|
|
||||||
|
// Archived
|
||||||
|
//
|
||||||
|
// Whether the short code is archived or not
|
||||||
|
// An archived short code CANNOT be used to issue errata
|
||||||
|
bool archived = 3;
|
||||||
|
}
|
60
apollo/ui/BUILD.bazel
Normal file
60
apollo/ui/BUILD.bazel
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
load("//rules_resf:defs.bzl", "RESFDEPLOY_OUTS_BASE", "container", "peridot_k8s", "resf_frontend")
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
server_deps = ["//common/frontend_server"]
|
||||||
|
|
||||||
|
server_entrypoint = "server/index.mjs"
|
||||||
|
|
||||||
|
server_srcs = glob(["server/**/*.mjs"])
|
||||||
|
|
||||||
|
resf_frontend(
|
||||||
|
name = "apollo",
|
||||||
|
srcs = glob([
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.ts",
|
||||||
|
]),
|
||||||
|
entrypoint = "apollo/ui/src/entrypoint.tsx",
|
||||||
|
index_html = "//rules_resf/internal/resf_bundle:index_no_mobile.hbs",
|
||||||
|
server_deps = server_deps,
|
||||||
|
server_entrypoint = server_entrypoint,
|
||||||
|
server_srcs = server_srcs,
|
||||||
|
tailwind_config = "//rules_resf/internal/resf_bundle:tailwind.config.nopreflight.js",
|
||||||
|
title = "Rocky Enterprise Software Foundation Product Errata",
|
||||||
|
deps = [
|
||||||
|
"//apollo/proto/v1:client_typescript",
|
||||||
|
"//common/mui",
|
||||||
|
"//common/ui",
|
||||||
|
"//tailwind:css",
|
||||||
|
"@npm//@chakra-ui/react",
|
||||||
|
"@npm//@chakra-ui/icons",
|
||||||
|
"@npm//@emotion/unitless",
|
||||||
|
"@npm//framer-motion",
|
||||||
|
"@npm//framesync",
|
||||||
|
"@npm//popmotion",
|
||||||
|
"@npm//style-value-types",
|
||||||
|
"@npm//await-to-js",
|
||||||
|
"@npm//react",
|
||||||
|
"@npm//react-dom",
|
||||||
|
"@npm//react-router",
|
||||||
|
"@npm//react-router-dom",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
container(
|
||||||
|
base = "//bases/bazel/node",
|
||||||
|
files = [
|
||||||
|
":apollo.bundle",
|
||||||
|
],
|
||||||
|
frontend = True,
|
||||||
|
image_name = "apollo-frontend",
|
||||||
|
server_entrypoint = server_entrypoint,
|
||||||
|
server_files = server_srcs + server_deps,
|
||||||
|
)
|
||||||
|
|
||||||
|
peridot_k8s(
|
||||||
|
name = "apollo-frontend",
|
||||||
|
src = "deploy.jsonnet",
|
||||||
|
outs = RESFDEPLOY_OUTS_BASE,
|
||||||
|
deps = ["//ci"],
|
||||||
|
)
|
25
apollo/ui/deploy.jsonnet
Normal file
25
apollo/ui/deploy.jsonnet
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
local resfdeploy = import 'ci/resfdeploy.jsonnet';
|
||||||
|
local kubernetes = import 'ci/kubernetes.jsonnet';
|
||||||
|
local frontend = import 'ci/frontend.jsonnet';
|
||||||
|
|
||||||
|
local tag = std.extVar('tag');
|
||||||
|
|
||||||
|
resfdeploy.new({
|
||||||
|
name: 'apollo-frontend',
|
||||||
|
backend: false,
|
||||||
|
migrate: false,
|
||||||
|
image: kubernetes.tag($.name),
|
||||||
|
tag: tag,
|
||||||
|
env: frontend.server_env,
|
||||||
|
ports: [
|
||||||
|
{
|
||||||
|
name: 'http',
|
||||||
|
containerPort: 8086,
|
||||||
|
protocol: 'TCP',
|
||||||
|
expose: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
health: {
|
||||||
|
port: 8086,
|
||||||
|
},
|
||||||
|
})
|
61
apollo/ui/server/index.mjs
Normal file
61
apollo/ui/server/index.mjs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import server from '../../../common/frontend_server/index.mjs';
|
||||||
|
import {
|
||||||
|
svcNameHttp,
|
||||||
|
endpointHttp,
|
||||||
|
NS,
|
||||||
|
} from '../../../common/frontend_server/upstream.mjs';
|
||||||
|
|
||||||
|
export default async function run(webpackConfig) {
|
||||||
|
const devFrontendUrl = 'http://errata.pdot.localhost:9007';
|
||||||
|
const envPublicUrl = process.env['APOLLO_FRONTEND_HTTP_PUBLIC_URL'];
|
||||||
|
const frontendUrl = process.env['RESF_NS'] ? envPublicUrl : devFrontendUrl;
|
||||||
|
|
||||||
|
server({
|
||||||
|
baseURL: frontendUrl,
|
||||||
|
apis: {
|
||||||
|
'/api': {
|
||||||
|
prodApiUrl: 'http://apollo-server.apollo2production.svc.cluster.local:8000',
|
||||||
|
devApiUrl: `https://apollo-dev.internal.pdev.resf.localhost`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
port: 9007,
|
||||||
|
disableAuth: true,
|
||||||
|
webpackConfig,
|
||||||
|
}).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
run().then();
|
||||||
|
}
|
39
apollo/ui/src/api.ts
Normal file
39
apollo/ui/src/api.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as apollo from 'bazel-bin/apollo/proto/v1/client_typescript';
|
||||||
|
|
||||||
|
export const api = new apollo.ApolloServiceApi(
|
||||||
|
new apollo.Configuration({
|
||||||
|
basePath: '/api', // Points to frontend API proxy
|
||||||
|
})
|
||||||
|
);
|
521
apollo/ui/src/components/Overview.tsx
Normal file
521
apollo/ui/src/components/Overview.tsx
Normal file
|
@ -0,0 +1,521 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
AddIcon,
|
||||||
|
ArrowLeftIcon,
|
||||||
|
ArrowRightIcon,
|
||||||
|
MinusIcon,
|
||||||
|
SearchIcon,
|
||||||
|
} from '@chakra-ui/icons';
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
AlertDescription,
|
||||||
|
AlertIcon,
|
||||||
|
AlertTitle,
|
||||||
|
Box,
|
||||||
|
ButtonGroup,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
HStack,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
InputLeftElement,
|
||||||
|
Link,
|
||||||
|
Select,
|
||||||
|
Spinner,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableColumnHeaderProps,
|
||||||
|
TableContainer,
|
||||||
|
Tbody,
|
||||||
|
Td,
|
||||||
|
Text,
|
||||||
|
Th,
|
||||||
|
Thead,
|
||||||
|
Tr,
|
||||||
|
useColorModeValue,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
severityToBadge,
|
||||||
|
severityToText,
|
||||||
|
typeToText,
|
||||||
|
} from 'apollo/ui/src/enumToText';
|
||||||
|
import {
|
||||||
|
ListAdvisoriesFiltersSeverityEnum,
|
||||||
|
ListAdvisoriesFiltersTypeEnum,
|
||||||
|
} from 'bazel-bin/apollo/proto/v1/client_typescript';
|
||||||
|
import {
|
||||||
|
V1Advisory,
|
||||||
|
V1AdvisoryType,
|
||||||
|
} from 'bazel-bin/apollo/proto/v1/client_typescript/models';
|
||||||
|
import { reqap } from 'common/ui/reqap';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { api } from '../api';
|
||||||
|
import { COLOR_RESF_GREEN } from '../styles';
|
||||||
|
|
||||||
|
export const Overview = () => {
|
||||||
|
const inputBackground = useColorModeValue('white', undefined);
|
||||||
|
|
||||||
|
const tableBg = useColorModeValue('white', 'gray.800');
|
||||||
|
const pagerButtonScheme = useColorModeValue('blackAlpha', 'gray');
|
||||||
|
const linkBlue = useColorModeValue('blue.600', 'blue.300');
|
||||||
|
const linkPurple = useColorModeValue('purple.600', 'purple.300');
|
||||||
|
|
||||||
|
const [advisories, setAdvisories] = useState<V1Advisory[]>();
|
||||||
|
const [lastUpdated, setLastUpdated] = useState<Date>();
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [isError, setIsError] = useState(false);
|
||||||
|
|
||||||
|
// Request State
|
||||||
|
const [page, setPage] = useState(0);
|
||||||
|
const [pageSize, setPageSize] = useState(25);
|
||||||
|
const [filtersKeyword, setFiltersKeyword] = useState<string>();
|
||||||
|
const [filterBefore, setFilterBefore] = useState<Date>();
|
||||||
|
const [filterAfter, setFilterAfter] = useState<Date>();
|
||||||
|
const [filterProduct, setFilterProduct] = useState<string>('');
|
||||||
|
const [filtersType, setFiltersType] =
|
||||||
|
useState<keyof typeof ListAdvisoriesFiltersTypeEnum>();
|
||||||
|
const [filtersSeverity, setFiltersSeverity] =
|
||||||
|
useState<keyof typeof ListAdvisoriesFiltersSeverityEnum>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetch = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
const [err, res] = await reqap(() =>
|
||||||
|
api.listAdvisories({
|
||||||
|
page,
|
||||||
|
limit: pageSize,
|
||||||
|
filtersKeyword,
|
||||||
|
filtersFetchRelated: false,
|
||||||
|
filtersBefore: filterBefore,
|
||||||
|
filtersAfter: filterAfter,
|
||||||
|
filtersProduct: filterProduct,
|
||||||
|
filtersSeverity: filtersSeverity
|
||||||
|
? ListAdvisoriesFiltersSeverityEnum[filtersSeverity]
|
||||||
|
: undefined,
|
||||||
|
filtersType: filtersType
|
||||||
|
? ListAdvisoriesFiltersTypeEnum[filtersType]
|
||||||
|
: undefined,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
|
||||||
|
if (err || !res) {
|
||||||
|
setIsError(true);
|
||||||
|
setAdvisories(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsError(false);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
setAdvisories(res.advisories);
|
||||||
|
setLastUpdated(res.lastUpdated);
|
||||||
|
setTotal(parseInt(res.total || '0'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const timer = setTimeout(() => fetch(), 500);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [
|
||||||
|
pageSize,
|
||||||
|
page,
|
||||||
|
filtersKeyword,
|
||||||
|
filterBefore,
|
||||||
|
filterAfter,
|
||||||
|
filtersSeverity,
|
||||||
|
filterProduct,
|
||||||
|
filtersType,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// TODO: Figure out why sticky isn't sticking
|
||||||
|
const stickyProps: TableColumnHeaderProps = {
|
||||||
|
position: 'sticky',
|
||||||
|
top: '0px',
|
||||||
|
zIndex: '10',
|
||||||
|
scope: 'col',
|
||||||
|
};
|
||||||
|
|
||||||
|
const lastPage = total < pageSize ? 0 : Math.ceil(total / pageSize) - 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
w="100%"
|
||||||
|
h="100%"
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
p={4}
|
||||||
|
alignItems="stretch"
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
direction={{
|
||||||
|
sm: 'column',
|
||||||
|
lg: 'row',
|
||||||
|
}}
|
||||||
|
alignItems={{
|
||||||
|
sm: 'stretch',
|
||||||
|
lg: 'flex-end',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InputGroup>
|
||||||
|
<InputLeftElement>
|
||||||
|
<SearchIcon />
|
||||||
|
</InputLeftElement>
|
||||||
|
<Input
|
||||||
|
type="search"
|
||||||
|
aria-label="Keyword search"
|
||||||
|
placeholder="Keyword Search"
|
||||||
|
flexGrow={1}
|
||||||
|
width="200px"
|
||||||
|
variant="filled"
|
||||||
|
borderRadius="0"
|
||||||
|
backgroundColor={inputBackground}
|
||||||
|
onChange={(e) => setFiltersKeyword(e.target.value)}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
<HStack>
|
||||||
|
<FormControl width="180px" flexShrink={0} flexGrow={1}>
|
||||||
|
<FormLabel fontSize="sm">Type</FormLabel>
|
||||||
|
<Select
|
||||||
|
aria-label="Type"
|
||||||
|
placeholder="All"
|
||||||
|
variant="filled"
|
||||||
|
background={inputBackground}
|
||||||
|
borderRadius="0"
|
||||||
|
value={filtersType}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.currentTarget.value !== 'Security') {
|
||||||
|
setFiltersSeverity(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFiltersType(
|
||||||
|
e.currentTarget
|
||||||
|
.value as keyof typeof ListAdvisoriesFiltersTypeEnum
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.keys(ListAdvisoriesFiltersTypeEnum)
|
||||||
|
.sort((a, b) => a.localeCompare(b))
|
||||||
|
.filter((a) => a !== 'Unknown')
|
||||||
|
.map((s) => (
|
||||||
|
<option key={s} value={s}>
|
||||||
|
{s}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
{filtersType === 'Security' && (
|
||||||
|
<FormControl width="180px" flexShrink={0} flexGrow={1}>
|
||||||
|
<FormLabel fontSize="sm">Severity</FormLabel>
|
||||||
|
<Select
|
||||||
|
aria-label="Severity"
|
||||||
|
placeholder="All"
|
||||||
|
variant="filled"
|
||||||
|
background={inputBackground}
|
||||||
|
borderRadius="0"
|
||||||
|
value={filtersSeverity}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFiltersSeverity(
|
||||||
|
e.currentTarget
|
||||||
|
.value as keyof typeof ListAdvisoriesFiltersSeverityEnum
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{Object.keys(ListAdvisoriesFiltersSeverityEnum)
|
||||||
|
.sort((a, b) => a.localeCompare(b))
|
||||||
|
.filter((a) => a !== 'Unknown')
|
||||||
|
.map((s) => (
|
||||||
|
<option key={s} value={s}>
|
||||||
|
{s}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
<FormControl width="180px" flexShrink={0} flexGrow={1}>
|
||||||
|
<FormLabel fontSize="sm">Product</FormLabel>
|
||||||
|
<Select
|
||||||
|
aria-label="Product"
|
||||||
|
placeholder="All"
|
||||||
|
variant="filled"
|
||||||
|
background={inputBackground}
|
||||||
|
borderRadius="0"
|
||||||
|
value={filterProduct}
|
||||||
|
onChange={(e) => {
|
||||||
|
setFilterProduct(e.currentTarget.value as string);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{['Rocky Linux 8', 'Rocky Linux 9'].map((s) => (
|
||||||
|
<option key={s} value={s}>{s}</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</HStack>
|
||||||
|
<HStack>
|
||||||
|
<FormControl width="180px" flexShrink={0} flexGrow={1}>
|
||||||
|
<FormLabel fontSize="sm">From</FormLabel>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
variant="filled"
|
||||||
|
background={inputBackground}
|
||||||
|
borderRadius="0"
|
||||||
|
max={
|
||||||
|
filterBefore
|
||||||
|
? filterBefore.toLocaleDateString('en-ca')
|
||||||
|
: new Date().toLocaleDateString('en-ca')
|
||||||
|
}
|
||||||
|
value={filterAfter?.toLocaleDateString('en-ca') || ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newVal = e.currentTarget.value;
|
||||||
|
console.log(newVal);
|
||||||
|
|
||||||
|
if (!newVal) {
|
||||||
|
setFilterAfter(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
const asDate = new Date(newVal);
|
||||||
|
if (!(asDate instanceof Date) || isNaN(asDate.getTime())) {
|
||||||
|
// Check value parses as a date
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [year, month, date] = newVal.split('-').map(Number);
|
||||||
|
|
||||||
|
setFilterAfter(new Date(year, month - 1, date));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl width="180px" flexShrink={0} flexGrow={1}>
|
||||||
|
<FormLabel fontSize="sm">To</FormLabel>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
variant="filled"
|
||||||
|
background={inputBackground}
|
||||||
|
borderRadius="0"
|
||||||
|
min={filterAfter?.toLocaleDateString('en-ca')}
|
||||||
|
max={new Date().toLocaleDateString('en-ca')}
|
||||||
|
value={filterBefore?.toLocaleDateString('en-ca') || ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newVal = e.currentTarget.value;
|
||||||
|
|
||||||
|
if (!newVal) {
|
||||||
|
setFilterBefore(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
const asDate = new Date(newVal);
|
||||||
|
if (!(asDate instanceof Date) || isNaN(asDate.getTime())) {
|
||||||
|
// Check value parses as a date
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [year, month, date] = newVal.split('-').map(Number);
|
||||||
|
|
||||||
|
setFilterBefore(
|
||||||
|
new Date(year, month - 1, date, 23, 59, 59, 59) // Set to 1ms prior to midnight to be inclusive of selected date
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</HStack>
|
||||||
|
</Stack>
|
||||||
|
<HStack my={4} justifyContent="space-between" flexWrap="wrap">
|
||||||
|
<Text fontStyle="italic" fontSize="xs">
|
||||||
|
Last updated {lastUpdated?.toLocaleString() || 'never'}
|
||||||
|
</Text>
|
||||||
|
<HStack>
|
||||||
|
<Text fontSize="xs">
|
||||||
|
Displaying {(page * pageSize + 1).toLocaleString()}-
|
||||||
|
{Math.min(total, page * pageSize + pageSize).toLocaleString()} of{' '}
|
||||||
|
{total.toLocaleString()}
|
||||||
|
</Text>
|
||||||
|
<ButtonGroup
|
||||||
|
size="xs"
|
||||||
|
isAttached
|
||||||
|
alignItems="stretch"
|
||||||
|
colorScheme={pagerButtonScheme}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
aria-label="First Page"
|
||||||
|
icon={<ArrowLeftIcon fontSize="8px" />}
|
||||||
|
disabled={page <= 0}
|
||||||
|
onClick={() => setPage(0)}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Previous Page"
|
||||||
|
icon={<MinusIcon fontSize="8px" />}
|
||||||
|
disabled={page <= 0}
|
||||||
|
onClick={() => setPage((old) => old - 1)}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
fontSize="xs"
|
||||||
|
// borderTop="1px solid"
|
||||||
|
// borderBottom="1px solid"
|
||||||
|
borderColor="gray.200"
|
||||||
|
backgroundColor={tableBg}
|
||||||
|
lineHeight="24px"
|
||||||
|
px={2}
|
||||||
|
>
|
||||||
|
{(page + 1).toLocaleString()} / {(lastPage + 1).toLocaleString()}
|
||||||
|
</Text>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Next Page"
|
||||||
|
icon={<AddIcon fontSize="8px" />}
|
||||||
|
disabled={page >= lastPage}
|
||||||
|
onClick={() => setPage((old) => old + 1)}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Last Page"
|
||||||
|
icon={<ArrowRightIcon fontSize="8px" />}
|
||||||
|
disabled={page >= lastPage}
|
||||||
|
onClick={() => setPage(lastPage)}
|
||||||
|
/>
|
||||||
|
</ButtonGroup>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
{isLoading ? (
|
||||||
|
<Spinner
|
||||||
|
m="auto"
|
||||||
|
size="xl"
|
||||||
|
alignSelf="center"
|
||||||
|
color={COLOR_RESF_GREEN}
|
||||||
|
thickness="3px"
|
||||||
|
/>
|
||||||
|
) : isError ? (
|
||||||
|
<Alert
|
||||||
|
status="error"
|
||||||
|
m="auto"
|
||||||
|
flexDirection="column"
|
||||||
|
width="300px"
|
||||||
|
borderRadius="md"
|
||||||
|
>
|
||||||
|
<AlertIcon mr="0" />
|
||||||
|
<AlertTitle>Something has gone wrong</AlertTitle>
|
||||||
|
<AlertDescription>Failed to load errata</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
) : (
|
||||||
|
<Box backgroundColor={tableBg} boxShadow="base">
|
||||||
|
<TableContainer>
|
||||||
|
<Table size="sm" variant="striped">
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th {...stickyProps} width="36px" />
|
||||||
|
<Th {...stickyProps}>Advisory</Th>
|
||||||
|
<Th {...stickyProps}>Synopsis</Th>
|
||||||
|
<Th {...stickyProps}>Type / Severity</Th>
|
||||||
|
<Th {...stickyProps}>Products</Th>
|
||||||
|
<Th {...stickyProps}>Issue Date</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{!advisories?.length && (
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={6} textAlign="center">
|
||||||
|
<Text>No rows found</Text>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)}
|
||||||
|
{advisories?.map((a, idx) => (
|
||||||
|
<Tr key={a.name}>
|
||||||
|
<Td textAlign="center" pr={0}>
|
||||||
|
{severityToBadge(a.severity, a.type)}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Link
|
||||||
|
as={RouterLink}
|
||||||
|
to={`/${a.name}`}
|
||||||
|
color={linkBlue}
|
||||||
|
_visited={{ color: linkPurple }}
|
||||||
|
>
|
||||||
|
{a.name}
|
||||||
|
</Link>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{a.synopsis?.replace(
|
||||||
|
/^(Critical|Important|Moderate|Low): /,
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{typeToText(a.type)}
|
||||||
|
{a.type === V1AdvisoryType.Security
|
||||||
|
? ` / ${severityToText(a.severity)}`
|
||||||
|
: ''}
|
||||||
|
</Td>
|
||||||
|
<Td>{a.affectedProducts?.join(', ')}</Td>
|
||||||
|
<Td>
|
||||||
|
{Intl.DateTimeFormat(undefined, {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
}).format(a.publishedAt)}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<HStack justifyContent="flex-end" mt={4}>
|
||||||
|
<Text as="label" htmlFor="row-count" fontSize="sm">
|
||||||
|
Rows per page:
|
||||||
|
</Text>
|
||||||
|
<Select
|
||||||
|
id="row-count"
|
||||||
|
name="row-count"
|
||||||
|
variant="filled"
|
||||||
|
backgroundColor={inputBackground}
|
||||||
|
width="100px"
|
||||||
|
size="sm"
|
||||||
|
value={pageSize}
|
||||||
|
onChange={(e) => {
|
||||||
|
setPage(0);
|
||||||
|
setPageSize(Number(e.currentTarget.value));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[10, 25, 50, 100].map((count) => (
|
||||||
|
<option key={count} value={count}>
|
||||||
|
{count.toLocaleString()}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</HStack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
150
apollo/ui/src/components/Root.tsx
Normal file
150
apollo/ui/src/components/Root.tsx
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { MoonIcon, SunIcon } from '@chakra-ui/icons';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
HStack,
|
||||||
|
Text,
|
||||||
|
Link as ChakraLink,
|
||||||
|
useColorMode,
|
||||||
|
IconButton,
|
||||||
|
useColorModeValue,
|
||||||
|
DarkMode,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { RESFLogo } from 'common/ui/RESFLogo';
|
||||||
|
import React from 'react';
|
||||||
|
import { Route, Switch } from 'react-router';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { COLOR_RESF_BLUE, COLOR_RESF_GREEN } from '../styles';
|
||||||
|
import { Overview } from './Overview';
|
||||||
|
import { ShowErrata } from './ShowErrata';
|
||||||
|
|
||||||
|
export const Root = () => {
|
||||||
|
const { colorMode, toggleColorMode } = useColorMode();
|
||||||
|
|
||||||
|
const SwitchIcon = useColorModeValue(MoonIcon, SunIcon);
|
||||||
|
const bodyBg = useColorModeValue('gray.100', 'gray.900');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
width="100%"
|
||||||
|
minHeight="100vh"
|
||||||
|
flexDirection="column"
|
||||||
|
alignItems="stretch"
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
as="header"
|
||||||
|
background={`linear-gradient(to bottom right, ${COLOR_RESF_GREEN}, ${COLOR_RESF_BLUE})`}
|
||||||
|
display="flex"
|
||||||
|
flexDirection="row"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="space-between"
|
||||||
|
py="1"
|
||||||
|
px={4}
|
||||||
|
>
|
||||||
|
<Link to="/" className="no-underline text-white">
|
||||||
|
<HStack flexGrow={1} height="90%" spacing="2">
|
||||||
|
<RESFLogo className="fill-current text-white" />
|
||||||
|
<Text
|
||||||
|
as="h1"
|
||||||
|
borderLeft="1px solid"
|
||||||
|
pl="2"
|
||||||
|
lineHeight="30px"
|
||||||
|
fontSize="xl"
|
||||||
|
fontWeight="300"
|
||||||
|
color="white"
|
||||||
|
whiteSpace="nowrap"
|
||||||
|
>
|
||||||
|
Product Errata
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
</Link>
|
||||||
|
<DarkMode>
|
||||||
|
<IconButton
|
||||||
|
size="md"
|
||||||
|
fontSize="lg"
|
||||||
|
aria-label={`Switch to ${
|
||||||
|
colorMode === 'light' ? 'dark' : 'light'
|
||||||
|
} mode`}
|
||||||
|
variant="ghost"
|
||||||
|
onClick={toggleColorMode}
|
||||||
|
icon={<SwitchIcon />}
|
||||||
|
/>
|
||||||
|
</DarkMode>
|
||||||
|
</Box>
|
||||||
|
<Box as="main" flexGrow={1} overflow="auto" background={bodyBg}>
|
||||||
|
<Switch>
|
||||||
|
<Route path="/" exact component={Overview} />
|
||||||
|
<Route path="/:id" component={ShowErrata} />
|
||||||
|
</Switch>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
as="footer"
|
||||||
|
px="4"
|
||||||
|
// backgroundColor="#10859E"
|
||||||
|
background={`linear-gradient(to top left, ${COLOR_RESF_GREEN}, ${COLOR_RESF_BLUE})`}
|
||||||
|
color="white"
|
||||||
|
display="flex"
|
||||||
|
height="50px"
|
||||||
|
>
|
||||||
|
<ChakraLink
|
||||||
|
href="/api/v2/advisories:rss"
|
||||||
|
isExternal
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
my="auto"
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
as="svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="18px"
|
||||||
|
height="18px"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
display="block"
|
||||||
|
mr={1.5}
|
||||||
|
>
|
||||||
|
<path d="M4 11a9 9 0 019 9M4 4a16 16 0 0116 16" />
|
||||||
|
<circle cx="5" cy="19" r="1" />
|
||||||
|
</Box>
|
||||||
|
<span>RSS</span>
|
||||||
|
</ChakraLink>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
343
apollo/ui/src/components/ShowErrata.tsx
Normal file
343
apollo/ui/src/components/ShowErrata.tsx
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
AlertDescription,
|
||||||
|
AlertIcon,
|
||||||
|
AlertTitle,
|
||||||
|
Box,
|
||||||
|
Breadcrumb,
|
||||||
|
BreadcrumbItem,
|
||||||
|
BreadcrumbLink,
|
||||||
|
Heading,
|
||||||
|
HStack,
|
||||||
|
Link,
|
||||||
|
ListItem,
|
||||||
|
Spinner,
|
||||||
|
Tab,
|
||||||
|
TabList,
|
||||||
|
TabPanel,
|
||||||
|
TabPanels,
|
||||||
|
Tabs,
|
||||||
|
Text,
|
||||||
|
UnorderedList,
|
||||||
|
useColorModeValue,
|
||||||
|
VStack,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
severityToBadge,
|
||||||
|
severityToText,
|
||||||
|
typeToText,
|
||||||
|
} from 'apollo/ui/src/enumToText';
|
||||||
|
import {
|
||||||
|
V1Advisory,
|
||||||
|
V1AdvisoryType,
|
||||||
|
} from 'bazel-bin/apollo/proto/v1/client_typescript';
|
||||||
|
import { reqap } from 'common/ui/reqap';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { RouteComponentProps } from 'react-router';
|
||||||
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { api } from '../api';
|
||||||
|
import { COLOR_RESF_GREEN } from '../styles';
|
||||||
|
|
||||||
|
interface ShowErrataParams {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShowErrataProps
|
||||||
|
extends RouteComponentProps<ShowErrataParams> {}
|
||||||
|
|
||||||
|
export const ShowErrata = (props: ShowErrataProps) => {
|
||||||
|
const id = props.match.params.id;
|
||||||
|
|
||||||
|
const cardBg = useColorModeValue('white', 'gray.800');
|
||||||
|
const sideBg = useColorModeValue('gray.100', 'gray.700');
|
||||||
|
const linkBlue = useColorModeValue('blue.600', 'blue.300');
|
||||||
|
const linkPurple = useColorModeValue('purple.600', 'purple.300');
|
||||||
|
|
||||||
|
const [errata, setErrata] = useState<V1Advisory>();
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [isError, setIsError] = useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const fetch = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const [err, res] = await reqap(() => api.getAdvisory({ id }));
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
|
||||||
|
if (err || !res) {
|
||||||
|
setIsError(true);
|
||||||
|
setErrata(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsError(false);
|
||||||
|
|
||||||
|
setErrata(res.advisory);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
w="100%"
|
||||||
|
h="100%"
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
p={4}
|
||||||
|
alignItems="stretch"
|
||||||
|
maxWidth="1300px"
|
||||||
|
m="auto"
|
||||||
|
>
|
||||||
|
<Breadcrumb mb={4}>
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink as={RouterLink} to="/">
|
||||||
|
Product Errata
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink isCurrentPage>{id}</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
{isLoading ? (
|
||||||
|
<Spinner
|
||||||
|
m="auto"
|
||||||
|
size="xl"
|
||||||
|
alignSelf="center"
|
||||||
|
color={COLOR_RESF_GREEN}
|
||||||
|
thickness="3px"
|
||||||
|
/>
|
||||||
|
) : isError ? (
|
||||||
|
<Alert
|
||||||
|
status="error"
|
||||||
|
m="auto"
|
||||||
|
flexDirection="column"
|
||||||
|
width="300px"
|
||||||
|
borderRadius="md"
|
||||||
|
>
|
||||||
|
<AlertIcon mr="0" />
|
||||||
|
<AlertTitle>Something has gone wrong</AlertTitle>
|
||||||
|
<AlertDescription>Failed to load errata</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
) : (
|
||||||
|
errata && (
|
||||||
|
<>
|
||||||
|
<HStack
|
||||||
|
alignItems="center"
|
||||||
|
backgroundColor={cardBg}
|
||||||
|
py="2"
|
||||||
|
px="4"
|
||||||
|
spacing="6"
|
||||||
|
mb={2}
|
||||||
|
>
|
||||||
|
{severityToBadge(errata.severity, errata.type, 40)}
|
||||||
|
<VStack alignItems="stretch" spacing="0" flexGrow={1}>
|
||||||
|
<HStack justifyContent="space-between">
|
||||||
|
<Text fontSize="lg" fontWeight="bold">
|
||||||
|
{errata.name}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
<Text fontSize="sm">{errata.synopsis}</Text>
|
||||||
|
</VStack>
|
||||||
|
</HStack>
|
||||||
|
<Tabs backgroundColor={cardBg} p="2">
|
||||||
|
<TabList>
|
||||||
|
<Tab>Erratum</Tab>
|
||||||
|
<Tab>Affected Packages</Tab>
|
||||||
|
</TabList>
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
flexDir="row"
|
||||||
|
alignItems="stretch"
|
||||||
|
flexWrap="wrap"
|
||||||
|
justifyContent="space-between"
|
||||||
|
>
|
||||||
|
<TabPanels maxWidth="850px" px="2">
|
||||||
|
<TabPanel>
|
||||||
|
<Heading as="h2" size="md">
|
||||||
|
Topic
|
||||||
|
</Heading>
|
||||||
|
{errata.topic?.split('\n').map((p, i) => (
|
||||||
|
<Text key={i} mt={2}>
|
||||||
|
{p}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
<Heading as="h2" size="md" mt={4}>
|
||||||
|
Description
|
||||||
|
</Heading>
|
||||||
|
{errata.description?.split('\n').map((p, i) => (
|
||||||
|
<Text key={i} mt={2}>
|
||||||
|
{p}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
<VStack alignItems="flex-start" spacing="6">
|
||||||
|
{Object.keys(errata.rpms || {}).map((product) => (
|
||||||
|
<div key={product}>
|
||||||
|
<Heading as="h2" size="lg" mb={4} fontWeight="300">
|
||||||
|
{product}
|
||||||
|
</Heading>
|
||||||
|
<Heading as="h3" size="md" mt={2}>
|
||||||
|
SRPMs
|
||||||
|
</Heading>
|
||||||
|
<UnorderedList pl="4">
|
||||||
|
{errata.rpms?.[product]?.nvras
|
||||||
|
?.filter((x) => x.indexOf('.src.rpm') !== -1)
|
||||||
|
.map((x) => (
|
||||||
|
<ListItem key={x}>{x}</ListItem>
|
||||||
|
))}
|
||||||
|
</UnorderedList>
|
||||||
|
<Heading as="h3" size="md" mt={2}>
|
||||||
|
RPMs
|
||||||
|
</Heading>
|
||||||
|
<UnorderedList pl="4">
|
||||||
|
{errata.rpms?.[product]?.nvras
|
||||||
|
?.filter((x) => x.indexOf('.src.rpm') === -1)
|
||||||
|
.map((x) => (
|
||||||
|
<ListItem key={x}>{x}</ListItem>
|
||||||
|
))}
|
||||||
|
</UnorderedList>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
</TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
<VStack
|
||||||
|
py="4"
|
||||||
|
px="8"
|
||||||
|
alignItems="flex-start"
|
||||||
|
minWidth="300px"
|
||||||
|
spacing="5"
|
||||||
|
flexShrink={0}
|
||||||
|
backgroundColor={sideBg}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
<b>Issued:</b> {errata.publishedAt?.toLocaleDateString()}
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
<b>Type:</b> {typeToText(errata.type)}
|
||||||
|
</Text>
|
||||||
|
{errata.type === V1AdvisoryType.Security && (
|
||||||
|
<Text>
|
||||||
|
<b>Severity:</b> {severityToText(errata.severity)}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Box>
|
||||||
|
<Text fontWeight="bold">
|
||||||
|
Affected Product
|
||||||
|
{(errata.affectedProducts?.length || 0) > 1 ? 's' : ''}
|
||||||
|
</Text>
|
||||||
|
<UnorderedList>
|
||||||
|
{errata.affectedProducts?.map((x, idx) => (
|
||||||
|
<ListItem key={idx}>{x}</ListItem>
|
||||||
|
))}
|
||||||
|
</UnorderedList>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fontWeight="bold">Fixes</Text>
|
||||||
|
<UnorderedList>
|
||||||
|
{errata.fixes?.map((x, idx) => (
|
||||||
|
<ListItem key={idx}>
|
||||||
|
<Link
|
||||||
|
href={x.sourceLink}
|
||||||
|
isExternal
|
||||||
|
color={linkBlue}
|
||||||
|
_visited={{
|
||||||
|
color: linkPurple,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{x.sourceBy} - {x.ticket}
|
||||||
|
</Link>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</UnorderedList>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fontWeight="bold">CVEs</Text>
|
||||||
|
<UnorderedList>
|
||||||
|
{!!errata.cves?.length ? (
|
||||||
|
errata.cves?.map((x, idx) => {
|
||||||
|
let text = `${x.name}${
|
||||||
|
x.sourceBy !== '' && ` (Source: ${x.sourceBy})`
|
||||||
|
}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem key={idx}>
|
||||||
|
{x.sourceLink === '' ? (
|
||||||
|
<span>{text}</span>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
href={x.sourceLink}
|
||||||
|
isExternal
|
||||||
|
color={linkBlue}
|
||||||
|
_visited={{
|
||||||
|
color: linkPurple,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<ListItem>No CVEs</ListItem>
|
||||||
|
)}
|
||||||
|
</UnorderedList>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fontWeight="bold">References</Text>
|
||||||
|
<UnorderedList>
|
||||||
|
{!!errata.references?.length ? (
|
||||||
|
errata.references?.map((x, idx) => (
|
||||||
|
<ListItem key={idx}>{x}</ListItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<ListItem>No references</ListItem>
|
||||||
|
)}
|
||||||
|
</UnorderedList>
|
||||||
|
</Box>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
</Tabs>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
61
apollo/ui/src/entrypoint.tsx
Normal file
61
apollo/ui/src/entrypoint.tsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'tailwind/tailwind.css';
|
||||||
|
|
||||||
|
import { ChakraProvider, ColorModeScript } from '@chakra-ui/react';
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { Root } from './components/Root';
|
||||||
|
import theme from './theme';
|
||||||
|
|
||||||
|
export const app = () => {
|
||||||
|
ReactDOM.render(
|
||||||
|
<>
|
||||||
|
<ColorModeScript initialColorMode={theme.config.initialColorMode} />
|
||||||
|
<BrowserRouter>
|
||||||
|
<ChakraProvider theme={theme}>
|
||||||
|
<Root />
|
||||||
|
</ChakraProvider>
|
||||||
|
</BrowserRouter>
|
||||||
|
</>,
|
||||||
|
document.getElementById('root')
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
app();
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept(app);
|
||||||
|
}
|
182
apollo/ui/src/enumToText.tsx
Normal file
182
apollo/ui/src/enumToText.tsx
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Box, Tag, TagProps, Tooltip } from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
AdvisorySeverity,
|
||||||
|
V1AdvisoryType,
|
||||||
|
} from 'bazel-bin/apollo/proto/v1/client_typescript';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const severityToText = (severity?: AdvisorySeverity): string => {
|
||||||
|
switch (severity) {
|
||||||
|
case AdvisorySeverity.Critical:
|
||||||
|
return 'Critical';
|
||||||
|
case AdvisorySeverity.Important:
|
||||||
|
return 'Important';
|
||||||
|
case AdvisorySeverity.Moderate:
|
||||||
|
return 'Moderate';
|
||||||
|
case AdvisorySeverity.Low:
|
||||||
|
return 'Low';
|
||||||
|
default:
|
||||||
|
return 'None';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const severityToBadge = (
|
||||||
|
severity: AdvisorySeverity | undefined,
|
||||||
|
type: V1AdvisoryType | undefined,
|
||||||
|
size: number = 20
|
||||||
|
): React.ReactNode => {
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
label={`${typeToText(type)}${
|
||||||
|
type === V1AdvisoryType.Security ? ` / ${severityToText(severity)}` : ''
|
||||||
|
}`}
|
||||||
|
placement="top-start"
|
||||||
|
hasArrow
|
||||||
|
>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
[AdvisorySeverity.Critical]: (
|
||||||
|
<Box
|
||||||
|
as="svg"
|
||||||
|
version="1.1"
|
||||||
|
id="prefix__Layer_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlSpace="preserve"
|
||||||
|
width={`${size}px`}
|
||||||
|
height={`${size}px`}
|
||||||
|
display="inline-block"
|
||||||
|
>
|
||||||
|
<g fill="#ED1C24">
|
||||||
|
<path d="M22.2 19.2l-8.8-16c-.2-.3-.5-.6-.8-.7-.3-.1-.7-.1-1.1 0-.3.1-.6.4-.8.7l-8.8 16c-.3.5-.2 1 0 1.5.3.5.8.7 1.3.8h17.7c.3 0 .5-.1.8-.2.2-.1.4-.3.5-.6.2-.4.2-1 0-1.5zm-18.8.6L12 4.3l8.6 15.5H3.4z" />
|
||||||
|
<path d="M12 15.7c-.2 0-.4.1-.6.2-.2.2-.2.4-.2.6v.8c0 .3.2.6.4.7.3.1.6.1.8 0s.4-.4.4-.7v-.8c0-.2-.1-.4-.2-.6-.2-.1-.4-.2-.6-.2zM11.2 9v5c0 .3.2.6.4.7.3.1.6.1.8 0 .3-.1.4-.4.4-.7V9c0-.3-.2-.6-.4-.7-.3-.1-.6-.1-.8 0s-.4.4-.4.7z" />
|
||||||
|
</g>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
[AdvisorySeverity.Important]: (
|
||||||
|
<Box
|
||||||
|
as="svg"
|
||||||
|
width={`${size}px`}
|
||||||
|
height={`${size}px`}
|
||||||
|
display="inline-block"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g fill="#F47B2A">
|
||||||
|
<path d="M22.2 19.2l-8.8-16c-.2-.3-.5-.6-.8-.7-.3-.1-.7-.1-1.1 0-.3.1-.6.4-.8.7l-8.8 16c-.3.5-.2 1 0 1.5.3.5.8.7 1.3.8h17.7c.3 0 .5-.1.8-.2.2-.1.4-.3.5-.6.2-.4.2-1 0-1.5zm-18.8.6L12 4.3l8.6 15.5H3.4z" />
|
||||||
|
<path d="M12 15.7c-.2 0-.4.1-.6.2-.2.2-.2.4-.2.6v.8c0 .3.2.6.4.7.3.1.6.1.8 0s.4-.4.4-.7v-.8c0-.2-.1-.4-.2-.6-.2-.1-.4-.2-.6-.2zM11.2 9v5c0 .3.2.6.4.7.3.1.6.1.8 0 .3-.1.4-.4.4-.7V9c0-.3-.2-.6-.4-.7-.3-.1-.6-.1-.8 0s-.4.4-.4.7z" />
|
||||||
|
</g>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
[AdvisorySeverity.Moderate]: (
|
||||||
|
<Box
|
||||||
|
as="svg"
|
||||||
|
width={`${size}px`}
|
||||||
|
height={`${size}px`}
|
||||||
|
display="inline-block"
|
||||||
|
version="1.1"
|
||||||
|
id="prefix__Layer_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlSpace="preserve"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="#ffc31a"
|
||||||
|
d="M22.2 19.2l-8.8-16c-.2-.3-.5-.6-.8-.7-.3-.1-.7-.1-1.1 0-.3.1-.6.4-.8.7l-8.8 16c-.3.5-.2 1 0 1.5.3.5.8.7 1.3.8h17.7c.3 0 .5-.1.8-.2.2-.1.4-.3.5-.6.2-.4.2-1 0-1.5zm-18.8.6L12 4.3l8.6 15.5H3.4z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#ffc31a"
|
||||||
|
d="M12 15.7c-.2 0-.4.1-.6.2-.2.2-.2.4-.2.6v.8c0 .3.2.6.4.7.3.1.6.1.8 0s.4-.4.4-.7v-.8c0-.2-.1-.4-.2-.6-.2-.1-.4-.2-.6-.2zM11.2 9v5c0 .3.2.6.4.7.3.1.6.1.8 0 .3-.1.4-.4.4-.7V9c0-.3-.2-.6-.4-.7-.3-.1-.6-.1-.8 0s-.4.4-.4.7z"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
[AdvisorySeverity.Low]: (
|
||||||
|
<Box
|
||||||
|
as="svg"
|
||||||
|
width={`${size}px`}
|
||||||
|
height={`${size}px`}
|
||||||
|
display="inline-block"
|
||||||
|
version="1.1"
|
||||||
|
id="prefix__Layer_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlSpace="preserve"
|
||||||
|
>
|
||||||
|
<g fill="#39B54A">
|
||||||
|
<path d="M22.2 19.2l-8.8-16c-.2-.3-.5-.6-.8-.7-.3-.1-.7-.1-1.1 0-.3.1-.6.4-.8.7l-8.8 16c-.3.5-.2 1 0 1.5.3.5.8.7 1.3.8h17.7c.3 0 .5-.1.8-.2.2-.1.4-.3.5-.6.2-.4.2-1 0-1.5zm-18.8.6L12 4.3l8.6 15.5H3.4z" />
|
||||||
|
<path d="M12 15.7c-.2 0-.4.1-.6.2-.2.2-.2.4-.2.6v.8c0 .3.2.6.4.7.3.1.6.1.8 0s.4-.4.4-.7v-.8c0-.2-.1-.4-.2-.6-.2-.1-.4-.2-.6-.2zM11.2 9v5c0 .3.2.6.4.7.3.1.6.1.8 0 .3-.1.4-.4.4-.7V9c0-.3-.2-.6-.4-.7-.3-.1-.6-.1-.8 0s-.4.4-.4.7z" />
|
||||||
|
</g>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
[AdvisorySeverity.Unknown]: (
|
||||||
|
<Box
|
||||||
|
as="svg"
|
||||||
|
width={`${size}px`}
|
||||||
|
height={`${size}px`}
|
||||||
|
display="inline-block"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g fill="#009444">
|
||||||
|
<path d="M22 5.6c0-.2 0-.3-.1-.4s-.2-.2-.4-.3L12.3 2h-.5L2.5 4.9c-.1 0-.3.1-.4.2-.1.2-.1.3-.1.5C2 6 1.6 15 6 19.5c.8.8 1.7 1.5 2.8 1.9 1 .4 2.2.6 3.3.6s2.2-.2 3.3-.6c1-.4 2-1.1 2.7-1.9C22.4 14.9 22 5.9 22 5.6zm-5 12.9c-1.3 1.4-3.1 2.1-5 2.1s-3.7-.7-5-2.1C3.6 15 3.4 8 3.4 6.1L12 3.4l8.6 2.7c0 1.9-.2 8.9-3.6 12.4z" />
|
||||||
|
<path d="M5.4 7c-.2 0-.3.1-.4.3-.1.1-.1.3-.1.4.1 2.1.6 7.2 3.1 9.8 1 1.1 2.4 1.7 3.9 1.6h.1c1.5 0 2.9-.6 3.9-1.6 2.5-2.6 3-7.7 3.2-9.8 0-.2 0-.3-.1-.4s-.2-.2-.4-.3l-6.4-2h-.4L5.4 7zm12.3 1.2c-.2 2.1-.7 6.3-2.7 8.3-.8.8-1.8 1.2-2.9 1.2H12c-1.1 0-2.1-.4-2.9-1.2-2-2.1-2.5-6.2-2.7-8.3L12 6.5l5.7 1.7z" />
|
||||||
|
<path d="M8.9 12.5l1.4 2.1c.1.2.3.3.6.3.2 0 .4-.1.5-.3l3.6-4.3c.2-.2.2-.5.1-.7-.1-.2-.3-.4-.5-.5-.3 0-.5.1-.7.2L11 13l-.9-1.3c-.1-.2-.3-.3-.5-.3s-.4 0-.6.1c-.2.1-.3.3-.3.5 0 .1.1.3.2.5z" />
|
||||||
|
</g>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
}[severity || AdvisorySeverity.Unknown]
|
||||||
|
}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const typeToText = (type?: V1AdvisoryType): string => {
|
||||||
|
switch (type) {
|
||||||
|
case V1AdvisoryType.Bugfix:
|
||||||
|
return 'Bug Fix';
|
||||||
|
case V1AdvisoryType.Security:
|
||||||
|
return 'Security';
|
||||||
|
case V1AdvisoryType.Enhancement:
|
||||||
|
return 'Enhancement';
|
||||||
|
default:
|
||||||
|
return 'Unknown';
|
||||||
|
}
|
||||||
|
};
|
34
apollo/ui/src/styles.ts
Normal file
34
apollo/ui/src/styles.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const COLOR_RESF_GREEN = '#10B981';
|
||||||
|
export const COLOR_RESF_BLUE = '#1054B9';
|
52
apollo/ui/src/theme.ts
Normal file
52
apollo/ui/src/theme.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
|
||||||
|
* Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { extendTheme, type Theme, type ThemeConfig } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const config: ThemeConfig = {
|
||||||
|
initialColorMode: 'system',
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles: Theme['styles'] = {
|
||||||
|
global: (props) => ({
|
||||||
|
':root': {
|
||||||
|
colorScheme: props.colorMode,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const theme = extendTheme({
|
||||||
|
config,
|
||||||
|
styles,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default theme;
|
|
@ -6,7 +6,7 @@ local DSN = db.dsn('hydra');
|
||||||
|
|
||||||
{
|
{
|
||||||
image: 'quay.io/peridot/spicedb',
|
image: 'quay.io/peridot/spicedb',
|
||||||
tag: 'v0.2.16',
|
tag: 'v0.3.21',
|
||||||
legacyDb: true,
|
legacyDb: true,
|
||||||
dsn: {
|
dsn: {
|
||||||
name: 'DSN',
|
name: 'DSN',
|
||||||
|
|
|
@ -19,15 +19,6 @@ resfdeploy.new({
|
||||||
image: common.image,
|
image: common.image,
|
||||||
tag: common.tag,
|
tag: common.tag,
|
||||||
dsn: common.dsn,
|
dsn: common.dsn,
|
||||||
internal_route_options: {
|
|
||||||
headers: {
|
|
||||||
request: {
|
|
||||||
add: {
|
|
||||||
'Authorization': 'Bearer %s' % common.env[0].value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
requests: if kubernetes.prod() then {
|
requests: if kubernetes.prod() then {
|
||||||
cpu: '0.2',
|
cpu: '0.2',
|
||||||
memory: '512M',
|
memory: '512M',
|
||||||
|
|
Loading…
Reference in a new issue