Skip to content

feat: phase 2 — CRDs, monitoring, port forwarding, bulk delete, and metrics improvements - #5

Merged
nuttingd merged 21 commits into
mainfrom
feat/phase2-notifications-crd-portforward
Feb 2, 2026
Merged

feat: phase 2 — CRDs, monitoring, port forwarding, bulk delete, and metrics improvements#5
nuttingd merged 21 commits into
mainfrom
feat/phase2-notifications-crd-portforward

Conversation

@nuttingd

@nuttingd nuttingd commented Feb 2, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 2 feature set for Kexplore, adding several major capabilities and quality-of-life improvements.

Custom Resource Definitions (CRDs)

  • Browse discovered CRDs via a dedicated bottom tab
  • View custom resource instances with full detail/YAML support

Cluster Monitoring & Notifications

  • Background cluster health polling with push notifications for anomalies (failed pods, unhealthy deployments, not-ready nodes)
  • Configurable notification channel

Port Forwarding

  • Port forward sessions with lifecycle management (start/stop)
  • Auto-fill form with discovered port names and defaults
  • Service targetPort resolution
  • Randomized local port assignment
  • Open-in-browser action for active forwards
  • Race condition and namespace scoping fixes

Multi-Select Bulk Delete

  • Long-press to enter selection mode on the resource list
  • Checkbox UI with tinted background for selected items
  • Select All / bulk delete with confirmation dialog
  • Back handler to exit selection mode

Metrics Improvements

  • Default poll interval changed from 5s to 2s
  • Configurable poll interval picker (1s, 2s, 5s, 10s, 30s) via FilterChips
  • Reduced lookback window from 5 minutes to 60 seconds for more responsive charts
  • Human-readable x-axis labels showing "seconds ago" (e.g., "60s", "30s", "0s")

Log Tailing Fixes

  • Replaced broken watchLog() streaming with polling-based approach using getLog() + sinceTime() deduplication — fixes logs never updating in real-time
  • Fixed CancellationException error shown when navigating away from and back to the log screen

Other

  • Updated app launcher icon and background color
  • Added MIT license
  • Moved CRD browser from drawer to bottom tab

Test plan

  • Verify CRD tab appears and lists custom resources
  • Verify notifications fire for cluster anomalies
  • Port forward a service and open in browser
  • Long-press a pod to enter selection mode, select multiple, bulk delete
  • Change metrics poll interval and verify chart updates at new rate
  • Verify x-axis labels show correct "seconds ago" values
  • Stream pod logs and verify new lines appear in real-time
  • Navigate away from logs tab and back — no error shown

Discover all CRDs on the cluster and browse/inspect their instances
using fabric8's generic resource API. Separate from the static
ResourceType enum since CRDs are dynamic.

- CrdRepository wraps fabric8 CRD discovery and generic resource APIs
- CrdDefinition and CustomResourceSummary data models
- CrdListScreen groups CRDs by API group with search
- CrdInstanceListScreen shows instances with pull-to-refresh and search
- CrdInstanceDetailScreen with Overview (flattened spec/status) and YAML tabs
- Extract YamlView composable from ResourceDetailScreen for reuse
- Add CRD navigation drawer item and routes
- MainViewModel exposes crdRepository StateFlow
Two-tier monitoring: WorkManager periodic checks (15-min baseline) +
optional ForegroundService with real-time fabric8 watches.

- ClusterAlert and AlertType model for notification events
- AlertStateStore with 1-hour dedup cooldown via SharedPreferences
- NotificationHelper creates channels and builds alert/service notifications
- ClusterMonitorWorker checks pods/nodes/deployments on 15-min schedule
- ClusterWatchService foreground service with live Kubernetes watches
- MonitoringPreferences DataStore for monitoring/real-time/alert toggles
- MonitoringSettingsScreen with toggles and POST_NOTIFICATIONS handling
- Register service in manifest with foregroundServiceType=dataSync
- Add monitoring drawer item and navigation route
Local TCP forwarding from device to cluster pods/services via fabric8's
portForward() API. Sessions persist across screen navigation via
app-scoped manager.

