ci: added basic workflows (#2)
Some checks failed
CI / Clippy (push) Successful in 2m35s
CI / Security Audit (push) Successful in 1m46s
CI / Tests (push) Successful in 3m5s
CI / Format (push) Successful in 6m53s
CI / Build & Push Image (push) Failing after 1m54s
CI / Changelog (push) Failing after 1m39s
Some checks failed
CI / Clippy (push) Successful in 2m35s
CI / Security Audit (push) Successful in 1m46s
CI / Tests (push) Successful in 3m5s
CI / Format (push) Successful in 6m53s
CI / Build & Push Image (push) Failing after 1m54s
CI / Changelog (push) Failing after 1m39s
Co-authored-by: Sharang Parnerkar <parnerkarsharang@gmail.com> Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
177
.gitea/workflows/ci.yml
Normal file
177
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,177 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: "-D warnings"
|
||||
|
||||
# Cancel in-progress runs for the same branch/PR
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stage 1: Code quality checks (run in parallel)
|
||||
# ---------------------------------------------------------------------------
|
||||
fmt:
|
||||
name: Format
|
||||
runs-on: docker
|
||||
container:
|
||||
image: rust:1.89-bookworm
|
||||
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
|
||||
- run: rustup component add rustfmt
|
||||
- run: cargo fmt --check
|
||||
- name: Install dx CLI
|
||||
run: cargo install dioxus-cli@0.7.3 --locked
|
||||
- name: RSX format check
|
||||
run: dx fmt --check
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: docker
|
||||
container:
|
||||
image: rust:1.89-bookworm
|
||||
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
|
||||
- run: rustup component add clippy
|
||||
# Lint both feature sets independently
|
||||
- name: Clippy (server)
|
||||
run: cargo clippy --features server --no-default-features -- -D warnings
|
||||
- name: Clippy (web)
|
||||
run: cargo clippy --features web --no-default-features -- -D warnings
|
||||
|
||||
audit:
|
||||
name: Security Audit
|
||||
runs-on: docker
|
||||
container:
|
||||
image: rust:1.89-bookworm
|
||||
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
|
||||
- run: cargo install cargo-audit
|
||||
- run: cargo audit
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stage 2: Tests (only after all quality checks pass)
|
||||
# ---------------------------------------------------------------------------
|
||||
test:
|
||||
name: Tests
|
||||
runs-on: docker
|
||||
needs: [fmt, clippy, audit]
|
||||
container:
|
||||
image: rust:1.89-bookworm
|
||||
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: Run tests (server)
|
||||
run: cargo test --features server --no-default-features
|
||||
- name: Run tests (web)
|
||||
run: cargo test --features web --no-default-features
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stage 3: Build Docker image and push to registry
|
||||
# Only on main and release/* branches
|
||||
# ---------------------------------------------------------------------------
|
||||
build-and-push:
|
||||
name: Build & Push Image
|
||||
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/'))
|
||||
container:
|
||||
image: rust:1.89-bookworm
|
||||
steps:
|
||||
- name: Checkout (full history)
|
||||
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: Commit and push changelog
|
||||
run: |
|
||||
git config user.name "CI Bot"
|
||||
git config user.email "ci@certifai.local"
|
||||
git add CHANGELOG.md
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changelog changes to commit"
|
||||
else
|
||||
git commit -m "docs: update CHANGELOG.md [skip ci]"
|
||||
git push origin HEAD:"${GITHUB_REF_NAME}"
|
||||
fi
|
||||
61
Dockerfile
Normal file
61
Dockerfile
Normal file
@@ -0,0 +1,61 @@
|
||||
# Stage 1: Generate dependency recipe for caching
|
||||
FROM rust:1.89-bookworm AS chef
|
||||
RUN cargo install cargo-chef
|
||||
WORKDIR /app
|
||||
|
||||
FROM chef AS planner
|
||||
COPY . .
|
||||
RUN cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
# Stage 2: Build dependencies + application
|
||||
FROM chef AS builder
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
pkg-config libssl-dev curl unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install bun (for Tailwind CSS build step)
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
ENV PATH="/root/.bun/bin:$PATH"
|
||||
|
||||
# Install dx CLI via cargo-binstall
|
||||
RUN curl -L --proto '=https' --tlsv1.2 -sSf \
|
||||
https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh \
|
||||
| bash
|
||||
RUN cargo binstall dioxus-cli@0.7.3 --root /.cargo -y --force
|
||||
ENV PATH="/.cargo/bin:$PATH"
|
||||
|
||||
# Cook dependencies from recipe (cached layer)
|
||||
COPY --from=planner /app/recipe.json recipe.json
|
||||
RUN cargo chef cook --release --recipe-path recipe.json
|
||||
|
||||
# Copy source and build
|
||||
COPY . .
|
||||
|
||||
# Ensure styles directory exists for build.rs tailwind step
|
||||
RUN mkdir -p styles && touch styles/input.css
|
||||
|
||||
# Bundle the fullstack application
|
||||
RUN dx bundle --platform fullstack
|
||||
|
||||
# Stage 3: Minimal runtime image
|
||||
FROM debian:bookworm-slim AS runtime
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates libssl3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN useradd --create-home --shell /bin/bash app
|
||||
USER app
|
||||
WORKDIR /home/app
|
||||
|
||||
# Copy the bundled output from builder
|
||||
COPY --from=builder --chown=app:app /app/target/dx/dashboard/release/web/ ./
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
ENV IP=0.0.0.0
|
||||
ENV PORT=8000
|
||||
|
||||
ENTRYPOINT ["./server"]
|
||||
@@ -1,6 +1,5 @@
|
||||
#![allow(non_snake_case)]
|
||||
#[allow(clippy::expect_used)]
|
||||
|
||||
fn main() {
|
||||
// Init logger
|
||||
dioxus_logger::init(tracing::Level::DEBUG).expect("Failed to init logger");
|
||||
|
||||
15
build.rs
15
build.rs
@@ -1,8 +1,9 @@
|
||||
#[allow(clippy::expect_used)]
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use std::process::Command;
|
||||
println!("cargo:rerun-if-changed=./styles/input.css");
|
||||
Command::new("bunx")
|
||||
|
||||
// Tailwind build is optional - skip gracefully in CI or environments without bun
|
||||
match Command::new("bunx")
|
||||
.args([
|
||||
"@tailwindcss/cli",
|
||||
"-i",
|
||||
@@ -11,7 +12,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
"./assets/tailwind.css",
|
||||
])
|
||||
.status()
|
||||
.expect("could not run tailwind");
|
||||
{
|
||||
Ok(status) if !status.success() => {
|
||||
println!("cargo:warning=tailwind build exited with {status}, skipping CSS generation");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("cargo:warning=bunx not found ({e}), skipping tailwind CSS generation");
|
||||
}
|
||||
Ok(_) => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
52
cliff.toml
Normal file
52
cliff.toml
Normal file
@@ -0,0 +1,52 @@
|
||||
[changelog]
|
||||
header = """
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
"""
|
||||
body = """
|
||||
{%- macro remote_url() -%}
|
||||
https://gitea.meghsakha.com/{{ remote.github.owner }}/{{ remote.github.repo }}
|
||||
{%- endmacro -%}
|
||||
|
||||
{% if version -%}
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else -%}
|
||||
## [Unreleased]
|
||||
{% endif -%}
|
||||
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | striptags | trim | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}{{ commit.message | upper_first }}\
|
||||
{% if commit.breaking %} [**breaking**]{% endif %}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
footer = """
|
||||
---
|
||||
*Generated by [git-cliff](https://git-cliff.org)*
|
||||
"""
|
||||
trim = true
|
||||
|
||||
[git]
|
||||
conventional_commits = true
|
||||
filter_unconventional = true
|
||||
split_commits = false
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "Features" },
|
||||
{ message = "^fix", group = "Bug Fixes" },
|
||||
{ message = "^doc", group = "Documentation" },
|
||||
{ message = "^perf", group = "Performance" },
|
||||
{ message = "^refactor", group = "Refactoring" },
|
||||
{ message = "^style", group = "Styling" },
|
||||
{ message = "^test", group = "Testing" },
|
||||
{ message = "^ci", group = "CI/CD" },
|
||||
{ message = "^chore", group = "Miscellaneous" },
|
||||
{ message = "^build", group = "Build" },
|
||||
]
|
||||
protect_breaking_commits = false
|
||||
filter_commits = false
|
||||
tag_pattern = "v[0-9].*"
|
||||
sort_commits = "oldest"
|
||||
@@ -15,9 +15,7 @@ pub fn AppShell() -> Element {
|
||||
email: "user@example.com".to_string(),
|
||||
avatar_url: String::new(),
|
||||
}
|
||||
main { class: "main-content",
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
main { class: "main-content", Outlet::<Route> {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,7 @@ use dioxus::prelude::*;
|
||||
/// * `href` - URL the card links to when clicked.
|
||||
/// * `icon` - Element rendered as the card icon (typically a `dioxus_free_icons::Icon`).
|
||||
#[component]
|
||||
pub fn DashboardCard(
|
||||
title: String,
|
||||
description: String,
|
||||
href: String,
|
||||
icon: Element,
|
||||
) -> Element {
|
||||
pub fn DashboardCard(title: String, description: String, href: String, icon: Element) -> Element {
|
||||
rsx! {
|
||||
a { class: "dashboard-card", href: "{href}",
|
||||
div { class: "card-icon", {icon} }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_free_icons::icons::bs_icons::{
|
||||
BsBoxArrowRight, BsFileEarmarkText, BsGear, BsGithub, BsGrid,
|
||||
BsHouseDoor, BsRobot,
|
||||
BsBoxArrowRight, BsFileEarmarkText, BsGear, BsGithub, BsGrid, BsHouseDoor, BsRobot,
|
||||
};
|
||||
use dioxus_free_icons::icons::fa_solid_icons::FaCubes;
|
||||
use dioxus_free_icons::Icon;
|
||||
@@ -66,15 +65,9 @@ pub fn Sidebar(email: String, avatar_url: String) -> Element {
|
||||
{
|
||||
// Simple active check: highlight Overview only when on `/`.
|
||||
let is_active = item.route == current_route;
|
||||
let cls = if is_active {
|
||||
"sidebar-link active"
|
||||
} else {
|
||||
"sidebar-link"
|
||||
};
|
||||
let cls = if is_active { "sidebar-link active" } else { "sidebar-link" };
|
||||
rsx! {
|
||||
Link {
|
||||
to: item.route,
|
||||
class: cls,
|
||||
Link { to: item.route, class: cls,
|
||||
{item.icon}
|
||||
span { "{item.label}" }
|
||||
}
|
||||
@@ -135,16 +128,10 @@ fn SidebarFooter() -> Element {
|
||||
rsx! {
|
||||
footer { class: "sidebar-footer",
|
||||
div { class: "sidebar-social",
|
||||
a {
|
||||
href: "#",
|
||||
class: "social-link",
|
||||
title: "GitHub",
|
||||
a { href: "#", class: "social-link", title: "GitHub",
|
||||
Icon { icon: BsGithub, width: 16, height: 16 }
|
||||
}
|
||||
a {
|
||||
href: "#",
|
||||
class: "social-link",
|
||||
title: "Impressum",
|
||||
a { href: "#", class: "social-link", title: "Impressum",
|
||||
Icon { icon: BsGrid, width: 16, height: 16 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ pub async fn auth_callback(
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let token_resp = client
|
||||
.post(&config.token_endpoint())
|
||||
.post(config.token_endpoint())
|
||||
.form(&[
|
||||
("grant_type", "authorization_code"),
|
||||
("client_id", &config.client_id),
|
||||
@@ -237,7 +237,7 @@ pub async fn auth_callback(
|
||||
|
||||
// --- Fetch userinfo ---
|
||||
let userinfo: UserinfoResponse = client
|
||||
.get(&config.userinfo_endpoint())
|
||||
.get(config.userinfo_endpoint())
|
||||
.bearer_auth(&tokens.access_token)
|
||||
.send()
|
||||
.await
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use axum::extract::FromRequestParts;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::debug;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserState(Arc<UserStateInner>);
|
||||
|
||||
@@ -40,11 +40,7 @@ pub fn OverviewPage() -> Element {
|
||||
description: "Guides & API Reference".to_string(),
|
||||
href: "#".to_string(),
|
||||
icon: rsx! {
|
||||
Icon {
|
||||
icon: BsBook,
|
||||
width: 28,
|
||||
height: 28,
|
||||
}
|
||||
Icon { icon: BsBook, width: 28, height: 28 }
|
||||
},
|
||||
}
|
||||
DashboardCard {
|
||||
@@ -52,11 +48,7 @@ pub fn OverviewPage() -> Element {
|
||||
description: "Observability & Analytics".to_string(),
|
||||
href: "#".to_string(),
|
||||
icon: rsx! {
|
||||
Icon {
|
||||
icon: FaChartLine,
|
||||
width: 28,
|
||||
height: 28,
|
||||
}
|
||||
Icon { icon: FaChartLine, width: 28, height: 28 }
|
||||
},
|
||||
}
|
||||
DashboardCard {
|
||||
@@ -64,11 +56,7 @@ pub fn OverviewPage() -> Element {
|
||||
description: "Agent Framework".to_string(),
|
||||
href: "#".to_string(),
|
||||
icon: rsx! {
|
||||
Icon {
|
||||
icon: FaGears,
|
||||
width: 28,
|
||||
height: 28,
|
||||
}
|
||||
Icon { icon: FaGears, width: 28, height: 28 }
|
||||
},
|
||||
}
|
||||
DashboardCard {
|
||||
@@ -76,11 +64,7 @@ pub fn OverviewPage() -> Element {
|
||||
description: "Browse Models".to_string(),
|
||||
href: "#".to_string(),
|
||||
icon: rsx! {
|
||||
Icon {
|
||||
icon: FaCubes,
|
||||
width: 28,
|
||||
height: 28,
|
||||
}
|
||||
Icon { icon: FaCubes, width: 28, height: 28 }
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user