ci: added basic workflows #2
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(non_snake_case)]
|
||||||
#[allow(clippy::expect_used)]
|
#[allow(clippy::expect_used)]
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Init logger
|
// Init logger
|
||||||
dioxus_logger::init(tracing::Level::DEBUG).expect("Failed to 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>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
println!("cargo:rerun-if-changed=./styles/input.css");
|
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([
|
.args([
|
||||||
"@tailwindcss/cli",
|
"@tailwindcss/cli",
|
||||||
"-i",
|
"-i",
|
||||||
@@ -11,7 +12,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
"./assets/tailwind.css",
|
"./assets/tailwind.css",
|
||||||
])
|
])
|
||||||
.status()
|
.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(())
|
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(),
|
email: "user@example.com".to_string(),
|
||||||
avatar_url: String::new(),
|
avatar_url: String::new(),
|
||||||
}
|
}
|
||||||
main { class: "main-content",
|
main { class: "main-content", Outlet::<Route> {} }
|
||||||
Outlet::<Route> {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,7 @@ use dioxus::prelude::*;
|
|||||||
/// * `href` - URL the card links to when clicked.
|
/// * `href` - URL the card links to when clicked.
|
||||||
/// * `icon` - Element rendered as the card icon (typically a `dioxus_free_icons::Icon`).
|
/// * `icon` - Element rendered as the card icon (typically a `dioxus_free_icons::Icon`).
|
||||||
#[component]
|
#[component]
|
||||||
pub fn DashboardCard(
|
pub fn DashboardCard(title: String, description: String, href: String, icon: Element) -> Element {
|
||||||
title: String,
|
|
||||||
description: String,
|
|
||||||
href: String,
|
|
||||||
icon: Element,
|
|
||||||
) -> Element {
|
|
||||||
rsx! {
|
rsx! {
|
||||||
a { class: "dashboard-card", href: "{href}",
|
a { class: "dashboard-card", href: "{href}",
|
||||||
div { class: "card-icon", {icon} }
|
div { class: "card-icon", {icon} }
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_free_icons::icons::bs_icons::{
|
use dioxus_free_icons::icons::bs_icons::{
|
||||||
BsBoxArrowRight, BsFileEarmarkText, BsGear, BsGithub, BsGrid,
|
BsBoxArrowRight, BsFileEarmarkText, BsGear, BsGithub, BsGrid, BsHouseDoor, BsRobot,
|
||||||
BsHouseDoor, BsRobot,
|
|
||||||
};
|
};
|
||||||
use dioxus_free_icons::icons::fa_solid_icons::FaCubes;
|
use dioxus_free_icons::icons::fa_solid_icons::FaCubes;
|
||||||
use dioxus_free_icons::Icon;
|
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 `/`.
|
// Simple active check: highlight Overview only when on `/`.
|
||||||
let is_active = item.route == current_route;
|
let is_active = item.route == current_route;
|
||||||
let cls = if is_active {
|
let cls = if is_active { "sidebar-link active" } else { "sidebar-link" };
|
||||||
"sidebar-link active"
|
|
||||||
} else {
|
|
||||||
"sidebar-link"
|
|
||||||
};
|
|
||||||
rsx! {
|
rsx! {
|
||||||
Link {
|
Link { to: item.route, class: cls,
|
||||||
to: item.route,
|
|
||||||
class: cls,
|
|
||||||
{item.icon}
|
{item.icon}
|
||||||
span { "{item.label}" }
|
span { "{item.label}" }
|
||||||
}
|
}
|
||||||
@@ -135,16 +128,10 @@ fn SidebarFooter() -> Element {
|
|||||||
rsx! {
|
rsx! {
|
||||||
footer { class: "sidebar-footer",
|
footer { class: "sidebar-footer",
|
||||||
div { class: "sidebar-social",
|
div { class: "sidebar-social",
|
||||||
a {
|
a { href: "#", class: "social-link", title: "GitHub",
|
||||||
href: "#",
|
|
||||||
class: "social-link",
|
|
||||||
title: "GitHub",
|
|
||||||
Icon { icon: BsGithub, width: 16, height: 16 }
|
Icon { icon: BsGithub, width: 16, height: 16 }
|
||||||
}
|
}
|
||||||
a {
|
a { href: "#", class: "social-link", title: "Impressum",
|
||||||
href: "#",
|
|
||||||
class: "social-link",
|
|
||||||
title: "Impressum",
|
|
||||||
Icon { icon: BsGrid, width: 16, height: 16 }
|
Icon { icon: BsGrid, width: 16, height: 16 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ pub async fn auth_callback(
|
|||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let token_resp = client
|
let token_resp = client
|
||||||
.post(&config.token_endpoint())
|
.post(config.token_endpoint())
|
||||||
.form(&[
|
.form(&[
|
||||||
("grant_type", "authorization_code"),
|
("grant_type", "authorization_code"),
|
||||||
("client_id", &config.client_id),
|
("client_id", &config.client_id),
|
||||||
@@ -237,7 +237,7 @@ pub async fn auth_callback(
|
|||||||
|
|
||||||
// --- Fetch userinfo ---
|
// --- Fetch userinfo ---
|
||||||
let userinfo: UserinfoResponse = client
|
let userinfo: UserinfoResponse = client
|
||||||
.get(&config.userinfo_endpoint())
|
.get(config.userinfo_endpoint())
|
||||||
.bearer_auth(&tokens.access_token)
|
.bearer_auth(&tokens.access_token)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
use std::{
|
use std::{ops::Deref, sync::Arc};
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use axum::extract::FromRequestParts;
|
use axum::extract::FromRequestParts;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UserState(Arc<UserStateInner>);
|
pub struct UserState(Arc<UserStateInner>);
|
||||||
|
|||||||
@@ -40,11 +40,7 @@ pub fn OverviewPage() -> Element {
|
|||||||
description: "Guides & API Reference".to_string(),
|
description: "Guides & API Reference".to_string(),
|
||||||
href: "#".to_string(),
|
href: "#".to_string(),
|
||||||
icon: rsx! {
|
icon: rsx! {
|
||||||
Icon {
|
Icon { icon: BsBook, width: 28, height: 28 }
|
||||||
icon: BsBook,
|
|
||||||
width: 28,
|
|
||||||
height: 28,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
DashboardCard {
|
DashboardCard {
|
||||||
@@ -52,11 +48,7 @@ pub fn OverviewPage() -> Element {
|
|||||||
description: "Observability & Analytics".to_string(),
|
description: "Observability & Analytics".to_string(),
|
||||||
href: "#".to_string(),
|
href: "#".to_string(),
|
||||||
icon: rsx! {
|
icon: rsx! {
|
||||||
Icon {
|
Icon { icon: FaChartLine, width: 28, height: 28 }
|
||||||
icon: FaChartLine,
|
|
||||||
width: 28,
|
|
||||||
height: 28,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
DashboardCard {
|
DashboardCard {
|
||||||
@@ -64,11 +56,7 @@ pub fn OverviewPage() -> Element {
|
|||||||
description: "Agent Framework".to_string(),
|
description: "Agent Framework".to_string(),
|
||||||
href: "#".to_string(),
|
href: "#".to_string(),
|
||||||
icon: rsx! {
|
icon: rsx! {
|
||||||
Icon {
|
Icon { icon: FaGears, width: 28, height: 28 }
|
||||||
icon: FaGears,
|
|
||||||
width: 28,
|
|
||||||
height: 28,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
DashboardCard {
|
DashboardCard {
|
||||||
@@ -76,11 +64,7 @@ pub fn OverviewPage() -> Element {
|
|||||||
description: "Browse Models".to_string(),
|
description: "Browse Models".to_string(),
|
||||||
href: "#".to_string(),
|
href: "#".to_string(),
|
||||||
icon: rsx! {
|
icon: rsx! {
|
||||||
Icon {
|
Icon { icon: FaCubes, width: 28, height: 28 }
|
||||||
icon: FaCubes,
|
|
||||||
width: 28,
|
|
||||||
height: 28,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user