- PortForwardSession model with status tracking (Starting/Active/Failed/Stopped)
- PortForwardManager as app-scoped singleton with health check polling
- PortForwardService foreground service keeping process alive
- PortForwardScreen with pod/service selector, port dropdown, active session list
- Service-to-pod resolution via label selector matching
- Copy localhost:port to clipboard, stop/remove individual forwards
- TopAppBar badge showing active forward count
- Port Forward action in Pod/Service detail overflow menu
- MainViewModel exposes KubernetesClient StateFlow for forwarding
- Stop forwards for old connection on connection change
Update adaptive icon to use color resource background (#232837) instead
of bitmap, and refresh all density launcher images.
…ults

Show named ports (e.g. "8080 (http)") in pod/service/port dropdowns,
auto-select when only one resource exists, auto-fill local port to
match remote, and fix missing pre-selected service port auto-fill.
CRDs are resources like any other category, so they belong in the
bottom navigation alongside Workloads, Network, Config, Storage, and
Cluster rather than buried in the drawer with settings items.
The previous approach set savedStateHandle on the source entry but read
from the destination entry. Switch to query parameters on the route so
pre-selected pod/service values actually reach the port forward screen.
The port forward screen was hardcoded to "default" when no namespace was
selected, so only resources in the default namespace appeared in the
dropdowns. Now passes the active namespace directly (empty = all
namespaces). Also uses each resource's own namespace when starting a
forward instead of the screen-level namespace.
The foreground service monitor checked activeCount immediately on start,
but activeCount only counted Active sessions — not Starting ones. Since
the async port forward hadn't connected yet, the count was 0 and the
service stopped itself, calling stopAll() which wiped the session before
it could finish connecting.

Fix activeCount to include Starting sessions, and make stopAll() skip
sessions already in a terminal state (Failed/Stopped).
fabric8's portForward() operates on the pod and needs the container
port, not the service port. A service with port 80 -> targetPort 8080
was passing 80 to portForward(), which failed because no container
listens on 80. Now extracts targetPort from ServicePort.targetPort and
uses it for the actual forward. The dropdown shows the mapping
(e.g. "80 -> 8080 (http)") so the user sees both ports.
Android can't bind to privileged ports (<1024), so auto-filling the
local port to match the remote port (e.g. 80) caused SocketException.
Now assigns a random port in the 10000-59999 range. Also adds an
open-in-browser button on active sessions and improves error messages
to include the exception class and cause chain.
Long-press any resource to enter selection mode with checkboxes,
tap to toggle items, then bulk delete with confirmation dialog
and snackbar feedback. Selection clears on type/namespace/search
changes and back press.
Add interval picker (1s, 2s, 5s, 10s, 30s) to the metrics screen
using FilterChips. Default changed from 5s to 2s. Buffer size
adjusts dynamically to maintain a 5-minute window at any interval.
Shorten the chart window from 5 minutes to 60 seconds so data points
scroll by more visibly. X-axis now shows seconds ago (e.g. "60s",
"30s", "0s") instead of raw indices.
- Log bulk delete failures instead of silently swallowing exceptions
- Clear selection state on screen dispose to prevent stale UI
- Centralize window slot calculation in MetricsCollector companion
- Add defensive bounds checking on x-axis formatter
- Fix missing space in message parameter assignments
The previous implementation used fabric8's watchLog() which relies
on HTTP streaming that doesn't work reliably on Android — the
InputStream/OutputStream approaches both fail to deliver new log
lines after the initial tail batch.

Replace with a polling approach: fetch initial lines with
tailingLines + usingTimestamps, then poll every 2s with sinceTime
to get only new lines. Timestamps are used to deduplicate across
polls since sinceTime is inclusive.
When navigating away and back, startStreaming() cancels the old job
and starts a new one. The old job's catch(Exception) block was
catching CancellationException, setting isStreaming=false and
showing "StandaloneCoroutine was cancelled" — overwriting the new
job's state. Rethrow CancellationException so it's treated as
normal coroutine cancellation, not an error.
@nuttingd nuttingd changed the title feat: CRD browser, cluster monitoring, and port forwarding feat: phase 2 — CRDs, monitoring, port forwarding, bulk delete, and metrics improvements Feb 2, 2026
@nuttingd
nuttingd enabled auto-merge (squash) February 2, 2026 05:53
@nuttingd
nuttingd disabled auto-merge February 2, 2026 05:56
@nuttingd
nuttingd enabled auto-merge (squash) February 2, 2026 06:00
@nuttingd
nuttingd merged commit b2b711b into main Feb 2, 2026
1 check passed
@nuttingd
nuttingd deleted the feat/phase2-notifications-crd-portforward branch February 2, 2026 06:09
@github-actions

github-actions Bot commented Feb 2, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 1.2.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant