feat: fully offline web interface with embedded Font Awesome
- Replace CDN with local Font Awesome files - Add favicon handler - Embed all static assets (CSS, fonts, logo) - Update README with Font Awesome license attribution - Bump version to 0.2.3
This commit is contained in:
10
README.md
10
README.md
@@ -575,10 +575,20 @@ ae_anons/
|
|||||||
|
|
||||||
## 🙏 Благодарности
|
## 🙏 Благодарности
|
||||||
|
|
||||||
|
### Nexrender
|
||||||
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
||||||
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
||||||
для автоматизации After Effects.
|
для автоматизации After Effects.
|
||||||
|
|
||||||
|
### Font Awesome Free 6.4.0
|
||||||
|
|
||||||
|
Веб-интерфейс использует иконки и шрифты **Font Awesome Free**:
|
||||||
|
|
||||||
|
- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
||||||
|
- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL)
|
||||||
|
|
||||||
|
© 2023 Fonticons, Inc. — https://fontawesome.com
|
||||||
|
|
||||||
## Лицензия
|
## Лицензия
|
||||||
|
|
||||||
**AE Anons** — [License](LICENSE)
|
**AE Anons** — [License](LICENSE)
|
||||||
|
|||||||
Binary file not shown.
@@ -575,10 +575,20 @@ ae_anons/
|
|||||||
|
|
||||||
## 🙏 Благодарности
|
## 🙏 Благодарности
|
||||||
|
|
||||||
|
### Nexrender
|
||||||
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
||||||
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
||||||
для автоматизации After Effects.
|
для автоматизации After Effects.
|
||||||
|
|
||||||
|
### Font Awesome Free 6.4.0
|
||||||
|
|
||||||
|
Веб-интерфейс использует иконки и шрифты **Font Awesome Free**:
|
||||||
|
|
||||||
|
- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
||||||
|
- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL)
|
||||||
|
|
||||||
|
© 2023 Fonticons, Inc. — https://fontawesome.com
|
||||||
|
|
||||||
## Лицензия
|
## Лицензия
|
||||||
|
|
||||||
**AE Anons** — [License](LICENSE)
|
**AE Anons** — [License](LICENSE)
|
||||||
|
|||||||
Binary file not shown.
@@ -575,10 +575,20 @@ ae_anons/
|
|||||||
|
|
||||||
## 🙏 Благодарности
|
## 🙏 Благодарности
|
||||||
|
|
||||||
|
### Nexrender
|
||||||
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
||||||
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
||||||
для автоматизации After Effects.
|
для автоматизации After Effects.
|
||||||
|
|
||||||
|
### Font Awesome Free 6.4.0
|
||||||
|
|
||||||
|
Веб-интерфейс использует иконки и шрифты **Font Awesome Free**:
|
||||||
|
|
||||||
|
- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
||||||
|
- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL)
|
||||||
|
|
||||||
|
© 2023 Fonticons, Inc. — https://fontawesome.com
|
||||||
|
|
||||||
## Лицензия
|
## Лицензия
|
||||||
|
|
||||||
**AE Anons** — [License](LICENSE)
|
**AE Anons** — [License](LICENSE)
|
||||||
|
|||||||
Binary file not shown.
@@ -575,10 +575,20 @@ ae_anons/
|
|||||||
|
|
||||||
## 🙏 Благодарности
|
## 🙏 Благодарности
|
||||||
|
|
||||||
|
### Nexrender
|
||||||
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
||||||
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
||||||
для автоматизации After Effects.
|
для автоматизации After Effects.
|
||||||
|
|
||||||
|
### Font Awesome Free 6.4.0
|
||||||
|
|
||||||
|
Веб-интерфейс использует иконки и шрифты **Font Awesome Free**:
|
||||||
|
|
||||||
|
- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
||||||
|
- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL)
|
||||||
|
|
||||||
|
© 2023 Fonticons, Inc. — https://fontawesome.com
|
||||||
|
|
||||||
## Лицензия
|
## Лицензия
|
||||||
|
|
||||||
**AE Anons** — [License](LICENSE)
|
**AE Anons** — [License](LICENSE)
|
||||||
|
|||||||
Binary file not shown.
@@ -575,10 +575,20 @@ ae_anons/
|
|||||||
|
|
||||||
## 🙏 Благодарности
|
## 🙏 Благодарности
|
||||||
|
|
||||||
|
### Nexrender
|
||||||
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
||||||
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
||||||
для автоматизации After Effects.
|
для автоматизации After Effects.
|
||||||
|
|
||||||
|
### Font Awesome Free 6.4.0
|
||||||
|
|
||||||
|
Веб-интерфейс использует иконки и шрифты **Font Awesome Free**:
|
||||||
|
|
||||||
|
- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
||||||
|
- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL)
|
||||||
|
|
||||||
|
© 2023 Fonticons, Inc. — https://fontawesome.com
|
||||||
|
|
||||||
## Лицензия
|
## Лицензия
|
||||||
|
|
||||||
**AE Anons** — [License](LICENSE)
|
**AE Anons** — [License](LICENSE)
|
||||||
|
|||||||
Binary file not shown.
8788
src/static/fontawesome/all.min.css
vendored
Normal file
8788
src/static/fontawesome/all.min.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/static/fontawesome/webfonts/fa-brands-400.woff2
Normal file
BIN
src/static/fontawesome/webfonts/fa-brands-400.woff2
Normal file
Binary file not shown.
BIN
src/static/fontawesome/webfonts/fa-regular-400.woff2
Normal file
BIN
src/static/fontawesome/webfonts/fa-regular-400.woff2
Normal file
Binary file not shown.
BIN
src/static/fontawesome/webfonts/fa-solid-900.woff2
Normal file
BIN
src/static/fontawesome/webfonts/fa-solid-900.woff2
Normal file
Binary file not shown.
@@ -5,7 +5,12 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>AE Anons - Nexrender Job Manager</title>
|
<title>AE Anons - Nexrender Job Manager</title>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/png" href="/favicon.ico">
|
||||||
|
<link rel="apple-touch-icon" href="/favicon.ico">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/static/fontawesome/all.min.css">
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
30
src/web.rs
30
src/web.rs
@@ -11,6 +11,7 @@ use serde::Serialize;
|
|||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -33,43 +34,32 @@ impl JobInfo {
|
|||||||
let uid = job.get("uid")?.as_str()?.to_string();
|
let uid = job.get("uid")?.as_str()?.to_string();
|
||||||
let state = job.get("state")?.as_str()?.to_string();
|
let state = job.get("state")?.as_str()?.to_string();
|
||||||
|
|
||||||
// Извлекаем имя выходного файла из postrender actions
|
|
||||||
let outfile_name = job
|
let outfile_name = job
|
||||||
.get("actions")
|
.get("actions")
|
||||||
.and_then(|a| a.get("postrender"))
|
.and_then(|a| a.get("postrender"))
|
||||||
.and_then(|p| p.as_array())
|
.and_then(|p| p.as_array())
|
||||||
.and_then(|arr| {
|
.and_then(|arr| {
|
||||||
// Ищем действие copy (в нём финальный путь)
|
|
||||||
arr.iter()
|
arr.iter()
|
||||||
.find_map(|action| {
|
.find_map(|action| {
|
||||||
// Проверяем, что это действие copy
|
|
||||||
action
|
action
|
||||||
.get("module")
|
.get("module")
|
||||||
.and_then(|m| m.as_str())
|
.and_then(|m| m.as_str())
|
||||||
.filter(|&m| m == "@nexrender/action-copy")
|
.filter(|&m| m == "@nexrender/action-copy")
|
||||||
.and_then(|_| {
|
.and_then(|_| action.get("output").and_then(|o| o.as_str()))
|
||||||
// Извлекаем output из copy
|
|
||||||
action.get("output").and_then(|o| o.as_str())
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
// Если copy не найдено, пробуем encode
|
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
arr.iter()
|
arr.iter()
|
||||||
.find_map(|action| action.get("output").and_then(|o| o.as_str()))
|
.find_map(|action| action.get("output").and_then(|o| o.as_str()))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.map(|path| {
|
.map(|path| {
|
||||||
// Извлекаем только имя файла из полного пути
|
|
||||||
std::path::Path::new(path)
|
std::path::Path::new(path)
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|n| n.to_str())
|
.and_then(|n| n.to_str())
|
||||||
.unwrap_or(path)
|
.unwrap_or(path)
|
||||||
.to_string()
|
.to_string()
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| format!("job_{}", uid));
|
||||||
// Если не удалось извлечь, используем UID
|
|
||||||
format!("job_{}", uid)
|
|
||||||
});
|
|
||||||
|
|
||||||
Some(JobInfo {
|
Some(JobInfo {
|
||||||
uid,
|
uid,
|
||||||
@@ -87,7 +77,6 @@ impl JobInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// web.rs - функция run_web_server
|
|
||||||
pub async fn run_web_server(config: Config) -> anyhow::Result<()> {
|
pub async fn run_web_server(config: Config) -> anyhow::Result<()> {
|
||||||
let web_port = config.web_port;
|
let web_port = config.web_port;
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
@@ -97,7 +86,10 @@ pub async fn run_web_server(config: Config) -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(index_page))
|
.route("/", get(index_page))
|
||||||
|
.route("/favicon.ico", get(favicon))
|
||||||
.route("/static/style.css", get(style_css))
|
.route("/static/style.css", get(style_css))
|
||||||
|
.route("/static/fontawesome/all.min.css", get(fontawesome_css))
|
||||||
|
.nest_service("/static/fontawesome/webfonts", ServeDir::new("src/static/fontawesome/webfonts"))
|
||||||
.route("/assets/logo.png", get(logo_png))
|
.route("/assets/logo.png", get(logo_png))
|
||||||
.route("/api/jobs", get(list_jobs))
|
.route("/api/jobs", get(list_jobs))
|
||||||
.route("/api/generate", post(generate_jobs))
|
.route("/api/generate", post(generate_jobs))
|
||||||
@@ -123,10 +115,17 @@ async fn style_css() -> impl IntoResponse {
|
|||||||
([(CONTENT_TYPE, "text/css")], include_str!("static/style.css"))
|
([(CONTENT_TYPE, "text/css")], include_str!("static/style.css"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn fontawesome_css() -> impl IntoResponse {
|
||||||
|
([(CONTENT_TYPE, "text/css")], include_str!("static/fontawesome/all.min.css"))
|
||||||
|
}
|
||||||
|
|
||||||
async fn logo_png() -> impl IntoResponse {
|
async fn logo_png() -> impl IntoResponse {
|
||||||
([(CONTENT_TYPE, "image/png")], include_bytes!("../assets/logo.png").as_slice())
|
([(CONTENT_TYPE, "image/png")], include_bytes!("../assets/logo.png").as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn favicon() -> impl IntoResponse {
|
||||||
|
([(CONTENT_TYPE, "image/png")], include_bytes!("../assets/logo.png").as_slice())
|
||||||
|
}
|
||||||
|
|
||||||
async fn list_jobs(State(state): State<AppState>) -> Result<Json<Vec<JobInfo>>, AppError> {
|
async fn list_jobs(State(state): State<AppState>) -> Result<Json<Vec<JobInfo>>, AppError> {
|
||||||
let jobs_json = fetch_all_jobs(&state.config.nexrender_api_url)
|
let jobs_json = fetch_all_jobs(&state.config.nexrender_api_url)
|
||||||
@@ -195,7 +194,6 @@ async fn stop_all_jobs(State(state): State<AppState>) -> Result<impl IntoRespons
|
|||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
let api_url = &state.config.nexrender_api_url;
|
let api_url = &state.config.nexrender_api_url;
|
||||||
|
|
||||||
// Получаем все задания
|
|
||||||
let jobs = fetch_all_jobs(api_url)
|
let jobs = fetch_all_jobs(api_url)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
@@ -205,7 +203,6 @@ async fn stop_all_jobs(State(state): State<AppState>) -> Result<impl IntoRespons
|
|||||||
let state = job.get("state").and_then(|s| s.as_str()).unwrap_or("");
|
let state = job.get("state").and_then(|s| s.as_str()).unwrap_or("");
|
||||||
if state == "queued" || state == "started" || state == "processing" {
|
if state == "queued" || state == "started" || state == "processing" {
|
||||||
if let Some(uid) = job.get("uid").and_then(|u| u.as_str()) {
|
if let Some(uid) = job.get("uid").and_then(|u| u.as_str()) {
|
||||||
// Отправляем DELETE запрос для остановки задания
|
|
||||||
let _ = client.delete(&format!("{}/{}", api_url, uid)).send().await;
|
let _ = client.delete(&format!("{}/{}", api_url, uid)).send().await;
|
||||||
stopped += 1;
|
stopped += 1;
|
||||||
log::info!("Stopped job: {}", uid);
|
log::info!("Stopped job: {}", uid);
|
||||||
@@ -217,7 +214,6 @@ async fn stop_all_jobs(State(state): State<AppState>) -> Result<impl IntoRespons
|
|||||||
Ok((StatusCode::OK, format!("Stopped {} jobs", stopped)))
|
Ok((StatusCode::OK, format!("Stopped {} jobs", stopped)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error handling
|
|
||||||
struct AppError(StatusCode, String);
|
struct AppError(StatusCode, String);
|
||||||
|
|
||||||
impl IntoResponse for AppError {
|
impl IntoResponse for AppError {
|
||||||
|
|||||||
Reference in New Issue
Block a user