Skip to content

Commit 6ab5a40

Browse files
authored
Merge pull request #32 from PredicateSystems/web_ui
web ui
2 parents 7e74a1e + b9019da commit 6ab5a40

3,628 files changed

Lines changed: 1155967 additions & 4 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ jobs:
2121
steps:
2222
- uses: actions/checkout@v4
2323

24+
- name: Setup Node.js
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: '20'
28+
cache: 'npm'
29+
cache-dependency-path: webui/package-lock.json
30+
31+
- name: Build Web UI
32+
run: |
33+
cd webui
34+
npm ci
35+
npm run build
36+
2437
- name: Install Rust toolchain
2538
uses: dtolnay/rust-toolchain@master
2639
with:
@@ -50,6 +63,19 @@ jobs:
5063
steps:
5164
- uses: actions/checkout@v4
5265

66+
- name: Setup Node.js
67+
uses: actions/setup-node@v4
68+
with:
69+
node-version: '20'
70+
cache: 'npm'
71+
cache-dependency-path: webui/package-lock.json
72+
73+
- name: Build Web UI
74+
run: |
75+
cd webui
76+
npm ci
77+
npm run build
78+
5379
- name: Install Rust toolchain
5480
uses: dtolnay/rust-toolchain@master
5581
with:
@@ -80,6 +106,19 @@ jobs:
80106
steps:
81107
- uses: actions/checkout@v4
82108

109+
- name: Setup Node.js
110+
uses: actions/setup-node@v4
111+
with:
112+
node-version: '20'
113+
cache: 'npm'
114+
cache-dependency-path: webui/package-lock.json
115+
116+
- name: Build Web UI
117+
run: |
118+
cd webui
119+
npm ci
120+
npm run build
121+
83122
- name: Install Rust toolchain
84123
uses: dtolnay/rust-toolchain@master
85124
with:
@@ -115,6 +154,19 @@ jobs:
115154
steps:
116155
- uses: actions/checkout@v4
117156

157+
- name: Setup Node.js
158+
uses: actions/setup-node@v4
159+
with:
160+
node-version: '20'
161+
cache: 'npm'
162+
cache-dependency-path: webui/package-lock.json
163+
164+
- name: Build Web UI
165+
run: |
166+
cd webui
167+
npm ci
168+
npm run build
169+
118170
- name: Install Rust toolchain
119171
uses: dtolnay/rust-toolchain@master
120172
with:

.github/workflows/release.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,19 @@ jobs:
4242
steps:
4343
- uses: actions/checkout@v4
4444

45+
- name: Setup Node.js
46+
uses: actions/setup-node@v4
47+
with:
48+
node-version: '20'
49+
cache: 'npm'
50+
cache-dependency-path: webui/package-lock.json
51+
52+
- name: Build Web UI
53+
run: |
54+
cd webui
55+
npm ci
56+
npm run build
57+
4558
- name: Install Rust toolchain
4659
uses: dtolnay/rust-toolchain@master
4760
with:

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ anyhow = "1"
6666
parking_lot = "0.12"
6767
urlencoding = "2"
6868

69+
# Web UI (embedded static files)
70+
rust-embed = "8"
71+
mime_guess = "2"
72+
futures = "0.3"
73+
async-stream = "0.3"
74+
tokio-stream = "0.1"
75+
6976
[dev-dependencies]
7077
tokio-test = "0.4"
7178
criterion = { version = "0.5", features = ["html_reports"] }

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,40 @@ Watch authorization decisions in real-time with the built-in TUI:
7070

7171
---
7272

