Merge pull request 'feature/rust-healthcheck-and-frontend' (#10) from feature/rust-healthcheck-and-frontend into main
Some checks failed
Deploy / build-and-deploy (push) Failing after 2s
Some checks failed
Deploy / build-and-deploy (push) Failing after 2s
Reviewed-on: http://10.0.0.13:3002/rattatwinko/markdownblog/pulls/10 fine merge rt - 26jun25
This commit is contained in:
@@ -1,8 +1,12 @@
|
|||||||
|
#[warn(unused_imports)]
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
mod markdown;
|
mod markdown;
|
||||||
use markdown::{get_all_posts, get_post_by_slug, get_posts_by_tag, watch_posts};
|
use markdown::{get_all_posts, get_post_by_slug, get_posts_by_tag, watch_posts};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::io;
|
||||||
|
use std::io::Read; // STD AYOOOOOOOOOOOOOO - Tsodin
|
||||||
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(name = "Markdown Backend")]
|
#[command(name = "Markdown Backend")]
|
||||||
@@ -28,6 +32,17 @@ enum Commands {
|
|||||||
Watch,
|
Watch,
|
||||||
/// Show Rust parser statistics
|
/// Show Rust parser statistics
|
||||||
Rsparseinfo,
|
Rsparseinfo,
|
||||||
|
/// Check backend health
|
||||||
|
Checkhealth,
|
||||||
|
/// Parse markdown from file or stdin
|
||||||
|
Parse {
|
||||||
|
#[arg(long)]
|
||||||
|
file: Option<String>,
|
||||||
|
#[arg(long)]
|
||||||
|
stdin: bool,
|
||||||
|
#[arg(long)]
|
||||||
|
ast: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -73,5 +88,43 @@ fn main() {
|
|||||||
Commands::Rsparseinfo => {
|
Commands::Rsparseinfo => {
|
||||||
println!("{}", markdown::rsparseinfo());
|
println!("{}", markdown::rsparseinfo());
|
||||||
}
|
}
|
||||||
|
Commands::Checkhealth => {
|
||||||
|
let health = markdown::checkhealth();
|
||||||
|
println!("{}", serde_json::to_string_pretty(&health).unwrap());
|
||||||
|
}
|
||||||
|
Commands::Parse { file, stdin, ast } => {
|
||||||
|
let input = if let Some(file_path) = file {
|
||||||
|
match std::fs::read_to_string(file_path) {
|
||||||
|
Ok(content) => content,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to read file: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if *stdin {
|
||||||
|
let mut buffer = String::new();
|
||||||
|
if let Err(e) = io::stdin().read_to_string(&mut buffer) {
|
||||||
|
eprintln!("Failed to read from stdin: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
buffer
|
||||||
|
} else {
|
||||||
|
eprintln!("Please provide --file <path> or --stdin");
|
||||||
|
std::process::exit(1);
|
||||||
|
};
|
||||||
|
if *ast {
|
||||||
|
// Print pulldown_cmark events as debug output
|
||||||
|
let parser = pulldown_cmark::Parser::new_ext(&input, pulldown_cmark::Options::all());
|
||||||
|
for event in parser {
|
||||||
|
println!("{:?}", event);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Print HTML output
|
||||||
|
let parser = pulldown_cmark::Parser::new_ext(&input, pulldown_cmark::Options::all());
|
||||||
|
let mut html_output = String::new();
|
||||||
|
pulldown_cmark::html::push_html(&mut html_output, parser);
|
||||||
|
println!("{}", html_output);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ BLAZINGLY FAST!
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#[warn(unused_imports)]
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
@@ -29,6 +29,8 @@ use std::collections::HashMap;
|
|||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use sysinfo::{System, Pid, RefreshKind, CpuRefreshKind, ProcessRefreshKind};
|
use sysinfo::{System, Pid, RefreshKind, CpuRefreshKind, ProcessRefreshKind};
|
||||||
|
use serde::Serialize;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
const POSTS_CACHE_PATH: &str = "./cache/posts_cache.json";
|
const POSTS_CACHE_PATH: &str = "./cache/posts_cache.json";
|
||||||
const POST_STATS_PATH: &str = "./cache/post_stats.json";
|
const POST_STATS_PATH: &str = "./cache/post_stats.json";
|
||||||
@@ -68,6 +70,19 @@ static POST_CACHE: Lazy<RwLock<HashMap<String, Post>>> = Lazy::new(|| RwLock::ne
|
|||||||
static ALL_POSTS_CACHE: Lazy<RwLock<Option<Vec<Post>>>> = Lazy::new(|| RwLock::new(None));
|
static ALL_POSTS_CACHE: Lazy<RwLock<Option<Vec<Post>>>> = Lazy::new(|| RwLock::new(None));
|
||||||
static POST_STATS: Lazy<RwLock<HashMap<String, PostStats>>> = Lazy::new(|| RwLock::new(HashMap::new()));
|
static POST_STATS: Lazy<RwLock<HashMap<String, PostStats>>> = Lazy::new(|| RwLock::new(HashMap::new()));
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct HealthReport {
|
||||||
|
pub posts_dir_exists: bool,
|
||||||
|
pub posts_count: usize,
|
||||||
|
pub cache_file_exists: bool,
|
||||||
|
pub cache_stats_file_exists: bool,
|
||||||
|
pub cache_readable: bool,
|
||||||
|
pub cache_stats_readable: bool,
|
||||||
|
pub cache_post_count: Option<usize>,
|
||||||
|
pub cache_stats_count: Option<usize>,
|
||||||
|
pub errors: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
fn get_posts_directory() -> PathBuf {
|
fn get_posts_directory() -> PathBuf {
|
||||||
let candidates = [
|
let candidates = [
|
||||||
"./posts",
|
"./posts",
|
||||||
@@ -131,6 +146,51 @@ fn strip_emojis(s: &str) -> String {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to process custom tags in markdown content
|
||||||
|
fn process_custom_tags(content: &str) -> String {
|
||||||
|
let mut processed = content.to_string();
|
||||||
|
|
||||||
|
// Handle simple tags without parameters FIRST
|
||||||
|
let simple_tags = [
|
||||||
|
("<mytag />", "<div class=\"custom-tag mytag\">This is my custom tag content!</div>"),
|
||||||
|
("<warning />", "<div class=\"custom-tag warning\" style=\"background: #fff3cd; border: 1px solid #ffeaa7; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">⚠️ Warning: This is a custom warning tag!</div>"),
|
||||||
|
("<info />", "<div class=\"custom-tag info\" style=\"background: #d1ecf1; border: 1px solid #bee5eb; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">ℹ️ Info: This is a custom info tag!</div>"),
|
||||||
|
("<success />", "<div class=\"custom-tag success\" style=\"background: #d4edda; border: 1px solid #c3e6cb; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">✅ Success: This is a custom success tag!</div>"),
|
||||||
|
("<error />", "<div class=\"custom-tag error\" style=\"background: #f8d7da; border: 1px solid #f5c6cb; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">❌ Error: This is a custom error tag!</div>"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (tag, replacement) in simple_tags.iter() {
|
||||||
|
processed = processed.replace(tag, replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle tags with parameters like <mytag param="value" />
|
||||||
|
let tag_with_params = Regex::new(r"<(\w+)\s+([^>]*?[a-zA-Z0-9=])[^>]*/>").unwrap();
|
||||||
|
processed = tag_with_params.replace_all(&processed, |caps: ®ex::Captures| {
|
||||||
|
let tag_name = &caps[1];
|
||||||
|
let params = &caps[2];
|
||||||
|
|
||||||
|
match tag_name {
|
||||||
|
"mytag" => {
|
||||||
|
// Parse parameters and generate custom HTML
|
||||||
|
format!("<div class=\"custom-tag mytag\" data-params=\"{}\">Custom content with params: {}</div>", params, params)
|
||||||
|
},
|
||||||
|
"alert" => {
|
||||||
|
// Parse alert type from params
|
||||||
|
if params.contains("type=\"warning\"") {
|
||||||
|
"<div class=\"custom-tag alert warning\" style=\"background: #fff3cd; border: 1px solid #ffeaa7; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">⚠️ Warning Alert!</div>".to_string()
|
||||||
|
} else if params.contains("type=\"error\"") {
|
||||||
|
"<div class=\"custom-tag alert error\" style=\"background: #f8d7da; border: 1px solid #f5c6cb; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">❌ Error Alert!</div>".to_string()
|
||||||
|
} else {
|
||||||
|
"<div class=\"custom-tag alert info\" style=\"background: #d1ecf1; border: 1px solid #bee5eb; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">ℹ️ Info Alert!</div>".to_string()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => format!("<div class=\"custom-tag {}\">Unknown custom tag: {}</div>", tag_name, tag_name)
|
||||||
|
}
|
||||||
|
}).to_string();
|
||||||
|
|
||||||
|
processed
|
||||||
|
}
|
||||||
|
|
||||||
static AMMONIA: Lazy<ammonia::Builder<'static>> = Lazy::new(|| {
|
static AMMONIA: Lazy<ammonia::Builder<'static>> = Lazy::new(|| {
|
||||||
let mut builder = ammonia::Builder::default();
|
let mut builder = ammonia::Builder::default();
|
||||||
// All possible HTML Tags so that you can stylize via HTML
|
// All possible HTML Tags so that you can stylize via HTML
|
||||||
@@ -156,7 +216,7 @@ static AMMONIA: Lazy<ammonia::Builder<'static>> = Lazy::new(|| {
|
|||||||
builder.add_tag_attributes("pre", &["style"]);
|
builder.add_tag_attributes("pre", &["style"]);
|
||||||
builder.add_tag_attributes("kbd", &["style"]);
|
builder.add_tag_attributes("kbd", &["style"]);
|
||||||
builder.add_tag_attributes("samp", &["style"]);
|
builder.add_tag_attributes("samp", &["style"]);
|
||||||
builder.add_tag_attributes("div", &["style"]);
|
builder.add_tag_attributes("div", &["style", "class"]);
|
||||||
builder.add_tag_attributes("section", &["style"]);
|
builder.add_tag_attributes("section", &["style"]);
|
||||||
builder.add_tag_attributes("article", &["style"]);
|
builder.add_tag_attributes("article", &["style"]);
|
||||||
builder.add_tag_attributes("header", &["style"]);
|
builder.add_tag_attributes("header", &["style"]);
|
||||||
@@ -266,7 +326,7 @@ pub fn get_post_by_slug(slug: &str) -> Result<Post, Box<dyn std::error::Error>>
|
|||||||
|
|
||||||
let created_at = get_file_creation_date(&file_path)?;
|
let created_at = get_file_creation_date(&file_path)?;
|
||||||
|
|
||||||
let processed_markdown = process_anchor_links(&result.content);
|
let processed_markdown = process_custom_tags(&process_anchor_links(&result.content));
|
||||||
let parser = Parser::new_ext(&processed_markdown, Options::all());
|
let parser = Parser::new_ext(&processed_markdown, Options::all());
|
||||||
let mut html_output = String::new();
|
let mut html_output = String::new();
|
||||||
let mut heading_text = String::new();
|
let mut heading_text = String::new();
|
||||||
@@ -440,3 +500,65 @@ pub fn save_post_cache_to_disk() {
|
|||||||
let _ = fs::write(POST_STATS_PATH, map);
|
let _ = fs::write(POST_STATS_PATH, map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn checkhealth() -> HealthReport {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
let posts_dir = get_posts_directory();
|
||||||
|
let posts_dir_exists = posts_dir.exists() && posts_dir.is_dir();
|
||||||
|
let mut posts_count = 0;
|
||||||
|
if posts_dir_exists {
|
||||||
|
match std::fs::read_dir(&posts_dir) {
|
||||||
|
Ok(entries) => {
|
||||||
|
posts_count = entries.filter_map(|e| e.ok())
|
||||||
|
.filter(|e| e.path().extension().map(|ext| ext == "md").unwrap_or(false))
|
||||||
|
.count();
|
||||||
|
},
|
||||||
|
Err(e) => errors.push(format!("Failed to read posts dir: {}", e)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors.push("Posts directory does not exist".to_string());
|
||||||
|
}
|
||||||
|
let cache_file_exists = Path::new(POSTS_CACHE_PATH).exists();
|
||||||
|
let cache_stats_file_exists = Path::new(POST_STATS_PATH).exists();
|
||||||
|
let (mut cache_readable, mut cache_post_count) = (false, None);
|
||||||
|
if cache_file_exists {
|
||||||
|
match std::fs::read_to_string(POSTS_CACHE_PATH) {
|
||||||
|
Ok(data) => {
|
||||||
|
match serde_json::from_str::<HashMap<String, Post>>(&data) {
|
||||||
|
Ok(map) => {
|
||||||
|
cache_readable = true;
|
||||||
|
cache_post_count = Some(map.len());
|
||||||
|
},
|
||||||
|
Err(e) => errors.push(format!("Cache file not valid JSON: {}", e)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => errors.push(format!("Failed to read cache file: {}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (mut cache_stats_readable, mut cache_stats_count) = (false, None);
|
||||||
|
if cache_stats_file_exists {
|
||||||
|
match std::fs::read_to_string(POST_STATS_PATH) {
|
||||||
|
Ok(data) => {
|
||||||
|
match serde_json::from_str::<HashMap<String, PostStats>>(&data) {
|
||||||
|
Ok(map) => {
|
||||||
|
cache_stats_readable = true;
|
||||||
|
cache_stats_count = Some(map.len());
|
||||||
|
},
|
||||||
|
Err(e) => errors.push(format!("Cache stats file not valid JSON: {}", e)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => errors.push(format!("Failed to read cache stats file: {}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HealthReport {
|
||||||
|
posts_dir_exists,
|
||||||
|
posts_count,
|
||||||
|
cache_file_exists,
|
||||||
|
cache_stats_file_exists,
|
||||||
|
cache_readable,
|
||||||
|
cache_stats_readable,
|
||||||
|
cache_post_count,
|
||||||
|
cache_stats_count,
|
||||||
|
errors,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ author: Rattatwinko
|
|||||||
- [Features 🎉](#features)
|
- [Features 🎉](#features)
|
||||||
- [Administration 🚧](#administration)
|
- [Administration 🚧](#administration)
|
||||||
- [Customization 🎨](#customization)
|
- [Customization 🎨](#customization)
|
||||||
|
- [Creating Posts with MdB ✍](#creating-posts-with-mdb)
|
||||||
- [Troubleshooting 🚨](#troubleshooting)
|
- [Troubleshooting 🚨](#troubleshooting)
|
||||||
- [Support 🤝](#support)
|
- [Support 🤝](#support)
|
||||||
- [Support the Project ❤️](#support-the-project)
|
- [Support the Project ❤️](#support-the-project)
|
||||||
@@ -31,7 +32,7 @@ author: Rattatwinko
|
|||||||
- [Folder Emojis 🇦🇹](#folder-emoji-technical-note)
|
- [Folder Emojis 🇦🇹](#folder-emoji-technical-note)
|
||||||
- [API 🏗️](#api)
|
- [API 🏗️](#api)
|
||||||
- [ToT, and Todo](#train-of-thought-for-this-project-and-todo)
|
- [ToT, and Todo](#train-of-thought-for-this-project-and-todo)
|
||||||
- [Recent Changes](#)
|
- [Recent Changes](#recent-changes)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -302,6 +303,35 @@ The codebase is well-structured and documented. Key files:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Creating Posts with MdB
|
||||||
|
|
||||||
|
If you are reading posts. Then you probably dont need this explenation!
|
||||||
|
|
||||||
|
Else you should read this.
|
||||||
|
|
||||||
|
First of all, if you are creating posts within the terminal. then you should create posts with the following headers.
|
||||||
|
|
||||||
|
```Markdown
|
||||||
|
---
|
||||||
|
title: Welcome to MarkdownBlog
|
||||||
|
date: '2025-06-19'
|
||||||
|
tags:
|
||||||
|
- welcome
|
||||||
|
- introduction
|
||||||
|
- getting-started
|
||||||
|
- documentation
|
||||||
|
summary: A comprehensive guide to getting started with MarkdownBlog
|
||||||
|
author: Rattatwinko
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see this is the header for the current Post.
|
||||||
|
You can write this like YML (idk).
|
||||||
|
|
||||||
|
If you are writing posts within the Admin-Panel then you are a _lucky piece of shit_ cause there it does that **automatically**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Common Issues
|
### Common Issues
|
||||||
|
|||||||
22
run-local-backend.sh
Executable file
22
run-local-backend.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# This script builds and runs the Rust backend locally, similar to the Docker container.
|
||||||
|
# Usage: ./run-local-backend.sh [args for markdown_backend]
|
||||||
|
# AnalSex with the frontend ( Cursor Autocompletion xD )
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Set environment variables as in Docker (customize as needed)
|
||||||
|
export BLOG_OWNER=${BLOG_OWNER:-"rattatwinko"}
|
||||||
|
|
||||||
|
# Build the backend in release mode
|
||||||
|
cd "$(dirname "$0")/markdown_backend"
|
||||||
|
echo "Building Rust backend..."
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
# Run the backend with any arguments passed to the script
|
||||||
|
cd target/release
|
||||||
|
echo "Running: ./markdown_backend $@"
|
||||||
|
./markdown_backend "$@"
|
||||||
|
|
||||||
|
npm run dev ## start the fuckass frontend
|
||||||
@@ -9,10 +9,25 @@ interface PostStats {
|
|||||||
last_compile_time_ms: number;
|
last_compile_time_ms: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface HealthReport {
|
||||||
|
posts_dir_exists: boolean;
|
||||||
|
posts_count: number;
|
||||||
|
cache_file_exists: boolean;
|
||||||
|
cache_stats_file_exists: boolean;
|
||||||
|
cache_readable: boolean;
|
||||||
|
cache_stats_readable: boolean;
|
||||||
|
cache_post_count?: number;
|
||||||
|
cache_stats_count?: number;
|
||||||
|
errors: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export default function RustStatusPage() {
|
export default function RustStatusPage() {
|
||||||
const [stats, setStats] = useState<PostStats[]>([]);
|
const [stats, setStats] = useState<PostStats[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [health, setHealth] = useState<HealthReport | null>(null);
|
||||||
|
const [healthLoading, setHealthLoading] = useState(true);
|
||||||
|
const [healthError, setHealthError] = useState<string | null>(null);
|
||||||
|
|
||||||
// Summary calculations
|
// Summary calculations
|
||||||
const totalHits = stats.reduce((sum, s) => sum + s.cache_hits, 0);
|
const totalHits = stats.reduce((sum, s) => sum + s.cache_hits, 0);
|
||||||
@@ -35,8 +50,24 @@ export default function RustStatusPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchHealth = async () => {
|
||||||
|
setHealthLoading(true);
|
||||||
|
setHealthError(null);
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/admin/posts?checkhealth=1');
|
||||||
|
if (!res.ok) throw new Error('Fehler beim Laden des Health-Checks');
|
||||||
|
const data = await res.json();
|
||||||
|
setHealth(data);
|
||||||
|
} catch (e: any) {
|
||||||
|
setHealthError(e.message || 'Unbekannter Fehler');
|
||||||
|
} finally {
|
||||||
|
setHealthLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchStats();
|
fetchStats();
|
||||||
|
fetchHealth();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -88,7 +119,63 @@ export default function RustStatusPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Rest of your component remains the same */}
|
{/* Health Check Section */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<h2 className="text-base sm:text-lg font-semibold mb-2 text-center">Health-Check</h2>
|
||||||
|
{healthLoading && <div className="text-center py-4 text-base">Lade Health-Check...</div>}
|
||||||
|
{healthError && <div className="text-red-500 text-center text-base">{healthError}</div>}
|
||||||
|
{health && (
|
||||||
|
<div className="bg-white rounded-lg shadow p-4 flex flex-col gap-2 items-center">
|
||||||
|
<div className="flex flex-wrap gap-4 justify-center">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<span className={`text-lg font-bold ${health.posts_dir_exists ? 'text-green-700' : 'text-red-700'}`}>{health.posts_dir_exists ? '✔' : '✖'}</span>
|
||||||
|
<span className="text-xs text-gray-600">Posts-Verzeichnis</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<span className="text-lg font-bold text-blue-700">{health.posts_count}</span>
|
||||||
|
<span className="text-xs text-gray-600">Posts</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<span className={`text-lg font-bold ${health.cache_file_exists ? 'text-green-700' : 'text-red-700'}`}>{health.cache_file_exists ? '✔' : '✖'}</span>
|
||||||
|
<span className="text-xs text-gray-600">Cache-Datei</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<span className={`text-lg font-bold ${health.cache_stats_file_exists ? 'text-green-700' : 'text-red-700'}`}>{health.cache_stats_file_exists ? '✔' : '✖'}</span>
|
||||||
|
<span className="text-xs text-gray-600">Cache-Stats</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<span className={`text-lg font-bold ${health.cache_readable ? 'text-green-700' : 'text-red-700'}`}>{health.cache_readable ? '✔' : '✖'}</span>
|
||||||
|
<span className="text-xs text-gray-600">Cache lesbar</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<span className={`text-lg font-bold ${health.cache_stats_readable ? 'text-green-700' : 'text-red-700'}`}>{health.cache_stats_readable ? '✔' : '✖'}</span>
|
||||||
|
<span className="text-xs text-gray-600">Stats lesbar</span>
|
||||||
|
</div>
|
||||||
|
{typeof health.cache_post_count === 'number' && (
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<span className="text-lg font-bold text-blue-700">{health.cache_post_count}</span>
|
||||||
|
<span className="text-xs text-gray-600">Cache-Posts</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{typeof health.cache_stats_count === 'number' && (
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<span className="text-lg font-bold text-blue-700">{health.cache_stats_count}</span>
|
||||||
|
<span className="text-xs text-gray-600">Stats-Einträge</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{health.errors.length > 0 && (
|
||||||
|
<div className="mt-2 text-red-600 text-xs text-center">
|
||||||
|
<b>Fehler:</b>
|
||||||
|
<ul className="list-disc ml-5 inline-block text-left">
|
||||||
|
{health.errors.map((err, i) => <li key={i}>{err}</li>)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Summary Cards */}
|
{/* Summary Cards */}
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4 mb-6">
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4 mb-6">
|
||||||
<div className="bg-green-100 rounded-lg p-4 flex flex-col items-center shadow">
|
<div className="bg-green-100 rounded-lg p-4 flex flex-col items-center shadow">
|
||||||
|
|||||||
@@ -70,6 +70,26 @@ export async function GET(request: Request) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const checkhealth = searchParams.get('checkhealth');
|
||||||
|
if (checkhealth === '1') {
|
||||||
|
// Call the Rust backend for health check
|
||||||
|
const rustResult = spawnSync(
|
||||||
|
process.cwd() + '/markdown_backend/target/release/markdown_backend',
|
||||||
|
['checkhealth'],
|
||||||
|
{ encoding: 'utf-8' }
|
||||||
|
);
|
||||||
|
if (rustResult.status === 0 && rustResult.stdout) {
|
||||||
|
return new Response(rustResult.stdout, {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return new Response(JSON.stringify({ error: rustResult.stderr || rustResult.error }), {
|
||||||
|
status: 500,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
// Return the current pinned.json object
|
// Return the current pinned.json object
|
||||||
try {
|
try {
|
||||||
const pinnedPath = path.join(process.cwd(), 'posts', 'pinned.json');
|
const pinnedPath = path.join(process.cwd(), 'posts', 'pinned.json');
|
||||||
|
|||||||
@@ -444,3 +444,15 @@ select:focus {
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-tag {
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 1rem 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.custom-tag.warning { background: #fff3cd; border: 1px solid #ffeaa7; color: #856404; }
|
||||||
|
.custom-tag.info { background: #d1ecf1; border: 1px solid #bee5eb; color: #0c5460; }
|
||||||
|
.custom-tag.success { background: #d4edda; border: 1px solid #c3e6cb; color: #155724; }
|
||||||
|
.custom-tag.error { background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; }
|
||||||
|
.custom-tag.mytag { background: #e3e3ff; border: 1px solid #b3b3ff; color: #333366; }
|
||||||
Reference in New Issue
Block a user