mirror of
https://github.com/BreizhHardware/ntfy_alerts.git
synced 2026-01-18 16:37:28 +01:00
feat(notifications): Add test notification routes for NTFY, Discord, Slack, and Gotify
This commit is contained in:
390
src/api.rs
390
src/api.rs
@@ -12,7 +12,8 @@ use crate::database::{
|
||||
get_user_by_username, verify_password, create_user, create_session,
|
||||
get_session, delete_session, get_app_settings, update_app_settings
|
||||
};
|
||||
use crate::models::{UserLogin, UserRegistration, AuthResponse, ApiResponse, AppSettings};
|
||||
use crate::models::{UserLogin, UserRegistration, AuthResponse, ApiResponse, AppSettings, GithubReleaseInfo};
|
||||
use crate::notifications::{ntfy, discord, slack, gotify};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct RepoRequest {
|
||||
@@ -128,6 +129,35 @@ pub async fn start_api() -> Result<(), Box<dyn std::error::Error + Send + Sync>>
|
||||
.and(with_db(versions_db.clone()))
|
||||
.and_then(is_configured);
|
||||
|
||||
// Test notification routes
|
||||
let test_ntfy_route = warp::path("test")
|
||||
.and(warp::path("ntfy"))
|
||||
.and(warp::post())
|
||||
.and(with_db(versions_db.clone()))
|
||||
.and(with_auth())
|
||||
.and_then(test_ntfy_notification);
|
||||
|
||||
let test_discord_route = warp::path("test")
|
||||
.and(warp::path("discord"))
|
||||
.and(warp::post())
|
||||
.and(with_db(versions_db.clone()))
|
||||
.and(with_auth())
|
||||
.and_then(test_discord_notification);
|
||||
|
||||
let test_slack_route = warp::path("test")
|
||||
.and(warp::path("slack"))
|
||||
.and(warp::post())
|
||||
.and(with_db(versions_db.clone()))
|
||||
.and(with_auth())
|
||||
.and_then(test_slack_notification);
|
||||
|
||||
let test_gotify_route = warp::path("test")
|
||||
.and(warp::path("gotify"))
|
||||
.and(warp::post())
|
||||
.and(with_db(versions_db.clone()))
|
||||
.and(with_auth())
|
||||
.and_then(test_gotify_notification);
|
||||
|
||||
// Configure CORS
|
||||
let cors = warp::cors()
|
||||
.allow_any_origin()
|
||||
@@ -148,6 +178,10 @@ pub async fn start_api() -> Result<(), Box<dyn std::error::Error + Send + Sync>>
|
||||
.or(get_settings_route)
|
||||
.or(update_settings_route)
|
||||
.or(is_configured_route)
|
||||
.or(test_ntfy_route)
|
||||
.or(test_discord_route)
|
||||
.or(test_slack_route)
|
||||
.or(test_gotify_route)
|
||||
.with(cors);
|
||||
|
||||
// Start the server
|
||||
@@ -872,3 +906,357 @@ async fn is_configured(db: Arc<Mutex<Connection>>) -> Result<impl Reply, Rejecti
|
||||
StatusCode::OK,
|
||||
))
|
||||
}
|
||||
|
||||
async fn test_ntfy_notification(db: Arc<Mutex<Connection>>, token: String) -> Result<impl Reply, Rejection> {
|
||||
let conn = db.lock().await;
|
||||
|
||||
// Verify authentication
|
||||
if let Ok(Some(session)) = get_session(&conn, &token) {
|
||||
if session.expires_at < Utc::now() {
|
||||
return Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "Session expired".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::UNAUTHORIZED,
|
||||
));
|
||||
}
|
||||
|
||||
// Retrieve settings
|
||||
match get_app_settings(&conn) {
|
||||
Ok(Some(settings)) => {
|
||||
if let Some(ntfy_url) = &settings.ntfy_url {
|
||||
// Send a test notification
|
||||
let result = ntfy::send_notification(
|
||||
ntfy_url,
|
||||
"Test Notification",
|
||||
"Ceci est une notification de test depuis l'API GitHub-NTFY.",
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: true,
|
||||
message: "Test notification sent successfully".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::OK,
|
||||
))
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error sending notification: {}", e);
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: format!("Error sending notification: {}", e),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "NTFY URL not configured".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::BAD_REQUEST,
|
||||
))
|
||||
}
|
||||
},
|
||||
Ok(None) => {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "No settings found".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::NOT_FOUND,
|
||||
))
|
||||
},
|
||||
Err(_) => {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "Error retrieving settings".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "Unauthorized".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::UNAUTHORIZED,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_discord_notification(db: Arc<Mutex<Connection>>, token: String) -> Result<impl Reply, Rejection> {
|
||||
let conn = db.lock().await;
|
||||
|
||||
// Verify authentication
|
||||
if let Ok(Some(session)) = get_session(&conn, &token) {
|
||||
if session.expires_at < Utc::now() {
|
||||
return Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "Session expired".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::UNAUTHORIZED,
|
||||
));
|
||||
}
|
||||
|
||||
// Retrieve settings
|
||||
match get_app_settings(&conn) {
|
||||
Ok(Some(settings)) => {
|
||||
if let Some(webhook_url) = &settings.discord_webhook_url {
|
||||
// Send a test notification
|
||||
let result = discord::send_notification(
|
||||
webhook_url,
|
||||
"Test Notification",
|
||||
"Ceci est une notification de test depuis l'API GitHub-NTFY.",
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: true,
|
||||
message: "Test notification sent successfully".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::OK,
|
||||
))
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error sending notification: {}", e);
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: format!("Error sending notification: {}", e),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "Discord webhook URL not configured".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::BAD_REQUEST,
|
||||
))
|
||||
}
|
||||
},
|
||||
Ok(None) => {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "No settings found".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::NOT_FOUND,
|
||||
))
|
||||
},
|
||||
Err(_) => {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "Error retrieving settings".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "Unauthorized".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::UNAUTHORIZED,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_slack_notification(db: Arc<Mutex<Connection>>, token: String) -> Result<impl Reply, Rejection> {
|
||||
let conn = db.lock().await;
|
||||
|
||||
// Verify authentication
|
||||
if let Ok(Some(session)) = get_session(&conn, &token) {
|
||||
if session.expires_at < Utc::now() {
|
||||
return Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "Session expired".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::UNAUTHORIZED,
|
||||
));
|
||||
}
|
||||
|
||||
// Retrieve settings
|
||||
match get_app_settings(&conn) {
|
||||
Ok(Some(settings)) => {
|
||||
// Send a test notification
|
||||
let result = slack::send_notification(
|
||||
&settings.slack_webhook_url,
|
||||
"Test Notification",
|
||||
"Ceci est une notification de test depuis l'API GitHub-NTFY.",
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: true,
|
||||
message: "Test notification sent successfully".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::OK,
|
||||
))
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error sending notification: {}", e);
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: format!("Error sending notification: {}", e),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
))
|
||||
}
|
||||
}
|
||||
},
|
||||
Ok(None) => {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "No settings found".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::NOT_FOUND,
|
||||
))
|
||||
},
|
||||
Err(_) => {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "Error retrieving settings".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "Unauthorized".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::UNAUTHORIZED,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_gotify_notification(db: Arc<Mutex<Connection>>, token: String) -> Result<impl Reply, Rejection> {
|
||||
let conn = db.lock().await;
|
||||
|
||||
// Verify authentication
|
||||
if let Ok(Some(session)) = get_session(&conn, &token) {
|
||||
if session.expires_at < Utc::now() {
|
||||
return Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "Session expired".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::UNAUTHORIZED,
|
||||
));
|
||||
}
|
||||
|
||||
// Retrieve settings
|
||||
match get_app_settings(&conn) {
|
||||
Ok(Some(settings)) => {
|
||||
// Send a test notification
|
||||
let result = gotify::send_notification(
|
||||
&settings.gotify_url,
|
||||
"Test Notification",
|
||||
"Ceci est une notification de test depuis l'API GitHub-NTFY.",
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: true,
|
||||
message: "Test notification sent successfully".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::OK,
|
||||
))
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error sending notification: {}", e);
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: format!("Error sending notification: {}", e),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
))
|
||||
}
|
||||
}
|
||||
},
|
||||
Ok(None) => {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "No settings found".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::NOT_FOUND,
|
||||
))
|
||||
},
|
||||
Err(_) => {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "Error retrieving settings".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&ApiResponse::<()> {
|
||||
success: false,
|
||||
message: "Unauthorized".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
StatusCode::UNAUTHORIZED,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,4 +82,28 @@ pub async fn send_docker_notification(release: &DockerReleaseInfo, webhook_url:
|
||||
error!("Error sending to Discord: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_notification(webhook_url: &str, title: &str, message: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let data = json!({
|
||||
"content": format!("**{}**\n{}", title, message),
|
||||
"username": "GitHub Ntfy Test"
|
||||
});
|
||||
|
||||
let response = client.post(webhook_url)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&data)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if response.status().is_success() {
|
||||
info!("Test notification sent to Discord successfully");
|
||||
Ok(())
|
||||
} else {
|
||||
let error_msg = format!("Failed to send test notification to Discord. Status code: {}", response.status());
|
||||
error!("{}", error_msg);
|
||||
Err(error_msg.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,4 +75,32 @@ pub async fn send_docker_notification(release: &DockerReleaseInfo, token: &str,
|
||||
error!("Error sending to Gotify: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_notification(gotify_url: &str, title: &str, message: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
// Note: For test notifications, we'll need to get the token from settings
|
||||
// This function will be called from the API where we have access to settings
|
||||
let url = format!("{}/message", gotify_url);
|
||||
|
||||
let content = json!({
|
||||
"title": title,
|
||||
"message": message,
|
||||
"priority": 5
|
||||
});
|
||||
|
||||
let response = client.post(&url)
|
||||
.json(&content)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if response.status().is_success() {
|
||||
info!("Test notification sent to Gotify successfully");
|
||||
Ok(())
|
||||
} else {
|
||||
let error_msg = format!("Failed to send test notification to Gotify. Status code: {}", response.status());
|
||||
error!("{}", error_msg);
|
||||
Err(error_msg.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,4 +81,27 @@ pub async fn send_docker_notification(release: &DockerReleaseInfo, auth: &str, n
|
||||
error!("Error sending to Ntfy: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_notification(ntfy_url: &str, title: &str, message: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("Title", HeaderValue::from_str(title)?);
|
||||
headers.insert("Priority", HeaderValue::from_static("default"));
|
||||
|
||||
let response = client.post(ntfy_url)
|
||||
.headers(headers)
|
||||
.body(message.to_string())
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if response.status().is_success() {
|
||||
info!("Test notification sent to NTFY successfully");
|
||||
Ok(())
|
||||
} else {
|
||||
let error_msg = format!("Failed to send test notification to NTFY. Status code: {}", response.status());
|
||||
error!("{}", error_msg);
|
||||
Err(error_msg.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,4 +128,27 @@ pub async fn send_docker_notification(release: &DockerReleaseInfo, webhook_url:
|
||||
error!("Error sending to Slack: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_notification(webhook_url: &str, title: &str, message: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let data = json!({
|
||||
"text": format!("*{}*\n{}", title, message)
|
||||
});
|
||||
|
||||
let response = client.post(webhook_url)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&data)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if response.status().is_success() {
|
||||
info!("Test notification sent to Slack successfully");
|
||||
Ok(())
|
||||
} else {
|
||||
let error_msg = format!("Failed to send test notification to Slack. Status code: {}", response.status());
|
||||
error!("{}", error_msg);
|
||||
Err(error_msg.into())
|
||||
}
|
||||
}
|
||||
|
||||
5
web/layouts/auth.vue
Normal file
5
web/layouts/auth.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-900 text-gray-200 flex items-center justify-center">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,63 +1,64 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-center min-h-screen bg-gray-900">
|
||||
<div class="w-full max-w-md p-8 space-y-8 bg-gray-800 rounded-lg shadow-lg">
|
||||
<div class="text-center">
|
||||
<h1 class="text-2xl font-bold text-white">Login</h1>
|
||||
<p class="mt-2 text-sm text-gray-400">Sign in to manage your notifications</p>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="handleLogin" class="mt-8 space-y-6">
|
||||
<div>
|
||||
<label for="username" class="block text-sm font-medium text-gray-400">Username</label>
|
||||
<input
|
||||
id="username"
|
||||
v-model="form.username"
|
||||
type="text"
|
||||
required
|
||||
class="block w-full px-3 py-2 mt-1 text-white placeholder-gray-500 bg-gray-700 border border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-gray-400">Password</label>
|
||||
<input
|
||||
id="password"
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
required
|
||||
class="block w-full px-3 py-2 mt-1 text-white placeholder-gray-500 bg-gray-700 border border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="p-3 text-sm text-red-500 bg-red-100 rounded-md">
|
||||
{{ error }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<UButton
|
||||
type="submit"
|
||||
color="primary"
|
||||
block
|
||||
:loading="loading"
|
||||
>
|
||||
Login
|
||||
</UButton>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<p class="text-sm text-gray-400">
|
||||
First time?
|
||||
<NuxtLink to="/onboarding" class="font-medium text-indigo-400 hover:text-indigo-300">
|
||||
Setup your application
|
||||
</NuxtLink>
|
||||
</p>
|
||||
</div>
|
||||
<div class="w-full max-w-md p-8 space-y-8 bg-gray-800 rounded-lg shadow-lg">
|
||||
<div class="text-center">
|
||||
<h1 class="text-2xl font-bold text-white">Login</h1>
|
||||
<p class="mt-2 text-sm text-gray-400">Sign in to manage your notifications</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="handleLogin" class="mt-8 space-y-6">
|
||||
<div>
|
||||
|
||||
<input
|
||||
id="username"
|
||||
v-model="form.username"
|
||||
type="text"
|
||||
required
|
||||
class="block w-full px-3 py-2 mt-1 text-white placeholder-gray-500 bg-gray-700 border border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-gray-400">Password</label>
|
||||
<div v-if="error" class="p-3 text-sm text-red-500 bg-red-100 rounded-md">
|
||||
id="password"
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
required
|
||||
class="block w-full px-3 py-2 mt-1 text-white placeholder-gray-500 bg-gray-700 border border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
{{ error }}
|
||||
<div v-if="error" class="p-3 text-sm text-red-500 bg-red-100 rounded-md">
|
||||
{{ error }}
|
||||
</div>
|
||||
<UButton
|
||||
<div>
|
||||
<UButton
|
||||
type="submit"
|
||||
color="primary"
|
||||
block
|
||||
:loading="loading"
|
||||
>
|
||||
Login
|
||||
</UButton>
|
||||
</div>
|
||||
</form>
|
||||
<p class="text-sm text-gray-400">
|
||||
<div class="text-center mt-4">
|
||||
<p class="text-sm text-gray-400">
|
||||
First time?
|
||||
<NuxtLink to="/onboarding" class="font-medium text-indigo-400 hover:text-indigo-300">
|
||||
Setup your application
|
||||
</NuxtLink>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// Utiliser le layout d'authentification
|
||||
definePageMeta({
|
||||
layout: 'auth'
|
||||
})
|
||||
|
||||
const auth = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-900 p-6">
|
||||
<div class="min-h-screen p-6">
|
||||
<div class="max-w-3xl mx-auto bg-gray-800 rounded-lg shadow-lg overflow-hidden">
|
||||
<div class="p-6 border-b border-gray-700">
|
||||
<h1 class="text-2xl font-bold text-white">Application Setup</h1>
|
||||
@@ -247,6 +247,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// Utiliser le layout d'authentification
|
||||
definePageMeta({
|
||||
layout: 'auth'
|
||||
})
|
||||
|
||||
const auth = useAuth();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<AppHeader />
|
||||
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<h1 class="text-2xl font-bold text-white mb-8">Settings</h1>
|
||||
@@ -15,7 +14,19 @@
|
||||
<div class="space-y-6">
|
||||
<!-- NTFY -->
|
||||
<div>
|
||||
<h3 class="text-lg font-medium mb-2">NTFY</h3>
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<h3 class="text-lg font-medium">NTFY</h3>
|
||||
<UButton
|
||||
@click="testNotification('ntfy')"
|
||||
size="sm"
|
||||
color="gray"
|
||||
variant="outline"
|
||||
:loading="testingNotifications.ntfy"
|
||||
:disabled="!settings.ntfy_url"
|
||||
>
|
||||
Tester
|
||||
</UButton>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<UInput
|
||||
v-model="settings.ntfy_url"
|
||||
@@ -44,7 +55,19 @@
|
||||
|
||||
<!-- Discord -->
|
||||
<div>
|
||||
<h3 class="text-lg font-medium mb-2">Discord</h3>
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<h3 class="text-lg font-medium">Discord</h3>
|
||||
<UButton
|
||||
@click="testNotification('discord')"
|
||||
size="sm"
|
||||
color="gray"
|
||||
variant="outline"
|
||||
:loading="testingNotifications.discord"
|
||||
:disabled="!settings.discord_webhook_url"
|
||||
>
|
||||
Tester
|
||||
</UButton>
|
||||
</div>
|
||||
<UInput
|
||||
v-model="settings.discord_webhook_url"
|
||||
label="Discord Webhook URL"
|
||||
@@ -55,7 +78,19 @@
|
||||
|
||||
<!-- Slack -->
|
||||
<div>
|
||||
<h3 class="text-lg font-medium mb-2">Slack</h3>
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<h3 class="text-lg font-medium">Slack</h3>
|
||||
<UButton
|
||||
@click="testNotification('slack')"
|
||||
size="sm"
|
||||
color="gray"
|
||||
variant="outline"
|
||||
:loading="testingNotifications.slack"
|
||||
:disabled="!settings.slack_webhook_url"
|
||||
>
|
||||
Tester
|
||||
</UButton>
|
||||
</div>
|
||||
<UInput
|
||||
v-model="settings.slack_webhook_url"
|
||||
label="Slack Webhook URL"
|
||||
@@ -66,7 +101,19 @@
|
||||
|
||||
<!-- Gotify -->
|
||||
<div>
|
||||
<h3 class="text-lg font-medium mb-2">Gotify</h3>
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<h3 class="text-lg font-medium">Gotify</h3>
|
||||
<UButton
|
||||
@click="testNotification('gotify')"
|
||||
size="sm"
|
||||
color="gray"
|
||||
variant="outline"
|
||||
:loading="testingNotifications.gotify"
|
||||
:disabled="!settings.gotify_url || !settings.gotify_token"
|
||||
>
|
||||
Tester
|
||||
</UButton>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<UInput
|
||||
v-model="settings.gotify_url"
|
||||
@@ -208,6 +255,12 @@ const settings = reactive({
|
||||
const error = ref('');
|
||||
const success = ref('');
|
||||
const loading = ref(false);
|
||||
const testingNotifications = reactive({
|
||||
ntfy: false,
|
||||
discord: false,
|
||||
slack: false,
|
||||
gotify: false
|
||||
});
|
||||
|
||||
// Load current settings
|
||||
async function loadSettings() {
|
||||
@@ -291,4 +344,41 @@ async function saveSettings() {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to test notifications
|
||||
async function testNotification(type) {
|
||||
try {
|
||||
// Set loading state for the specific notification type
|
||||
testingNotifications[type] = true;
|
||||
error.value = '';
|
||||
success.value = '';
|
||||
|
||||
// Send test notification via our API endpoint
|
||||
const response = await fetch(`/test/${type}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': auth.token.value
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
throw new Error(data.message || 'Error sending test notification');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
success.value = `Notification de test envoyée avec succès via ${type.toUpperCase()}`;
|
||||
} else {
|
||||
throw new Error(data.message || 'Error sending test notification');
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.message || 'Une erreur est survenue lors de l\'envoi de la notification de test';
|
||||
} finally {
|
||||
// Reset loading state for the specific notification type
|
||||
testingNotifications[type] = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user