mirror of
https://github.com/BreizhHardware/ntfy_alerts.git
synced 2026-01-18 16:37:28 +01:00
refactor(web): refactor the web interface using Nuxt and NuxtUI
This commit is contained in:
18
.github/dependabot.yaml
vendored
18
.github/dependabot.yaml
vendored
@@ -10,3 +10,21 @@ updates:
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
target-branch: "dev"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
target-branch: "dev"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/.github/workflows"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
target-branch: "dev"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/web"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
target-branch: "dev"
|
||||
48
.github/workflows/create_dev.yml
vendored
48
.github/workflows/create_dev.yml
vendored
@@ -53,9 +53,39 @@ jobs:
|
||||
name: github-ntfy
|
||||
path: release/github-ntfy
|
||||
|
||||
build-frontend:
|
||||
if: ${{ github.actor != 'dependabot[bot]' && !startsWith(github.ref, 'refs/heads/dependabot/') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: '10.x'
|
||||
run_install: false
|
||||
|
||||
- name: Build Frontend (Nuxt)
|
||||
run: |
|
||||
cd web
|
||||
pnpm install
|
||||
pnpm generate
|
||||
|
||||
- name: Upload frontend comme artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: nuxt-frontend
|
||||
path: web/.output
|
||||
|
||||
docker-build-push:
|
||||
if: ${{ github.actor != 'dependabot[bot]' && !startsWith(github.ref, 'refs/heads/dependabot/') }}
|
||||
needs: [build-binary]
|
||||
needs: [build-binary, build-frontend]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -76,14 +106,26 @@ jobs:
|
||||
name: github-ntfy
|
||||
path: binaries
|
||||
|
||||
- name: Préparer le binaire pour Docker
|
||||
- name: Télécharger le frontend
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: nuxt-frontend
|
||||
path: web/.output
|
||||
|
||||
- name: Préparer les fichiers pour Docker
|
||||
run: |
|
||||
chmod +x binaries/github-ntfy
|
||||
mkdir -p docker-build
|
||||
cp binaries/github-ntfy docker-build/
|
||||
cp -r web/.output docker-build/web
|
||||
cp nginx.conf docker-build/
|
||||
cp entrypoint.sh docker-build/
|
||||
chmod +x docker-build/entrypoint.sh
|
||||
|
||||
- name: Construire et pousser l'image Docker
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
context: docker-build
|
||||
push: true
|
||||
tags: breizhhardware/github-ntfy:dev
|
||||
file: Dockerfile
|
||||
70
.github/workflows/create_release.yml
vendored
70
.github/workflows/create_release.yml
vendored
@@ -85,8 +85,38 @@ jobs:
|
||||
name: github-ntfy
|
||||
path: release/github-ntfy
|
||||
|
||||
build-frontend:
|
||||
needs: version
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: '10.x'
|
||||
run_install: false
|
||||
|
||||
- name: Build Frontend (Nuxt)
|
||||
run: |
|
||||
cd web
|
||||
pnpm install
|
||||
pnpm generate
|
||||
|
||||
- name: Upload frontend comme artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: nuxt-frontend
|
||||
path: web/.output
|
||||
|
||||
docker-build-push:
|
||||
needs: [version, build-binaries]
|
||||
needs: [version, build-binaries, build-frontend]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -101,48 +131,72 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Télécharger tous les binaires
|
||||
- name: Télécharger le binaire
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: github-ntfy
|
||||
path: binaries
|
||||
|
||||
- name: Préparer le binaire pour Docker
|
||||
- name: Télécharger le frontend
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: nuxt-frontend
|
||||
path: web/.output
|
||||
|
||||
- name: Préparer les fichiers pour Docker
|
||||
run: |
|
||||
chmod +x binaries/github-ntfy
|
||||
mkdir -p docker-build
|
||||
cp binaries/github-ntfy docker-build/
|
||||
cp -r web/.output docker-build/web
|
||||
cp nginx.conf docker-build/
|
||||
cp entrypoint.sh docker-build/
|
||||
chmod +x docker-build/entrypoint.sh
|
||||
|
||||
# Construire et pousser l'image multi-architecture
|
||||
- name: Construire et pousser l'image Docker
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
context: docker-build
|
||||
push: true
|
||||
tags: |
|
||||
breizhhardware/github-ntfy:latest
|
||||
breizhhardware/github-ntfy:dev
|
||||
breizhhardware/github-ntfy:${{ needs.version.outputs.version }}
|
||||
file: Dockerfile
|
||||
|
||||
create-release:
|
||||
needs: [version, build-binaries]
|
||||
needs: [version, build-binaries, build-frontend]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Télécharger tous les binaires
|
||||
- name: Télécharger le binaire
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: github-ntfy
|
||||
path: binaries
|
||||
|
||||
- name: Télécharger le frontend
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: nuxt-frontend
|
||||
path: web/.output
|
||||
|
||||
- name: Préparer les fichiers pour la release
|
||||
run: |
|
||||
mkdir -p release-artifacts
|
||||
cp binaries/github-ntfy release-artifacts/
|
||||
tar -czf release-artifacts/frontend.tar.gz -C web/.output .
|
||||
|
||||
- name: Créer une release GitHub
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ needs.version.outputs.version }}
|
||||
name: Release ${{ needs.version.outputs.version }}
|
||||
files: |
|
||||
binaries/github-ntfy
|
||||
release-artifacts/github-ntfy
|
||||
release-artifacts/frontend.tar.gz
|
||||
draft: false
|
||||
prerelease: false
|
||||
generate_release_notes: true
|
||||
|
||||
35
.github/workflows/dependabot-build.yml
vendored
35
.github/workflows/dependabot-build.yml
vendored
@@ -28,22 +28,35 @@ jobs:
|
||||
|
||||
- name: Créer Cross.toml pour spécifier OpenSSL vendored
|
||||
run: |
|
||||
cat > Cross.toml << 'EOF'
|
||||
cat > Cross.toml << 'EOL'
|
||||
[target.x86_64-unknown-linux-musl]
|
||||
image = "ghcr.io/cross-rs/x86_64-unknown-linux-musl:main"
|
||||
|
||||
[build.env]
|
||||
passthrough = [
|
||||
"RUSTFLAGS",
|
||||
"OPENSSL_STATIC",
|
||||
"OPENSSL_NO_VENDOR"
|
||||
"RUST_BACKTRACE",
|
||||
]
|
||||
EOF
|
||||
EOL
|
||||
|
||||
- name: Construire avec cross et OpenSSL vendored
|
||||
env:
|
||||
OPENSSL_STATIC: 1
|
||||
RUSTFLAGS: "-C target-feature=+crt-static"
|
||||
OPENSSL_NO_VENDOR: 0
|
||||
- name: Build Backend (Rust)
|
||||
run: cross build --release --target x86_64-unknown-linux-musl
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: '10.x'
|
||||
run_install: false
|
||||
|
||||
- name: Build Frontend (Nuxt)
|
||||
run: |
|
||||
cross build --release --target x86_64-unknown-linux-musl --features vendored-openssl
|
||||
cd web
|
||||
pnpm install
|
||||
pnpm build
|
||||
|
||||
- name: Afficher des informations de débogage
|
||||
run: |
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -413,3 +413,4 @@ target/*
|
||||
|
||||
binaries
|
||||
binaries/*
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
FROM alpine:3.22
|
||||
|
||||
# Copier le binaire
|
||||
COPY binaries/github-ntfy /usr/local/bin/github-ntfy
|
||||
COPY github-ntfy /usr/local/bin/github-ntfy
|
||||
|
||||
# Installer les dépendances
|
||||
RUN apk add --no-cache sqlite-libs openssl nginx && \
|
||||
RUN apk add --no-cache sqlite-libs openssl nginx nodejs npm && \
|
||||
chmod +x /usr/local/bin/github-ntfy
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copier les fichiers web dans le répertoire attendu par nginx
|
||||
COPY web/* /var/www/html/
|
||||
COPY web /var/www/html/
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
# Copier le script d'entrée
|
||||
@@ -20,6 +20,6 @@ RUN chmod +x /app/entrypoint.sh
|
||||
# Créer le répertoire de données
|
||||
RUN mkdir -p /github-ntfy && chmod 755 /github-ntfy
|
||||
|
||||
EXPOSE 5000 80
|
||||
EXPOSE 5000 80 3000
|
||||
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
|
||||
@@ -6,5 +6,8 @@ echo -n "$USERNAME:$PASSWORD" | base64 > /auth.txt
|
||||
# Démarrer nginx en arrière-plan
|
||||
nginx -g 'daemon off;' &
|
||||
|
||||
# Exécute l'application Rust
|
||||
# Démarrer le serveur Nuxt en arrière-plan
|
||||
cd /var/www/html && node ./server/index.mjs &
|
||||
|
||||
# Démarrer l'API principale
|
||||
exec /usr/local/bin/github-ntfy
|
||||
16
nginx.conf
16
nginx.conf
@@ -9,11 +9,16 @@ http {
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
# Configuration pour servir le frontend Nuxt
|
||||
location / {
|
||||
root /var/www/html;
|
||||
index index.html;
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Routes API pour le backend Rust
|
||||
location /app_repo {
|
||||
proxy_pass http://127.0.0.1:5000;
|
||||
proxy_set_header Host $host;
|
||||
@@ -56,5 +61,12 @@ http {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
location /latest_updates {
|
||||
proxy_pass http://127.0.0.1:5000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
99
src/api.rs
99
src/api.rs
@@ -8,12 +8,21 @@ use warp::{Filter, Reply, Rejection};
|
||||
use warp::http::StatusCode;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use warp::cors::Cors;
|
||||
use chrono::{DateTime, Utc, NaiveDateTime};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct RepoRequest {
|
||||
repo: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct UpdateInfo {
|
||||
date: String,
|
||||
repo: String,
|
||||
version: String,
|
||||
changelog: String,
|
||||
}
|
||||
|
||||
pub async fn start_api() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Open the database
|
||||
let db_path = env::var("DB_PATH").unwrap_or_else(|_| "/github-ntfy".to_string());
|
||||
@@ -60,6 +69,11 @@ pub async fn start_api() -> Result<(), Box<dyn std::error::Error + Send + Sync>>
|
||||
.and(with_db(db.clone()))
|
||||
.and_then(delete_docker_repo);
|
||||
|
||||
let get_updates = warp::path("latest_updates")
|
||||
.and(warp::get())
|
||||
.and(with_db(db.clone()))
|
||||
.and_then(get_latest_updates);
|
||||
|
||||
// Configure CORS
|
||||
let cors = warp::cors()
|
||||
.allow_any_origin()
|
||||
@@ -73,6 +87,7 @@ pub async fn start_api() -> Result<(), Box<dyn std::error::Error + Send + Sync>>
|
||||
.or(get_docker)
|
||||
.or(delete_github)
|
||||
.or(delete_docker)
|
||||
.or(get_updates)
|
||||
.with(cors);
|
||||
|
||||
// Start the server
|
||||
@@ -383,4 +398,86 @@ async fn delete_docker_repo(body: RepoRequest, db: Arc<Mutex<Connection>>) -> Re
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_latest_updates(db: Arc<Mutex<Connection>>) -> Result<impl Reply, Rejection> {
|
||||
let updates = {
|
||||
let db_guard = db.lock().await;
|
||||
|
||||
let db_path = env::var("DB_PATH").unwrap_or_else(|_| "/github-ntfy".to_string());
|
||||
let versions_path = format!("{}/ghntfy_versions.db", db_path);
|
||||
|
||||
match Connection::open(&versions_path) {
|
||||
Ok(versions_db) => {
|
||||
match versions_db.prepare("SELECT repo, version, changelog, datetime('now') as date FROM versions ORDER BY rowid DESC LIMIT 5") {
|
||||
Ok(mut stmt) => {
|
||||
let rows = match stmt.query_map([], |row| {
|
||||
Ok(UpdateInfo {
|
||||
repo: row.get(0)?,
|
||||
version: row.get(1)?,
|
||||
changelog: row.get(2)?,
|
||||
date: row.get(3)?,
|
||||
})
|
||||
}) {
|
||||
Ok(rows) => rows,
|
||||
Err(e) => {
|
||||
error!("Error executing query: {}", e);
|
||||
return Ok(warp::reply::with_status(
|
||||
warp::reply::json(&json!({"error": format!("Database error: {}", e)})),
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let mut updates = Vec::new();
|
||||
for row in rows {
|
||||
if let Ok(update) = row {
|
||||
updates.push(update);
|
||||
}
|
||||
}
|
||||
|
||||
if updates.is_empty() {
|
||||
vec![
|
||||
UpdateInfo {
|
||||
date: "20 juin 2025".to_string(),
|
||||
repo: "BreizhHardware/ntfy_alerts".to_string(),
|
||||
version: "2.0.2".to_string(),
|
||||
changelog: "- Aucune mise à jour trouvée dans la base de données\n- Ceci est une donnée d'exemple".to_string(),
|
||||
}
|
||||
]
|
||||
} else {
|
||||
updates
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error preparing query: {}", e);
|
||||
vec![
|
||||
UpdateInfo {
|
||||
date: "20 juin 2025".to_string(),
|
||||
repo: "Erreur".to_string(),
|
||||
version: "N/A".to_string(),
|
||||
changelog: format!("- Erreur lors de la préparation de la requête: {}", e),
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error opening versions database: {}", e);
|
||||
vec![
|
||||
UpdateInfo {
|
||||
date: "20 juin 2025".to_string(),
|
||||
repo: "Erreur".to_string(),
|
||||
version: "N/A".to_string(),
|
||||
changelog: format!("- Erreur lors de l'ouverture de la base de données: {}", e),
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&updates),
|
||||
StatusCode::OK
|
||||
))
|
||||
}
|
||||
|
||||
24
web/.gitignore
vendored
Normal file
24
web/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
75
web/README.md
Normal file
75
web/README.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Nuxt Minimal Starter
|
||||
|
||||
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install dependencies:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run dev
|
||||
|
||||
# pnpm
|
||||
pnpm dev
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
|
||||
# pnpm
|
||||
pnpm build
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
|
||||
# bun
|
||||
bun run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
|
||||
# pnpm
|
||||
pnpm preview
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
|
||||
# bun
|
||||
bun run preview
|
||||
```
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||
26
web/app.vue
Normal file
26
web/app.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-900 text-gray-200">
|
||||
<UContainer>
|
||||
<AppHeader />
|
||||
<main class="py-8">
|
||||
<!-- Section des dernières mises à jour -->
|
||||
<LatestUpdates />
|
||||
|
||||
<!-- Section des dépôts GitHub et Docker -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<GithubRepoSection />
|
||||
<DockerRepoSection />
|
||||
</div>
|
||||
</main>
|
||||
<AppFooter />
|
||||
</UContainer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// No script content provided in the original code or the change description
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* No style content provided in the original code or the change description */
|
||||
</style>
|
||||
2
web/assets/css/main.css
Normal file
2
web/assets/css/main.css
Normal file
@@ -0,0 +1,2 @@
|
||||
@import 'tailwindcss';
|
||||
@import '@nuxt/ui';
|
||||
6
web/components/AppFooter.vue
Normal file
6
web/components/AppFooter.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<footer class="text-center py-6 bg-emerald-950 rounded-t-lg mt-4">
|
||||
<p class="text-sm">I know this web interface is simple, but I'm improving!</p>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
6
web/components/AppHeader.vue
Normal file
6
web/components/AppHeader.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<header class="py-8 bg-emerald-950 shadow-lg rounded-b-lg mb-4">
|
||||
<h1 class="text-5xl font-bold tracking-wide text-white text-center">Github Ntfy</h1>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
106
web/components/DockerRepoSection.vue
Normal file
106
web/components/DockerRepoSection.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<UCard class="bg-emerald-950 shadow-lg">
|
||||
<template #header>
|
||||
<h2 class="text-2xl font-semibold">Add a Docker Repo</h2>
|
||||
</template>
|
||||
|
||||
<form @submit.prevent="addDockerRepo">
|
||||
<UFormGroup label="Name of the Docker Repo" name="dockerRepo">
|
||||
<div class="flex items-center">
|
||||
<UBadge class="mr-2 py-2.5 px-3 bg-gray-700 text-gray-400">hub.docker.com/r/</UBadge>
|
||||
<UInput
|
||||
v-model="dockerRepoName"
|
||||
placeholder="breizhhardware/github-ntfy"
|
||||
class="flex-1 bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
</UFormGroup>
|
||||
<div class="flex justify-end gap-4 mt-4">
|
||||
<UButton color="gray" variant="ghost" @click="dockerRepoName = ''">Cancel</UButton>
|
||||
<UButton type="submit" color="green" variant="solid">Save</UButton>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<template #footer>
|
||||
<div class="mt-4">
|
||||
<h3 class="text-lg font-semibold mb-2">Watched Docker Repositories</h3>
|
||||
<UList v-if="watchedDockerRepos.length" class="space-y-2">
|
||||
<UListItem v-for="repo in watchedDockerRepos" :key="repo" class="flex justify-between items-center">
|
||||
<span>{{ repo }}</span>
|
||||
<UButton
|
||||
color="red"
|
||||
variant="ghost"
|
||||
icon="i-heroicons-x-mark"
|
||||
size="xs"
|
||||
@click="removeDockerRepo(repo)"
|
||||
/>
|
||||
</UListItem>
|
||||
</UList>
|
||||
<p v-else class="text-gray-400 italic">No Docker repositories being watched</p>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const dockerRepoName = ref('')
|
||||
const watchedDockerRepos = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
refreshWatchedDockerRepos()
|
||||
})
|
||||
|
||||
async function addDockerRepo() {
|
||||
if (!dockerRepoName.value) return
|
||||
|
||||
try {
|
||||
const response = await fetch('/app_docker_repo', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ repo: dockerRepoName.value })
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
dockerRepoName.value = ''
|
||||
await refreshWatchedDockerRepos()
|
||||
} else {
|
||||
throw new Error('Failed to add Docker repository')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshWatchedDockerRepos() {
|
||||
try {
|
||||
const response = await fetch('/watched_docker_repos')
|
||||
if (response.ok) {
|
||||
watchedDockerRepos.value = await response.json()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching watched Docker repos:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function removeDockerRepo(repo) {
|
||||
try {
|
||||
const response = await fetch('/delete_docker_repo', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ repo })
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
await refreshWatchedDockerRepos()
|
||||
} else {
|
||||
throw new Error('Failed to remove Docker repository')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
106
web/components/GithubRepoSection.vue
Normal file
106
web/components/GithubRepoSection.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<UCard class="bg-emerald-950 shadow-lg">
|
||||
<template #header>
|
||||
<h2 class="text-2xl font-semibold">Add a Github Repo</h2>
|
||||
</template>
|
||||
|
||||
<form @submit.prevent="addRepo">
|
||||
<UFormGroup label="Name of the Github Repo" name="repo">
|
||||
<div class="flex items-center">
|
||||
<UBadge class="mr-2 py-2.5 px-3 bg-gray-700 text-gray-400">github.com/</UBadge>
|
||||
<UInput
|
||||
v-model="repoName"
|
||||
placeholder="BreizhHardware/ntfy_alerts"
|
||||
class="flex-1 bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
</UFormGroup>
|
||||
<div class="flex justify-end gap-4 mt-4">
|
||||
<UButton color="gray" variant="ghost" @click="repoName = ''">Cancel</UButton>
|
||||
<UButton type="submit" color="green" variant="solid">Save</UButton>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<template #footer>
|
||||
<div class="mt-4">
|
||||
<h3 class="text-lg font-semibold mb-2">Watched Github Repositories</h3>
|
||||
<UList v-if="watchedRepos.length" class="space-y-2">
|
||||
<UListItem v-for="repo in watchedRepos" :key="repo" class="flex justify-between items-center">
|
||||
<span>{{ repo }}</span>
|
||||
<UButton
|
||||
color="red"
|
||||
variant="ghost"
|
||||
icon="i-heroicons-x-mark"
|
||||
size="xs"
|
||||
@click="removeRepo(repo)"
|
||||
/>
|
||||
</UListItem>
|
||||
</UList>
|
||||
<p v-else class="text-gray-400 italic">No repositories being watched</p>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const repoName = ref('')
|
||||
const watchedRepos = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
refreshWatchedRepos()
|
||||
})
|
||||
|
||||
async function addRepo() {
|
||||
if (!repoName.value) return
|
||||
|
||||
try {
|
||||
const response = await fetch('/app_repo', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ repo: repoName.value })
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
repoName.value = ''
|
||||
await refreshWatchedRepos()
|
||||
} else {
|
||||
throw new Error('Failed to add repository')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshWatchedRepos() {
|
||||
try {
|
||||
const response = await fetch('/watched_repos')
|
||||
if (response.ok) {
|
||||
watchedRepos.value = await response.json()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching watched repos:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function removeRepo(repo) {
|
||||
try {
|
||||
const response = await fetch('/delete_repo', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ repo })
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
await refreshWatchedRepos()
|
||||
} else {
|
||||
throw new Error('Failed to remove repository')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
71
web/components/LatestUpdates.vue
Normal file
71
web/components/LatestUpdates.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<UCard class="bg-gray-800 shadow-lg mb-8">
|
||||
<template #header>
|
||||
<h2 class="text-2xl font-semibold">Dernières mises à jour</h2>
|
||||
</template>
|
||||
|
||||
<div class="space-y-4">
|
||||
<UAccordion v-for="(update, index) in latestUpdates" :key="index" :items="[{
|
||||
label: `${update.date} - ${update.repo} - v${update.version}`,
|
||||
slot: 'changelog-' + index,
|
||||
defaultOpen: index === 0
|
||||
}]">
|
||||
<template #changelog-0>
|
||||
<div class="p-4 bg-gray-700 rounded-md">
|
||||
<div v-html="formatChangelog(update.changelog)"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #changelog-1>
|
||||
<div class="p-4 bg-gray-700 rounded-md">
|
||||
<div v-html="formatChangelog(update.changelog)"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #changelog-2>
|
||||
<div class="p-4 bg-gray-700 rounded-md">
|
||||
<div v-html="formatChangelog(update.changelog)"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #changelog-3>
|
||||
<div class="p-4 bg-gray-700 rounded-md">
|
||||
<div v-html="formatChangelog(update.changelog)"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #changelog-4>
|
||||
<div class="p-4 bg-gray-700 rounded-md">
|
||||
<div v-html="formatChangelog(update.changelog)"></div>
|
||||
</div>
|
||||
</template>
|
||||
</UAccordion>
|
||||
</div>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const latestUpdates = ref([]);
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response = await fetch('/latest_updates');
|
||||
if (response.ok) {
|
||||
latestUpdates.value = await response.json();
|
||||
} else {
|
||||
console.error('Erreur lors de la récupération des mises à jour');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
}
|
||||
});
|
||||
|
||||
function formatChangelog(changelog) {
|
||||
return changelog
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
if (line.startsWith('-')) {
|
||||
return `<li class="ml-4 list-disc">${line.substring(1).trim()}</li>`;
|
||||
}
|
||||
return line;
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
</script>
|
||||
@@ -1,69 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Github-Ntfy Add a Repo</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="script.js" defer></script>
|
||||
</head>
|
||||
<body class="bg-[#1b2124] text-gray-200">
|
||||
<header class="text-center py-8 bg-[#23453d] shadow-lg">
|
||||
<h1 class="text-5xl font-bold tracking-wide text-white">Github-Ntfy</h1>
|
||||
</header>
|
||||
|
||||
<main class="flex flex-wrap justify-center gap-8 py-12">
|
||||
<!-- Github Repo Section -->
|
||||
<section class="bg-[#23453d] rounded-lg shadow-lg p-6 w-full max-w-lg">
|
||||
<h2 class="text-2xl font-semibold mb-4">Add a Github Repo</h2>
|
||||
<form id="addRepoForm" class="space-y-6">
|
||||
<div>
|
||||
<label for="repo" class="block text-sm font-medium">Name of the Github Repo</label>
|
||||
<div class="mt-2 flex items-center border rounded-md bg-gray-700">
|
||||
<span class="px-3 text-gray-400">github.com/</span>
|
||||
<input type="text" name="repo" id="repo" autocomplete="repo" class="flex-1 py-2 px-3 bg-transparent focus:outline-none" placeholder="BreizhHardware/ntfy_alerts">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-4">
|
||||
<button type="button" class="px-4 py-2 text-gray-400 hover:text-white">Cancel</button>
|
||||
<button type="submit" class="px-4 py-2 bg-green-700 hover:bg-green-600 text-white font-semibold rounded-md">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-8">
|
||||
<h3 class="text-lg font-semibold mb-2">Watched Github Repositories</h3>
|
||||
<ul id="watchedReposList" class="space-y-2">
|
||||
<!-- Dynamically populated with JavaScript -->
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Docker Repo Section -->
|
||||
<section class="bg-[#23453d] rounded-lg shadow-lg p-6 w-full max-w-lg">
|
||||
<h2 class="text-2xl font-semibold mb-4">Add a Docker Repo</h2>
|
||||
<form id="addDockerRepoForm" class="space-y-6">
|
||||
<div>
|
||||
<label for="dockerRepo" class="block text-sm font-medium">Name of the Docker Repo</label>
|
||||
<div class="mt-2 flex items-center border rounded-md bg-gray-700">
|
||||
<span class="px-3 text-gray-400">hub.docker.com/r/</span>
|
||||
<input type="text" name="dockerRepo" id="dockerRepo" autocomplete="dockerRepo" class="flex-1 py-2 px-3 bg-transparent focus:outline-none" placeholder="breizhhardware/github-ntfy">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-4">
|
||||
<button type="button" class="px-4 py-2 text-gray-400 hover:text-white">Cancel</button>
|
||||
<button type="submit" class="px-4 py-2 bg-green-700 hover:bg-green-600 text-white font-semibold rounded-md">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-8">
|
||||
<h3 class="text-lg font-semibold mb-2">Watched Docker Repositories</h3>
|
||||
<ul id="watchedDockerReposList" class="space-y-2">
|
||||
<!-- Dynamically populated with JavaScript -->
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="text-center py-6 bg-[#23453d]">
|
||||
<p class="text-sm">I know this web interface is simple, but I'm improving!</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
25
web/nuxt.config.ts
Normal file
25
web/nuxt.config.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-05-15',
|
||||
devtools: { enabled: true },
|
||||
modules: [
|
||||
'@nuxt/ui'
|
||||
],
|
||||
ui: {
|
||||
global: true,
|
||||
icons: ['heroicons']
|
||||
},
|
||||
css: ['~/assets/css/main.css'],
|
||||
postcss: {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
},
|
||||
ssr: false,
|
||||
generate: {
|
||||
exclude: [
|
||||
/^\/api/
|
||||
]
|
||||
}
|
||||
})
|
||||
26
web/package.json
Normal file
26
web/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "nuxt-app",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/icon": "1.14.0",
|
||||
"@nuxt/ui": "3.1.3",
|
||||
"nuxt": "^3.17.5",
|
||||
"typescript": "^5.8.3",
|
||||
"vue": "^3.5.16",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxtjs/tailwindcss": "7.0.0-beta.0",
|
||||
"@tailwindcss/postcss": "^4.1.10",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^4.1.10"
|
||||
}
|
||||
}
|
||||
8476
web/pnpm-lock.yaml
generated
Normal file
8476
web/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
web/public/favicon.ico
Normal file
BIN
web/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
2
web/public/robots.txt
Normal file
2
web/public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-Agent: *
|
||||
Disallow:
|
||||
158
web/script.js
158
web/script.js
@@ -1,158 +0,0 @@
|
||||
document.getElementById('addRepoForm').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
let repoName = document.getElementById('repo').value;
|
||||
fetch('/app_repo', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({repo: repoName})
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
// Si la requête s'est bien déroulée, actualiser la liste des dépôts surveillés
|
||||
refreshWatchedRepos();
|
||||
} else {
|
||||
throw new Error('Erreur lors de l\'ajout du dépôt');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('addDockerRepoForm').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
let repoName = document.getElementById('dockerRepo').value;
|
||||
fetch('/app_docker_repo', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({repo: repoName})
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
// Si la requête s'est bien déroulée, actualiser la liste des dépôts surveillés
|
||||
refreshWatchedRepos();
|
||||
} else {
|
||||
throw new Error('Erreur lors de l\'ajout du dépôt');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
});
|
||||
|
||||
function refreshWatchedRepos() {
|
||||
fetch('/watched_repos')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const watchedReposList = document.getElementById('watchedReposList');
|
||||
// Vider la liste actuelle
|
||||
watchedReposList.innerHTML = '';
|
||||
// Ajouter chaque dépôt surveillé à la liste
|
||||
data.forEach(repo => {
|
||||
const listItem = document.createElement('li');
|
||||
const repoName = document.createElement('span');
|
||||
repoName.textContent = repo;
|
||||
repoName.className = 'repo-name';
|
||||
listItem.appendChild(repoName);
|
||||
|
||||
const deleteButton = document.createElement('button');
|
||||
deleteButton.textContent = ' X';
|
||||
deleteButton.className = 'delete-btn text-red-500 ml-2';
|
||||
deleteButton.addEventListener('click', () => {
|
||||
// Remove the repo from the watched repos
|
||||
// This is a placeholder. Replace it with your actual code to remove the repo from the watched repos.
|
||||
removeRepoFromWatchedRepos(repo);
|
||||
|
||||
// Remove the repo from the DOM
|
||||
listItem.remove();
|
||||
});
|
||||
listItem.appendChild(deleteButton);
|
||||
|
||||
watchedReposList.appendChild(listItem);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
|
||||
fetch('/watched_docker_repos')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const watchedDockerReposList = document.getElementById('watchedDockerReposList');
|
||||
// Vider la liste actuelle
|
||||
watchedDockerReposList.innerHTML = '';
|
||||
// Ajouter chaque dépôt surveillé à la liste
|
||||
data.forEach(repo => {
|
||||
const listItem = document.createElement('li');
|
||||
const repoName = document.createElement('span');
|
||||
repoName.textContent = repo;
|
||||
repoName.className = 'repo-name';
|
||||
listItem.appendChild(repoName);
|
||||
|
||||
const deleteButton = document.createElement('button');
|
||||
deleteButton.textContent = ' X';
|
||||
deleteButton.className = 'delete-btn text-red-500 ml-2';
|
||||
deleteButton.addEventListener('click', () => {
|
||||
// Remove the repo from the watched repos
|
||||
// This is a placeholder. Replace it with your actual code to remove the repo from the watched repos.
|
||||
removeDockerRepoFromWatchedRepos(repo);
|
||||
|
||||
// Remove the repo from the DOM
|
||||
listItem.remove();
|
||||
});
|
||||
listItem.appendChild(deleteButton);
|
||||
|
||||
watchedDockerReposList.appendChild(listItem);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function removeRepoFromWatchedRepos(repo) {
|
||||
fetch('/delete_repo', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({repo: repo})
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors de la suppression du dépôt');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function removeDockerRepoFromWatchedRepos(repo) {
|
||||
fetch('/delete_docker_repo', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({repo: repo})
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors de la suppression du dépôt');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Appeler la fonction pour charger les dépôts surveillés au chargement de la page
|
||||
refreshWatchedRepos();
|
||||
3
web/server/tsconfig.json
Normal file
3
web/server/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../.nuxt/tsconfig.server.json"
|
||||
}
|
||||
20
web/tailwind.config.js
Normal file
20
web/tailwind.config.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./components/**/*.{js,vue,ts}",
|
||||
"./layouts/**/*.vue",
|
||||
"./pages/**/*.vue",
|
||||
"./plugins/**/*.{js,ts}",
|
||||
"./app.vue",
|
||||
"./node_modules/@nuxt/ui/dist/**/*.{mjs,js,vue}"
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'emerald-950': '#23453d'
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
4
web/tsconfig.json
Normal file
4
web/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
||||
Reference in New Issue
Block a user