Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5efde53
Squashed 'console/' content from commit aff3ec5
r2dedios May 28, 2026
bd52fdd
chore: merge cluster-iq-console repo into console/ subdirectory
r2dedios May 28, 2026
13a5d64
chore: integrate console into monorepo structure
r2dedios May 28, 2026
8a1aa1e
test(integration): fix expected error message for instance insertion
r2dedios May 28, 2026
c25ab7f
docs: update versions to v0.6 and replace scanner cronjob references …
r2dedios May 28, 2026
ab4f9b8
Merge pull request #262 from r2dedios/monorepo-merge
r2dedios May 29, 2026
4ea7d34
feat(scanner): redesign scanner as long-running gRPC service with tar…
r2dedios May 28, 2026
ec52152
fix(compose): ensure API waits for DB initialization
r2dedios May 29, 2026
01218a7
fix(scanner): add API readiness check, account seeding, and billing o…
r2dedios May 29, 2026
4deaec0
fix(api): use lightweight struct for expense update instances
r2dedios May 29, 2026
e980741
fix(db): handle NULL columns in system events and schedule models
r2dedios May 29, 2026
2126037
feat(events): add Account resource type to audit event system
r2dedios May 29, 2026
2c62af5
feat(db): add cluster and account names to schedule view
r2dedios May 29, 2026
af023e2
feat(console): replace ModalPowerManagement with ModalCreateAction
r2dedios May 29, 2026
8417c60
fix(console): fix resource path resolution and handle empty resourceId
r2dedios May 29, 2026
05af239
feat(console): add ResourceBadge and ResourceLabel with styled action…
r2dedios May 29, 2026
33c5812
feat(console): unify Target column and add resource labels to detail …
r2dedios May 29, 2026
ae821f1
refactor(db): rename triggered_by to requester and add schedule metad…
r2dedios Jun 1, 2026
f77483b
refactor(backend): rename TriggeredBy to Requester and persist schedu…
r2dedios Jun 1, 2026
752006a
test: update tests for requester rename and enriched event models
r2dedios Jun 1, 2026
26b09c4
feat(console): redesign Audit Logs and Scheduler tables
r2dedios Jun 1, 2026
3d7527a
feat(console): add Scan action dropdown to Account Details
r2dedios Jun 1, 2026
73f1d19
fix(console): align Recent Events table with Audit Logs rendering
r2dedios Jun 1, 2026
7762795
fix(console): add error handling to action create API calls
r2dedios Jun 1, 2026
0b385da
fix(lint): align struct field formatting in action models
r2dedios Jun 1, 2026
08dcd09
fix(scanner): extract magic numbers into named constants
r2dedios Jun 1, 2026
426f3c0
refactor(agent): extract dispatchActionLocked to reduce cyclomatic co…
r2dedios Jun 1, 2026
ff1ab17
fix(ci): remove depends_on and healthcheck incompatible with CI podman
r2dedios Jun 1, 2026
ecf984f
Merge pull request #264 from r2dedios/scanner-rpc-active
r2dedios Jun 1, 2026
b707454
feat(console): add auto-refresh polling and event lifecycle improveme…
r2dedios Jun 3, 2026
e955b5a
feat(db): add automatic purge of expired terminated clusters (#266)
r2dedios Jun 23, 2026
1839ff8
refactor(inventory): replace fmt.Printf printing with structured zap …
r2dedios Jun 23, 2026
8fe0a9d
fix(credentials): validate user and key fields when parsing credentia…
r2dedios Jun 23, 2026
3f0f0f3
chore(agent): replace stdlib log with zap in gRPC interceptor (#269)
r2dedios Jun 23, 2026
296c605
feat(overview): redesign overview page with charts and cost metrics (…
r2dedios Jun 24, 2026
f4ef34b
feat(console): UI improvements, bug fixes, and code quality audit (#272)
r2dedios Jun 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Editor configuration, see http://editorconfig.org
root = true

[*]
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true

# Go files use tabs
[*.go]
indent_style = tab
indent_size = 4

# Frontend files (TypeScript, JavaScript, CSS, HTML, JSON, YAML)
[*.{ts,tsx,js,jsx,css,html,json,yaml,yml}]
indent_style = space
indent_size = 2

# Makefiles require tabs
[Makefile]
indent_style = tab

# Markdown and snapshots
[*.md]
max_line_length = off
trim_trailing_whitespace = false

[*.snap]
max_line_length = off
trim_trailing_whitespace = false
51 changes: 51 additions & 0 deletions .github/workflows/build-container-images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -207,16 +207,67 @@ jobs:
run: |
podman logout quay.io

console:
runs-on: ubuntu-latest
needs: setup
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: Login to Quay.io
run: |
echo "${{ secrets.QUAY_PASSWORD }}" | podman login quay.io -u "${{ secrets.QUAY_USERNAME }}" --password-stdin

- name: Container image building
run: |
echo "Building ClusterIQ Console (${{ needs.setup.outputs.BRANCH }}/${{ needs.setup.outputs.SHA_COMMIT }})"
podman build \
--platform linux/amd64 \
--build-arg VERSION=${{ needs.setup.outputs.GIT_TAG }} \
--build-arg COMMIT=${{ needs.setup.outputs.SHA_COMMIT }} \
-t quay.io/${{ secrets.QUAY_ORG_NAME }}/cluster-iq-console:${{ needs.setup.outputs.SHA_COMMIT }} \
-f ./console/deployments/containerfiles/Containerfile ./console

- name: Pushing Hash based image
run: |
podman push quay.io/${{ secrets.QUAY_ORG_NAME }}/cluster-iq-console:${{ needs.setup.outputs.SHA_COMMIT }}

- name: Tagging and Pushing Latest Image
if: ${{ needs.setup.outputs.LATEST_TAG != '' && needs.setup.outputs.LATEST_TAG != null }}
run: |
podman tag \
quay.io/${{ secrets.QUAY_ORG_NAME }}/cluster-iq-console:${{ needs.setup.outputs.SHA_COMMIT }} \
quay.io/${{ secrets.QUAY_ORG_NAME }}/cluster-iq-console:${{ needs.setup.outputs.LATEST_TAG }}
podman push quay.io/${{ secrets.QUAY_ORG_NAME }}/cluster-iq-console:${{ needs.setup.outputs.LATEST_TAG }}

- name: Tagging and Pushing GitTag based image
if: ${{ needs.setup.outputs.GIT_TAG != '' && needs.setup.outputs.GIT_TAG != null }}
run: |
echo "Building Tagged version image: ${{ needs.setup.outputs.GIT_TAG }}"
podman tag \
quay.io/${{ secrets.QUAY_ORG_NAME }}/cluster-iq-console:${{ needs.setup.outputs.SHA_COMMIT }} \
quay.io/${{ secrets.QUAY_ORG_NAME }}/cluster-iq-console:${{ needs.setup.outputs.GIT_TAG }}
podman push quay.io/${{ secrets.QUAY_ORG_NAME }}/cluster-iq-console:${{ needs.setup.outputs.GIT_TAG }}

- name: Logout from Quay.io
run: |
podman logout quay.io

final:
runs-on: ubuntu-latest
needs:
- setup
- api
- agent
- scanner
- console
steps:
- name: Validating
run: |
podman pull quay.io/${{ secrets.QUAY_ORG_NAME }}/cluster-iq-api:${{ needs.setup.outputs.SHA_COMMIT }}
podman pull quay.io/${{ secrets.QUAY_ORG_NAME }}/cluster-iq-agent:${{ needs.setup.outputs.SHA_COMMIT }}
podman pull quay.io/${{ secrets.QUAY_ORG_NAME }}/cluster-iq-scanner:${{ needs.setup.outputs.SHA_COMMIT }}
podman pull quay.io/${{ secrets.QUAY_ORG_NAME }}/cluster-iq-console:${{ needs.setup.outputs.SHA_COMMIT }}
29 changes: 29 additions & 0 deletions .github/workflows/validate-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,35 @@ jobs:
only-new-issues: true
args: --whole-files

console-lint:
name: Console Lint
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./console
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
cache-dependency-path: console/package-lock.json

- name: Install dependencies
run: npm ci

- name: Code Prettier
run: make ts-prettier

- name: Code Linter
run: make ts-eslint

- name: TypeScript type check
run: make ts-tsc

call-unit-tests:
name: Go Unit tests
needs:
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,9 @@ go.work.sum
# IDE files
.idea/
.vscode/

# Console (frontend)
console/node_modules/
console/dist/
console/dist-ssr/
console/coverage/
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
18
17 changes: 15 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ This file provides guidance to Claude Code when working with this repository.
**ClusterIQ** is an inventory and cost estimation platform for OpenShift clusters across multi-cloud environments (currently AWS only). It provides automated discovery, cost tracking, and lifecycle management.

**Architecture Components:**
1. **Scanner**: CronJob that discovers cloud resources using "Stocker" pattern
1. **Scanner**: Long-running gRPC service that discovers cloud resources using "Stocker" pattern
2. **API Server**: REST API (Gin framework) for inventory queries and cluster operations
3. **Agent**: gRPC service handling cluster power operations (instant, scheduled, recurring)
4. **Console**: React/TypeScript web UI (PatternFly, Vite)

**Repository Structure:**
```
Expand All @@ -23,6 +24,10 @@ internal/
├── services/ # Business logic
├── api/handlers/ # HTTP handlers
└── models/ # DTO, DB, domain models
console/ # Web UI (React/TypeScript/Vite/PatternFly)
├── src/ # Application source code
├── deployments/ # Console Containerfile
└── nginx/ # NGINX config template and startup script
db/sql/ # Schema definitions (init.sql, cron.sql)
test/integration/ # Integration tests
```
Expand Down Expand Up @@ -53,6 +58,13 @@ make lint-staged # Lint staged files only
# Code Generation
make generate-converters # Goverter (DB to DTO)
make swagger-doc # OpenAPI docs

# Console (frontend)
make console-install # Install npm dependencies
make console-build # Build console locally
make console-start-dev # Vite dev server (port 3000)
make console-lint # Run prettier + eslint + tsc
make build-console # Build console container image
```

## Architecture Patterns
Expand Down Expand Up @@ -111,12 +123,13 @@ go tool cover -html=coverage.out -o coverage.html # Visual
## Development Workflow

1. Make code changes
2. Run `make lint-staged` before committing
2. Run `make lint-staged` before committing (Go), `make console-lint` (Console)
3. Run relevant tests: `make go-unit-tests`
4. For API changes: update Swagger with `make swagger-doc`
5. For DB changes: update `db/sql/init.sql` or add data migration in `doc/releases/`
6. For protobuf changes: `make local-build-agent`
7. For goverter changes: `make generate-converters`
8. For console changes: run `make console-lint` and test in browser via `make console-start-dev`

**Commit Convention:**
- Use conventional commits format: `type(scope): brief description`
Expand Down
38 changes: 36 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,14 @@ AGENT_IMG_NAME ?= $(PROJECT_NAME)-agent
AGENT_IMAGE ?= $(REGISTRY)/$(REGISTRY_REPO)/$(AGENT_IMG_NAME)
AGENT_CONTAINERFILE ?= ./$(DEPLOYMENTS_DIR)/containerfiles/Containerfile-agent
AGENT_PROTO_PATH ?= ./cmd/agent/proto/agent.proto
SCANNER_PROTO_PATH ?= ./cmd/scanner/proto/scanner.proto
PGSQL_IMG_NAME ?= $(PROJECT_NAME)-pgsql
PGSQL_IMAGE ?= $(REGISTRY)/$(REGISTRY_REPO)/$(PGSQL_IMG_NAME)
PGSQL_CONTAINERFILE ?= ./$(DEPLOYMENTS_DIR)/containerfiles/Containerfile-pgsql
CONSOLE_DIR ?= ./console
CONSOLE_IMG_NAME ?= $(PROJECT_NAME)-console
CONSOLE_IMAGE ?= $(REGISTRY)/$(REGISTRY_REPO)/$(CONSOLE_IMG_NAME)
CONSOLE_CONTAINERFILE ?= $(CONSOLE_DIR)/deployments/containerfiles/Containerfile

# Standard targets
all: ## Stop, build and start the development environment based on containers
Expand Down Expand Up @@ -77,6 +82,8 @@ local-build-api: generate-converters swagger-doc ## Build the API binary

local-build-scanner: ## Build the scanner binary
@echo "### [Building Scanner] ###"
@[ ! -d $(GENERATED_DIR) ] && { mkdir $(GENERATED_DIR); } || { exit 0; }
@$(PROTOC) --go_out=$(GENERATED_DIR) --go-grpc_out=$(GENERATED_DIR) $(SCANNER_PROTO_PATH)
@$(GO) build -o $(BIN_DIR)/scanners/scanner $(LDFLAGS) ./cmd/scanner

local-build-agent: ## Build the agent binary
Expand All @@ -89,10 +96,10 @@ local-build-agent: ## Build the agent binary
# Container based working targets
clean: ## Remove the container images
@echo "### [Cleaning Container images] ###"
@-$(CONTAINER_ENGINE) images | grep -e $(SCANNER_IMAGE) -e $(API_IMAGE) -e $(AGENT_IMAGE) -e $(PGSQL_IMAGE) | awk '{print $$3}' | xargs $(CONTAINER_ENGINE) rmi -f
@-$(CONTAINER_ENGINE) images | grep -e $(SCANNER_IMAGE) -e $(API_IMAGE) -e $(AGENT_IMAGE) -e $(PGSQL_IMAGE) -e $(CONSOLE_IMAGE) | awk '{print $$3}' | xargs $(CONTAINER_ENGINE) rmi -f

build: ## Build all container images
build: build-api build-scanner build-agent build-pgsql
build: build-api build-scanner build-agent build-pgsql build-console
build-api: generate-converters ## Build the API container image
@echo "### [Building API container image] ###"
@$(CONTAINER_ENGINE) build \
Expand Down Expand Up @@ -129,12 +136,22 @@ build-pgsql: ## Build the PGSQL container image
@$(CONTAINER_ENGINE) tag $(PGSQL_IMAGE):latest $(PGSQL_IMAGE):$(SHORT_COMMIT_HASH)
@echo "Build Successful"

build-console: ## Build the Console container image
@echo "### [Building Console container image] ###"
@$(CONTAINER_ENGINE) build \
--build-arg VERSION=$(VERSION) \
--build-arg COMMIT=$(SHORT_COMMIT_HASH) \
-t $(CONSOLE_IMAGE):latest -f $(CONSOLE_CONTAINERFILE) $(CONSOLE_DIR)
@$(CONTAINER_ENGINE) tag $(CONSOLE_IMAGE):latest $(CONSOLE_IMAGE):$(SHORT_COMMIT_HASH)
@echo "Build Successful"


# Development targets
start-dev: ## Start the container-based development environment
@echo "### [Starting dev environment] ###"
@$(CONTAINER_ENGINE)-compose -f $(DEPLOYMENTS_DIR)/compose/compose-devel.yaml up -d
@echo "### [Running dev environment] ###"
@echo "### [Console: http://localhost:8080 ] ###"
@echo "### [API: http://localhost:8081/api/v1/healthcheck ] ###"

stop-dev: ## Stop the container-based development environment
Expand Down Expand Up @@ -207,6 +224,23 @@ swagger-doc: ## Generate Swagger documentation for ClusterIQ API
@$(SWAGGER) init --generalInfo ./cmd/api/server.go --parseDependency --output ./cmd/api/docs


# Console targets (delegated to console/Makefile)
console-install: ## Install console dependencies
@$(MAKE) -C $(CONSOLE_DIR) local-install

console-build: ## Build console locally
@$(MAKE) -C $(CONSOLE_DIR) local-build

console-clean: ## Clean console build artifacts
@$(MAKE) -C $(CONSOLE_DIR) local-clean

console-start-dev: ## Start console dev server
@$(MAKE) -C $(CONSOLE_DIR) local-start-dev

console-lint: ## Run console linters (prettier + eslint + tsc)
@$(MAKE) -C $(CONSOLE_DIR) ts-test


# Set the default target to "help"
.DEFAULT_GOAL := help
# Help
Expand Down
44 changes: 35 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![Go Report Card](https://goreportcard.com/badge/github.com/RHEcosystemAppEng/cluster-iq)](https://goreportcard.com/report/github.com/RHEcosystemAppEng/cluster-iq)
[![Go Reference](https://pkg.go.dev/badge/github.com/RHEcosystemAppEng/cluster-iq.svg)](https://pkg.go.dev/github.com/RHEcosystemAppEng/cluster-iq)
![Version](https://img.shields.io/badge/version-0.5-blue)
![Version](https://img.shields.io/badge/version-0.6-blue)
![License](https://img.shields.io/badge/license-Apache--2.0-green)
---
[![Container image building](https://github.com/RHEcosystemAppEng/cluster-iq/actions/workflows/build-container-images.yaml/badge.svg)](https://github.com/RHEcosystemAppEng/cluster-iq/actions/workflows/build-container-images.yaml)
Expand All @@ -19,8 +19,8 @@ goal is to provide a continually updated inventory of clusters. This helps
users efficiently identify and manage their clusters, offering a simplified
approach to estimating costs and ensuring better resource management.

ClusterIQ has a Web UI called [ClusterIQ Console](https://github.com/RHEcosystemAppEng/cluster-iq-console).
Follow this [link](https://github.com/RHEcosystemAppEng/cluster-iq-console?tab=readme-ov-file#development-scripts) for installation instructions.
ClusterIQ includes a Web UI (Console) under the `console/` directory.
See the [Console](#console) section for development instructions.


## Supported cloud providers
Expand Down Expand Up @@ -138,11 +138,8 @@ For more information about the supported parameters, check the [Configuration Se
helm list -n $NAMESPACE
```

6. Once every pod is up and running, trigger the scanner manually for
initializing the inventory
```sh
oc create job --from=cronjob/scanner scanner-init -n $NAMESPACE
```
6. Once every pod is up and running, the scanner will automatically begin
discovering cloud resources.

### Uninstalling
To uninstall ClusterIQ Helm chart, use the following commands
Expand Down Expand Up @@ -206,7 +203,7 @@ make local-build-api
The Agent performs actions over the selected cloud resources. It only accepts
incoming requests from the API.

Currently, on release `v0.4`, the agent only supports Power On/Off clusters on AWS.
The Agent supports Power On/Off operations for clusters on AWS, including instant, scheduled, and recurring actions.

```shell
# Building in a container
Expand All @@ -216,6 +213,35 @@ make build-agent
make local-build-agent
```

## Console

The web console is a React/TypeScript application located under `console/`.
It provides the ClusterIQ Web UI with cluster inventory views, cost tracking, and action management.

### Prerequisites
- [Node.js](https://nodejs.org/) 18.x or higher
- [npm](https://www.npmjs.com/) 8.x or higher

### Development Commands
```shell
# Install console dependencies
make console-install

# Build console locally
make console-build

# Start console dev server (port 3000, proxies API to localhost:8081)
make console-start-dev

# Run console linters (prettier + eslint + tsc)
make console-lint

# Build console container image
make build-console
```

For more details, see `console/README.md`.

## Extra Documentation

The following documentation is available:
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.5
v0.6
7 changes: 3 additions & 4 deletions cmd/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"sync"
Expand Down Expand Up @@ -194,14 +193,14 @@ func LoggingInterceptor(
logger.Info("Client connected", zap.String("ip", p.Addr.String()))
}

log.Printf("Invoked method: %s", info.FullMethod)
logger.Info("Invoked method", zap.String("method", info.FullMethod))

resp, err := handler(ctx, req)

if err != nil {
log.Printf("Error in method %s: %v", info.FullMethod, err)
logger.Error("Method failed", zap.String("method", info.FullMethod), zap.Error(err))
} else {
log.Printf("Method %s executed successfully", info.FullMethod)
logger.Info("Method executed successfully", zap.String("method", info.FullMethod))
}

return resp, err
Expand Down
Loading