mirror of
https://github.com/BreizhHardware/ntfy_alerts.git
synced 2026-03-18 21:40:38 +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_user_by_username, verify_password, create_user, create_session,
|
||||||
get_session, delete_session, get_app_settings, update_app_settings
|
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)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct RepoRequest {
|
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(with_db(versions_db.clone()))
|
||||||
.and_then(is_configured);
|
.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
|
// Configure CORS
|
||||||
let cors = warp::cors()
|
let cors = warp::cors()
|
||||||
.allow_any_origin()
|
.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(get_settings_route)
|
||||||
.or(update_settings_route)
|
.or(update_settings_route)
|
||||||
.or(is_configured_route)
|
.or(is_configured_route)
|
||||||
|
.or(test_ntfy_route)
|
||||||
|
.or(test_discord_route)
|
||||||
|
.or(test_slack_route)
|
||||||
|
.or(test_gotify_route)
|
||||||
.with(cors);
|
.with(cors);
|
||||||
|
|
||||||
// Start the server
|
// Start the server
|
||||||
@@ -872,3 +906,357 @@ async fn is_configured(db: Arc<Mutex<Connection>>) -> Result<impl Reply, Rejecti
|
|||||||
StatusCode::OK,
|
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,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -83,3 +83,27 @@ pub async fn send_docker_notification(release: &DockerReleaseInfo, webhook_url:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -76,3 +76,31 @@ pub async fn send_docker_notification(release: &DockerReleaseInfo, token: &str,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -82,3 +82,26 @@ pub async fn send_docker_notification(release: &DockerReleaseInfo, auth: &str, n
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -129,3 +129,26 @@ pub async fn send_docker_notification(release: &DockerReleaseInfo, webhook_url:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,5 +1,4 @@
|
|||||||
<template>
|
<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="w-full max-w-md p-8 space-y-8 bg-gray-800 rounded-lg shadow-lg">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h1 class="text-2xl font-bold text-white">Login</h1>
|
<h1 class="text-2xl font-bold text-white">Login</h1>
|
||||||
@@ -8,7 +7,7 @@
|
|||||||
|
|
||||||
<form @submit.prevent="handleLogin" class="mt-8 space-y-6">
|
<form @submit.prevent="handleLogin" class="mt-8 space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label for="username" class="block text-sm font-medium text-gray-400">Username</label>
|
|
||||||
<input
|
<input
|
||||||
id="username"
|
id="username"
|
||||||
v-model="form.username"
|
v-model="form.username"
|
||||||
@@ -17,10 +16,10 @@
|
|||||||
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"
|
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>
|
||||||
<div>
|
<div>
|
||||||
<label for="password" class="block text-sm font-medium text-gray-400">Password</label>
|
<label for="password" class="block text-sm font-medium text-gray-400">Password</label>
|
||||||
<input
|
<div v-if="error" class="p-3 text-sm text-red-500 bg-red-100 rounded-md">
|
||||||
id="password"
|
id="password"
|
||||||
v-model="form.password"
|
v-model="form.password"
|
||||||
type="password"
|
type="password"
|
||||||
@@ -28,11 +27,11 @@
|
|||||||
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"
|
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>
|
||||||
|
{{ error }}
|
||||||
<div v-if="error" class="p-3 text-sm text-red-500 bg-red-100 rounded-md">
|
<div v-if="error" class="p-3 text-sm text-red-500 bg-red-100 rounded-md">
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</div>
|
</div>
|
||||||
|
<UButton
|
||||||
<div>
|
<div>
|
||||||
<UButton
|
<UButton
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -44,7 +43,7 @@
|
|||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<p class="text-sm text-gray-400">
|
||||||
<div class="text-center mt-4">
|
<div class="text-center mt-4">
|
||||||
<p class="text-sm text-gray-400">
|
<p class="text-sm text-gray-400">
|
||||||
First time?
|
First time?
|
||||||
@@ -52,12 +51,14 @@
|
|||||||
Setup your application
|
Setup your application
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
// Utiliser le layout d'authentification
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'auth'
|
||||||
|
})
|
||||||
|
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<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="max-w-3xl mx-auto bg-gray-800 rounded-lg shadow-lg overflow-hidden">
|
||||||
<div class="p-6 border-b border-gray-700">
|
<div class="p-6 border-b border-gray-700">
|
||||||
<h1 class="text-2xl font-bold text-white">Application Setup</h1>
|
<h1 class="text-2xl font-bold text-white">Application Setup</h1>
|
||||||
@@ -247,6 +247,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
// Utiliser le layout d'authentification
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'auth'
|
||||||
|
})
|
||||||
|
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<AppHeader />
|
|
||||||
|
|
||||||
<div class="container mx-auto px-4 py-8">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<h1 class="text-2xl font-bold text-white mb-8">Settings</h1>
|
<h1 class="text-2xl font-bold text-white mb-8">Settings</h1>
|
||||||
@@ -15,7 +14,19 @@
|
|||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- NTFY -->
|
<!-- NTFY -->
|
||||||
<div>
|
<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">
|
<div class="space-y-2">
|
||||||
<UInput
|
<UInput
|
||||||
v-model="settings.ntfy_url"
|
v-model="settings.ntfy_url"
|
||||||
@@ -44,7 +55,19 @@
|
|||||||
|
|
||||||
<!-- Discord -->
|
<!-- Discord -->
|
||||||
<div>
|
<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
|
<UInput
|
||||||
v-model="settings.discord_webhook_url"
|
v-model="settings.discord_webhook_url"
|
||||||
label="Discord Webhook URL"
|
label="Discord Webhook URL"
|
||||||
@@ -55,7 +78,19 @@
|
|||||||
|
|
||||||
<!-- Slack -->
|
<!-- Slack -->
|
||||||
<div>
|
<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
|
<UInput
|
||||||
v-model="settings.slack_webhook_url"
|
v-model="settings.slack_webhook_url"
|
||||||
label="Slack Webhook URL"
|
label="Slack Webhook URL"
|
||||||
@@ -66,7 +101,19 @@
|
|||||||
|
|
||||||
<!-- Gotify -->
|
<!-- Gotify -->
|
||||||
<div>
|
<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">
|
<div class="space-y-2">
|
||||||
<UInput
|
<UInput
|
||||||
v-model="settings.gotify_url"
|
v-model="settings.gotify_url"
|
||||||
@@ -208,6 +255,12 @@ const settings = reactive({
|
|||||||
const error = ref('');
|
const error = ref('');
|
||||||
const success = ref('');
|
const success = ref('');
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const testingNotifications = reactive({
|
||||||
|
ntfy: false,
|
||||||
|
discord: false,
|
||||||
|
slack: false,
|
||||||
|
gotify: false
|
||||||
|
});
|
||||||
|
|
||||||
// Load current settings
|
// Load current settings
|
||||||
async function loadSettings() {
|
async function loadSettings() {
|
||||||
@@ -291,4 +344,41 @@ async function saveSettings() {
|
|||||||
loading.value = false;
|
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>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user