73+
## Web UI
74+
75+
Browser-based monitoring dashboard with real-time authorization event streaming:
76+
77+
![Web UI Screenshot](docs/images/web-ui.png)
78+
79+
```bash
80+
./predicate-authorityd --policy-file policy.json --web-ui run
81+
```
82+
83+
On startup, a secure URL is printed to the terminal:
84+
85+
```
86+
Web UI enabled: http://127.0.0.1:8787/ui/?token=a1b2c3d4e5f6...
87+
```
88+
89+
**Features:**
90+
- **Split-pane layout:** Policy viewer on the left, live event feed on the right
91+
- **Real-time streaming:** Events appear instantly via Server-Sent Events (SSE)
92+
- **Color-coded results:** Green for ALLOW, red for DENY
93+
- **Event filtering:** Filter by principal, action, or result type
94+
- **Statistics:** Total allowed/denied counts and average latency
95+
- **Copy URL button:** Share the dashboard URL (includes auth token)
96+
- **Connection status:** Visual indicator when SSE connection is active
97+
98+
**Security:**
99+
- Token-based authentication (32-character random token)
100+
- Token stored in `sessionStorage` (cleared on tab close)
101+
- URL cleaned after token extraction (no token in history)
102+
103+
The `--web-ui` flag works with both `run` and `dashboard` commands. When used with `dashboard`, both the TUI and Web UI run simultaneously.
104+
105+
---
106+
73107
## Quick Start
74108

75109
**30 seconds to your first authorization decision.**

build.sh

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Build script for predicate-authorityd with embedded Web UI
4+
#
5+
# Usage:
6+
# ./build.sh # Build release binary
7+
# ./build.sh --debug # Build debug binary
8+
# ./build.sh --clean # Clean and rebuild everything
9+
#
10+
11+
set -euo pipefail
12+
13+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14+
cd "$SCRIPT_DIR"
15+
16+
# Colors for output
17+
RED='\033[0;31m'
18+
GREEN='\033[0;32m'
19+
YELLOW='\033[1;33m'
20+
NC='\033[0m' # No Color
21+
22+
log_info() {
23+
echo -e "${GREEN}[INFO]${NC} $1"
24+
}
25+
26+
log_warn() {
27+
echo -e "${YELLOW}[WARN]${NC} $1"
28+
}
29+
30+
log_error() {
31+
echo -e "${RED}[ERROR]${NC} $1"
32+
}
33+
34+
# Parse arguments
35+
BUILD_MODE="release"
36+
CLEAN=false
37+
38+
for arg in "$@"; do
39+
case $arg in
40+
--debug)
41+
BUILD_MODE="debug"
42+
;;
43+
--clean)
44+
CLEAN=true
45+
;;
46+
--help|-h)
47+
echo "Usage: $0 [OPTIONS]"
48+
echo ""
49+
echo "Options:"
50+
echo " --debug Build debug binary instead of release"
51+
echo " --clean Clean and rebuild everything"
52+
echo " --help Show this help message"
53+
exit 0
54+
;;
55+
*)
56+
log_error "Unknown option: $arg"
57+
exit 1
58+
;;
59+
esac
60+
done
61+
62+
# Clean if requested
63+
if [ "$CLEAN" = true ]; then
64+
log_info "Cleaning previous builds..."
65+
rm -rf webui/dist webui/node_modules target
66+
fi
67+
68+
# Check for required tools
69+
log_info "Checking prerequisites..."
70+
71+
if ! command -v node &> /dev/null; then
72+
log_error "Node.js is not installed. Please install Node.js 18+ first."
73+
exit 1
74+
fi
75+
76+
if ! command -v npm &> /dev/null; then
77+
log_error "npm is not installed. Please install npm first."
78+
exit 1
79+
fi
80+
81+
if ! command -v cargo &> /dev/null; then
82+
log_error "Rust/Cargo is not installed. Please install Rust first."
83+
exit 1
84+
fi
85+
86+
NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
87+
if [ "$NODE_VERSION" -lt 18 ]; then
88+
log_warn "Node.js version $NODE_VERSION detected. Recommended: 18+"
89+
fi
90+
91+
# Step 1: Build Web UI
92+
log_info "Building Web UI (React + Vite)..."
93+
cd webui
94+
95+
if [ ! -d "node_modules" ]; then
96+
log_info "Installing npm dependencies..."
97+
npm ci
98+
fi
99+
100+
npm run build
101+
102+
if [ ! -f "dist/index.html" ]; then
103+
log_error "Web UI build failed - dist/index.html not found"
104+
exit 1
105+
fi
106+
107+
log_info "Web UI built successfully ($(du -sh dist | cut -f1))"
108+
cd ..
109+
110+
# Step 2: Touch static_files.rs to force re-embed
111+
# This ensures rust-embed picks up the new dist files
112+
touch src/webui/static_files.rs
113+
114+
# Step 3: Build Rust binary
115+
log_info "Building Rust binary ($BUILD_MODE mode)..."
116+
117+
if [ "$BUILD_MODE" = "release" ]; then
118+
cargo build --release
119+
BINARY_PATH="target/release/predicate-authorityd"
120+
else
121+
cargo build
122+
BINARY_PATH="target/debug/predicate-authorityd"
123+
fi
124+
125+
if [ ! -f "$BINARY_PATH" ]; then
126+
log_error "Rust build failed - binary not found"
127+
exit 1
128+
fi
129+
130+
BINARY_SIZE=$(du -h "$BINARY_PATH" | cut -f1)
131+
log_info "Build complete!"
132+
echo ""
133+
echo "Binary: $BINARY_PATH ($BINARY_SIZE)"
134+
echo ""
135+
echo "Run with Web UI:"
136+
echo " $BINARY_PATH --policy-file policies/strict.json --web-ui run"

