4 Commits

Author SHA1 Message Date
Sharang Parnerkar
8b16eba1ad chore: remove completed feature specs and apply dx fmt
All checks were successful
CI / Format (pull_request) Successful in 6m55s
CI / Clippy (pull_request) Successful in 2m28s
CI / Security Audit (pull_request) Successful in 1m46s
CI / Tests (pull_request) Successful in 2m56s
CI / Deploy (pull_request) Has been skipped
CI / Format (push) Successful in 6m15s
CI / Clippy (push) Successful in 2m16s
CI / Security Audit (push) Successful in 1m38s
CI / Tests (push) Successful in 2m45s
CI / Deploy (push) Has been skipped
Remove CAI-1 and CAI-2 feature files that have been implemented.
Apply dx fmt formatting to landing and privacy pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 10:35:26 +01:00
Sharang Parnerkar
58420b4547 docs: add project badges to README
Some checks failed
CI / Format (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Security Audit (push) Has been cancelled
CI / Tests (push) Has been cancelled
CI / Deploy (push) Has been cancelled
CI / Format (pull_request) Has been cancelled
CI / Clippy (pull_request) Has been cancelled
CI / Security Audit (pull_request) Has been cancelled
CI / Tests (pull_request) Has been cancelled
CI / Deploy (pull_request) Has been cancelled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 10:32:36 +01:00
Sharang Parnerkar
d473f7570b ci: add deploy stage to trigger Coolify after CI passes
Some checks failed
CI / Format (push) Failing after 6m21s
CI / Clippy (push) Successful in 2m17s
CI / Security Audit (push) Successful in 1m39s
CI / Tests (push) Has been skipped
CI / Clippy (pull_request) Has been cancelled
CI / Security Audit (pull_request) Has been cancelled
CI / Tests (pull_request) Has been cancelled
CI / Deploy (pull_request) Has been cancelled
CI / Format (pull_request) Has been cancelled
CI / Deploy (push) Has been cancelled
Deploy job runs only on main branch after all quality checks and tests
succeed, replacing the immediate push webhook with a gated deployment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 10:17:34 +01:00
Sharang Parnerkar
46b2ee5dfa fix(ci): removed build and changelog
Some checks failed
CI / Clippy (push) Has been cancelled
CI / Security Audit (push) Has been cancelled
CI / Tests (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Format (pull_request) Failing after 6m27s
CI / Clippy (pull_request) Successful in 2m19s
CI / Security Audit (pull_request) Successful in 1m42s
CI / Tests (pull_request) Has been skipped
2026-02-19 09:58:11 +01:00
7 changed files with 161 additions and 146 deletions

View File

@@ -96,76 +96,19 @@ jobs:
run: cargo test --features web --no-default-features
# ---------------------------------------------------------------------------
# Stage 3: Build Docker image and push to registry
# Only on main and release/* branches
# Stage 3: Deploy (only after tests pass, only on main)
# ---------------------------------------------------------------------------
build-and-push:
name: Build & Push Image
deploy:
name: Deploy
runs-on: docker
needs: [test]
if: >-
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/'))
steps:
- name: Checkout
run: |
git init
git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git fetch --depth=1 origin "${GITHUB_SHA}"
git checkout FETCH_HEAD
- name: Determine image tag
id: tag
run: |
BRANCH="${GITHUB_REF#refs/heads/}"
# Replace / with - for valid Docker tags (e.g. release/1.0 -> release-1.0)
BRANCH_SAFE=$(echo "$BRANCH" | tr '/' '-')
SHA=$(echo "$GITHUB_SHA" | head -c 8)
echo "tag=${BRANCH_SAFE}-${SHA}" >> "$GITHUB_OUTPUT"
- name: Log in to container registry
run: >-
echo "${{ secrets.REGISTRY_PASSWORD }}"
| docker login https://registry.meghsakha.com
-u "${{ secrets.REGISTRY_USERNAME }}"
--password-stdin
- name: Build Docker image
run: >-
docker build
-t registry.meghsakha.com/certifai/dashboard:${{ steps.tag.outputs.tag }}
-t registry.meghsakha.com/certifai/dashboard:latest
.
- name: Push Docker image
run: |
docker push registry.meghsakha.com/certifai/dashboard:${{ steps.tag.outputs.tag }}
docker push registry.meghsakha.com/certifai/dashboard:latest
# ---------------------------------------------------------------------------
# Stage 3b: Generate changelog from conventional commits
# Only on main and release/* branches
# ---------------------------------------------------------------------------
changelog:
name: Changelog
runs-on: docker
needs: [test]
if: >-
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/'))
if: github.ref == 'refs/heads/main'
container:
image: rust:1.89-bookworm
image: alpine:latest
steps:
- name: Checkout (full history)
- name: Trigger Coolify deploy
run: |
git clone "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" .
git checkout "${GITHUB_SHA}"
- name: Install git-cliff
run: cargo install git-cliff --locked
- name: Generate changelog
run: git cliff --output CHANGELOG.md
- name: Upload changelog artifact
uses: actions/upload-artifact@v4
with:
name: changelog
path: CHANGELOG.md
apk add --no-cache curl
curl -sf "${{ secrets.COOLIFY_WEBHOOK }}" \
-H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}"

View File

@@ -237,10 +237,6 @@ The SaaS application dashboard is the landing page for the company admin to view
This project is written in dioxus with fullstack and router features. MongoDB is used as a database for maintaining user state. Keycloak is used as identity provider for user management.
## Features management
All features are detailed and described under the features folder in clear markdown instructions which are valid for both human and AI code developers.
## Code structure
The following folder structure is maintained for separation of concerns:
- src/components/*.rs : All components that are required to be rendered are placed here. These are frontend only, reusable components that are specific for the application.

View File

@@ -1,5 +1,11 @@
# CERTifAI
[![CI](https://gitea.meghsakha.com/sharang/certifai/actions/workflows/ci.yml/badge.svg?branch=main)](https://gitea.meghsakha.com/sharang/certifai/actions?workflow=ci.yml)
[![Rust](https://img.shields.io/badge/Rust-1.89-orange?logo=rust&logoColor=white)](https://www.rust-lang.org/)
[![Dioxus](https://img.shields.io/badge/Dioxus-0.7-blue?logo=webassembly&logoColor=white)](https://dioxuslabs.com/)
[![License](https://img.shields.io/badge/License-Proprietary-red)](LICENSE)
[![GDPR](https://img.shields.io/badge/GDPR-Compliant-green)](https://gdpr.eu/)
This project is a SaaS application dashboard for administation of self-hosted private GenAI (generative AI) toolbox for companies and individuals. The purpose of the dashboard is to manage LLMs, Agents, MCP Servers and other GenAI related features.
The purpose of `CERTifAI`is to provide self-hosted or GDPR-Conform GenAI infrastructure to companies who do not wish to subscribe to non-EU cloud providers to protect their intellectual property from being used as training data.
@@ -19,9 +25,6 @@ The SaaS application dashboard is the landing page for the company admin to view
This project is written in dioxus with fullstack and router features. MongoDB is used as a database for maintaining user state. Keycloak is used as identity provider for user management.
## Features management
All features are detailed and described under the features folder in clear markdown instructions which are valid for both human and AI code developers.
## Code structure
The following folder structure is maintained for separation of concerns:

View File

@@ -1,9 +0,0 @@
# CAI-1
This feature creates a new login/registration page for the GenAI admin dashboard. The user management is provided by Keycloak, which also serves the login/registration flow. The dioxus app should detect if a user is already logged-in or not, and if not, redirect the user to the keycloak landing page and after successful login, capture the user's access token in a state and save a session state.
Steps to follow:
- Create a docker-compose file for hosting a local keycloak and create a realm for testing and a client for Oauth.
- Setup the environment variables using .env. Fill the environment with keycloak URL, realm, client ID and secret.
- Create a user state in Dioxus which manages the session and the access token. Add other user identifying information like email address to the state.
- Modify dioxus to check the state and load the correct URL based on the state.

View File

@@ -1,3 +0,0 @@
# CERTifAI 2
This feature defines the types for database as well as the API between the dashboard backend and frontend.

View File

@@ -46,12 +46,16 @@ fn LandingNav() -> Element {
}
div { class: "landing-nav-actions",
Link {
to: Route::Login { redirect_url: "/dashboard".into() },
to: Route::Login {
redirect_url: "/dashboard".into(),
},
class: "btn btn-ghost btn-sm",
"Log In"
}
Link {
to: Route::Login { redirect_url: "/dashboard".into() },
to: Route::Login {
redirect_url: "/dashboard".into(),
},
class: "btn btn-primary btn-sm",
"Get Started"
}
@@ -67,9 +71,7 @@ fn HeroSection() -> Element {
rsx! {
section { class: "hero-section",
div { class: "hero-content",
div { class: "hero-badge badge badge-outline",
"Privacy-First GenAI Infrastructure"
}
div { class: "hero-badge badge badge-outline", "Privacy-First GenAI Infrastructure" }
h1 { class: "hero-title",
"Your AI. Your Data."
br {}
@@ -82,16 +84,14 @@ fn HeroSection() -> Element {
}
div { class: "hero-actions",
Link {
to: Route::Login { redirect_url: "/dashboard".into() },
to: Route::Login {
redirect_url: "/dashboard".into(),
},
class: "btn btn-primary btn-lg",
"Get Started"
Icon { icon: BsArrowRight, width: 18, height: 18 }
}
a {
href: "#features",
class: "btn btn-outline btn-lg",
"Learn More"
}
a { href: "#features", class: "btn btn-outline btn-lg", "Learn More" }
}
}
div { class: "hero-graphic",
@@ -105,27 +105,44 @@ fn HeroSection() -> Element {
defs {
linearGradient {
id: "grad1",
x1: "0%", y1: "0%",
x2: "100%", y2: "100%",
x1: "0%",
y1: "0%",
x2: "100%",
y2: "100%",
stop { offset: "0%", stop_color: "#91a4d2" }
stop { offset: "100%", stop_color: "#6d85c6" }
}
linearGradient {
id: "grad2",
x1: "0%", y1: "100%",
x2: "100%", y2: "0%",
x1: "0%",
y1: "100%",
x2: "100%",
y2: "0%",
stop { offset: "0%", stop_color: "#f97066" }
stop { offset: "100%", stop_color: "#f9a066" }
}
radialGradient {
id: "glow",
cx: "50%", cy: "50%", r: "50%",
stop { offset: "0%", stop_color: "rgba(145,164,210,0.3)" }
stop { offset: "100%", stop_color: "rgba(145,164,210,0)" }
cx: "50%",
cy: "50%",
r: "50%",
stop {
offset: "0%",
stop_color: "rgba(145,164,210,0.3)",
}
stop {
offset: "100%",
stop_color: "rgba(145,164,210,0)",
}
}
}
// Background glow
circle { cx: "200", cy: "200", r: "180", fill: "url(#glow)" }
circle {
cx: "200",
cy: "200",
r: "180",
fill: "url(#glow)",
}
// Shield outline
path {
d: "M200 40 L340 110 L340 230 C340 300 270 360 200 380 \
@@ -145,36 +162,98 @@ fn HeroSection() -> Element {
opacity: "0.8",
}
// Network nodes
circle { cx: "200", cy: "180", r: "8", fill: "url(#grad1)" }
circle { cx: "150", cy: "230", r: "6", fill: "url(#grad2)" }
circle { cx: "250", cy: "230", r: "6", fill: "url(#grad2)" }
circle { cx: "200", cy: "280", r: "6", fill: "url(#grad1)" }
circle { cx: "130", cy: "170", r: "4", fill: "#91a4d2", opacity: "0.6" }
circle { cx: "270", cy: "170", r: "4", fill: "#91a4d2", opacity: "0.6" }
circle {
cx: "200",
cy: "180",
r: "8",
fill: "url(#grad1)",
}
circle {
cx: "150",
cy: "230",
r: "6",
fill: "url(#grad2)",
}
circle {
cx: "250",
cy: "230",
r: "6",
fill: "url(#grad2)",
}
circle {
cx: "200",
cy: "280",
r: "6",
fill: "url(#grad1)",
}
circle {
cx: "130",
cy: "170",
r: "4",
fill: "#91a4d2",
opacity: "0.6",
}
circle {
cx: "270",
cy: "170",
r: "4",
fill: "#91a4d2",
opacity: "0.6",
}
// Network connections
line {
x1: "200", y1: "180", x2: "150", y2: "230",
stroke: "#91a4d2", stroke_width: "1", opacity: "0.4",
x1: "200",
y1: "180",
x2: "150",
y2: "230",
stroke: "#91a4d2",
stroke_width: "1",
opacity: "0.4",
}
line {
x1: "200", y1: "180", x2: "250", y2: "230",
stroke: "#91a4d2", stroke_width: "1", opacity: "0.4",
x1: "200",
y1: "180",
x2: "250",
y2: "230",
stroke: "#91a4d2",
stroke_width: "1",
opacity: "0.4",
}
line {
x1: "150", y1: "230", x2: "200", y2: "280",
stroke: "#91a4d2", stroke_width: "1", opacity: "0.4",
x1: "150",
y1: "230",
x2: "200",
y2: "280",
stroke: "#91a4d2",
stroke_width: "1",
opacity: "0.4",
}
line {
x1: "250", y1: "230", x2: "200", y2: "280",
stroke: "#91a4d2", stroke_width: "1", opacity: "0.4",
x1: "250",
y1: "230",
x2: "200",
y2: "280",
stroke: "#91a4d2",
stroke_width: "1",
opacity: "0.4",
}
line {
x1: "200", y1: "180", x2: "130", y2: "170",
stroke: "#91a4d2", stroke_width: "1", opacity: "0.3",
x1: "200",
y1: "180",
x2: "130",
y2: "170",
stroke: "#91a4d2",
stroke_width: "1",
opacity: "0.3",
}
line {
x1: "200", y1: "180", x2: "270", y2: "170",
stroke: "#91a4d2", stroke_width: "1", opacity: "0.3",
x1: "200",
y1: "180",
x2: "270",
y2: "170",
stroke: "#91a4d2",
stroke_width: "1",
opacity: "0.3",
}
// Checkmark inside shield center
path {
@@ -236,37 +315,49 @@ fn FeaturesGrid() -> Element {
}
div { class: "features-grid",
FeatureCard {
icon: rsx! { Icon { icon: BsServer, width: 28, height: 28 } },
icon: rsx! {
Icon { icon: BsServer, width: 28, height: 28 }
},
title: "Self-Hosted Infrastructure",
description: "Deploy on your own hardware or private cloud. \
Full control over your AI stack with no external dependencies.",
}
FeatureCard {
icon: rsx! { Icon { icon: BsShieldCheck, width: 28, height: 28 } },
icon: rsx! {
Icon { icon: BsShieldCheck, width: 28, height: 28 }
},
title: "GDPR Compliant",
description: "EU data residency guaranteed. Your data never \
leaves your infrastructure or gets shared with third parties.",
}
FeatureCard {
icon: rsx! { Icon { icon: FaCubes, width: 28, height: 28 } },
icon: rsx! {
Icon { icon: FaCubes, width: 28, height: 28 }
},
title: "LLM Management",
description: "Deploy, monitor, and manage multiple language \
models. Switch between models with zero downtime.",
}
FeatureCard {
icon: rsx! { Icon { icon: BsRobot, width: 28, height: 28 } },
icon: rsx! {
Icon { icon: BsRobot, width: 28, height: 28 }
},
title: "Agent Builder",
description: "Create custom AI agents with integrated Langchain \
and Langfuse for full observability and control.",
}
FeatureCard {
icon: rsx! { Icon { icon: BsGlobe2, width: 28, height: 28 } },
icon: rsx! {
Icon { icon: BsGlobe2, width: 28, height: 28 }
},
title: "MCP Server Management",
description: "Manage Model Context Protocol servers to extend \
your AI capabilities with external tool integrations.",
}
FeatureCard {
icon: rsx! { Icon { icon: BsKey, width: 28, height: 28 } },
icon: rsx! {
Icon { icon: BsKey, width: 28, height: 28 }
},
title: "API Key Management",
description: "Generate API keys, track usage per seat, and \
set fine-grained permissions for every integration.",
@@ -300,9 +391,7 @@ fn HowItWorks() -> Element {
rsx! {
section { id: "how-it-works", class: "how-it-works-section",
h2 { class: "section-title", "Up and Running in Minutes" }
p { class: "section-subtitle",
"Three steps to sovereign AI infrastructure."
}
p { class: "section-subtitle", "Three steps to sovereign AI infrastructure." }
div { class: "steps-grid",
StepCard {
number: "01",
@@ -353,21 +442,23 @@ fn StepCard(number: &'static str, title: &'static str, description: &'static str
fn CtaBanner() -> Element {
rsx! {
section { class: "cta-banner",
h2 { class: "cta-title",
"Ready to take control of your AI infrastructure?"
}
h2 { class: "cta-title", "Ready to take control of your AI infrastructure?" }
p { class: "cta-subtitle",
"Start deploying sovereign GenAI today. No credit card required."
}
div { class: "cta-actions",
Link {
to: Route::Login { redirect_url: "/dashboard".into() },
to: Route::Login {
redirect_url: "/dashboard".into(),
},
class: "btn btn-primary btn-lg",
"Get Started Free"
Icon { icon: BsArrowRight, width: 18, height: 18 }
}
Link {
to: Route::Login { redirect_url: "/dashboard".into() },
to: Route::Login {
redirect_url: "/dashboard".into(),
},
class: "btn btn-outline btn-lg",
"Log In"
}
@@ -389,9 +480,7 @@ fn LandingFooter() -> Element {
}
span { "CERTifAI" }
}
p { class: "footer-tagline",
"Sovereign GenAI infrastructure for enterprises."
}
p { class: "footer-tagline", "Sovereign GenAI infrastructure for enterprises." }
}
div { class: "footer-links-group",
h4 { class: "footer-links-heading", "Product" }

View File

@@ -22,9 +22,7 @@ pub fn PrivacyPage() -> Element {
}
main { class: "legal-content",
h1 { "Privacy Policy" }
p { class: "legal-updated",
"Last updated: February 2026"
}
p { class: "legal-updated", "Last updated: February 2026" }
h2 { "1. Introduction" }
p {
@@ -90,9 +88,7 @@ pub fn PrivacyPage() -> Element {
li { "Request erasure of your data" }
li { "Restrict or object to processing" }
li { "Data portability" }
li {
"Lodge a complaint with a supervisory authority"
}
li { "Lodge a complaint with a supervisory authority" }
}
h2 { "7. Contact" }