docs/images/web-ui.png

136 KB
Loading

src/http/mod.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use axum::{
1616
use serde::{Deserialize, Serialize};
1717
use std::collections::HashMap;
1818
use std::sync::Arc;
19+
use tokio::sync::watch;
1920
use tower_http::cors::{Any, CorsLayer};
2021
use tracing::{debug, info, warn};
2122

@@ -45,6 +46,12 @@ pub struct AppState {
4546
pub policy_reload_secret: Option<String>,
4647
/// Whether /policy/reload endpoint is disabled
4748
pub policy_reload_disabled: bool,
49+
/// Web UI authentication token (if web UI is enabled)
50+
pub web_ui_token: Option<String>,
51+
/// Policy file path (for Web UI to read raw policy)
52+
pub policy_file_path: Option<std::path::PathBuf>,
53+
/// Shutdown signal receiver for SSE streams
54+
pub shutdown_rx: Option<watch::Receiver<bool>>,
4855
}
4956

5057
impl AppState {
@@ -61,6 +68,9 @@ impl AppState {
6168
identity_mode: "local".to_string(),
6269
policy_reload_secret: None,
6370
policy_reload_disabled: false,
71+
web_ui_token: None,
72+
policy_file_path: None,
73+
shutdown_rx: None,
6474
}
6575
}
6676

@@ -94,6 +104,21 @@ impl AppState {
94104
self.mandate_store = Some(Arc::new(mandate_store));
95105
self
96106
}
107+
108+
pub fn with_web_ui_token(mut self, token: Option<String>) -> Self {
109+
self.web_ui_token = token;
110+
self
111+
}
112+
113+
pub fn with_policy_file_path(mut self, path: Option<std::path::PathBuf>) -> Self {
114+
self.policy_file_path = path;
115+
self
116+
}
117+
118+
pub fn with_shutdown_signal(mut self, rx: watch::Receiver<bool>) -> Self {
119+
self.shutdown_rx = Some(rx);
120+
self
121+
}
97122
}
98123

99124
/// Create the HTTP router with all endpoints
@@ -126,6 +151,12 @@ pub fn create_router(state: AppState) -> Router {
126151
router = router.route("/policy/reload", post(policy_reload_handler));
127152
}
128153

154+
// Add Web UI routes if token is configured
155+
if state.web_ui_token.is_some() {
156+
let webui_router = crate::webui::create_webui_router(state.clone());
157+
router = router.merge(webui_router);
158+
}
159+
129160
router
130161
// Operations
131162
.route("/health", get(health_handler))

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ pub mod proof;
1717
pub mod secrets;
1818
pub mod ssrf;
1919
pub mod ui;
20+
pub mod webui;

0 commit comments

Comments
 (0)