include templates in library statically
This commit is contained in:
parent
c6118f52c4
commit
e61b683baa
26
Cargo.toml
26
Cargo.toml
@ -2,7 +2,7 @@
|
|||||||
name = "actix-admin"
|
name = "actix-admin"
|
||||||
description = "An admin interface for actix-web"
|
description = "An admin interface for actix-web"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
repository = "https://github.com/mgugger/actix-admin"
|
repository = "https://github.com/mgugger/actix-admin"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
exclude = [
|
exclude = [
|
||||||
@ -19,26 +19,26 @@ name = "actix_admin"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "^4.2.1"
|
actix-web = "^4.3.1"
|
||||||
actix-session = { version = "^0.7.2", features = [] }
|
actix-session = { version = "^0.7.2", features = [] }
|
||||||
actix-multipart = "^0.4.0"
|
actix-multipart = "^0.6.0"
|
||||||
actix-files = "^0.6.2"
|
actix-files = "^0.6.2"
|
||||||
futures-util = "0.3.25"
|
futures-util = "0.3.27"
|
||||||
chrono = "0.4.23"
|
chrono = "0.4.24"
|
||||||
tera = "^1.17.1"
|
tera = "^1.18.1"
|
||||||
async-trait = "^0.1.60"
|
async-trait = "^0.1.67"
|
||||||
lazy_static = "^1.4.0"
|
lazy_static = "^1.4.0"
|
||||||
itertools = "^0.10.5"
|
itertools = "^0.10.5"
|
||||||
serde = "^1.0.152"
|
serde = "^1.0.158"
|
||||||
serde_derive = "^1.0.152"
|
serde_derive = "^1.0.158"
|
||||||
sea-orm = { version = "^0.10.6", features = [], default-features = false }
|
sea-orm = { version = "^0.11.1", features = [], default-features = false }
|
||||||
actix-admin-macros = { version = "0.3.0", path = "actix_admin_macros" }
|
actix-admin-macros = { version = "0.4.0", path = "actix_admin_macros" }
|
||||||
derive_more = "0.99.17"
|
derive_more = "0.99.17"
|
||||||
regex = "1.7.1"
|
regex = "1.7.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
sea-orm = { version = "^0.10.6", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
sea-orm = { version = "^0.11.1", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
||||||
actix-rt = "2.7.0"
|
actix-rt = "2.8.0"
|
||||||
azure_auth = { path = "./examples/azure_auth/azure_auth" }
|
azure_auth = { path = "./examples/azure_auth/azure_auth" }
|
||||||
oauth2 = "4.3"
|
oauth2 = "4.3"
|
||||||
dotenv = "0.15"
|
dotenv = "0.15"
|
||||||
|
@ -3,7 +3,7 @@ name = "actix-admin-macros"
|
|||||||
description = "macros to be used with actix-admin crate"
|
description = "macros to be used with actix-admin crate"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/mgugger/actix-admin"
|
repository = "https://github.com/mgugger/actix-admin"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
exclude = [
|
exclude = [
|
||||||
"tests/*"
|
"tests/*"
|
||||||
@ -14,7 +14,7 @@ proc-macro = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bae = "0.1.7"
|
bae = "0.1.7"
|
||||||
quote = "1.0"
|
quote = "1.0.26"
|
||||||
syn = { version = "1.0", features = ["full", "extra-traits"] }
|
syn = { version = "1.0.109", features = ["full", "extra-traits"] }
|
||||||
proc-macro2 = { version = "1.0.36", default-features = false }
|
proc-macro2 = { version = "1.0.52", default-features = false }
|
||||||
regex = "1.7.1"
|
regex = "1.7.1"
|
21
basictest/basic/Cargo.toml
Normal file
21
basictest/basic/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "actix-admin-example"
|
||||||
|
description = "An admin interface for actix-web"
|
||||||
|
version = "0.4.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "actix-admin-example"
|
||||||
|
path = "main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-web = "^4.2.1"
|
||||||
|
actix-rt = "2.7.0"
|
||||||
|
actix-multipart = "^0.4.0"
|
||||||
|
sea-orm = { version = "^0.11.1", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
||||||
|
chrono = "0.4.23"
|
||||||
|
tera = "^1.17.1"
|
||||||
|
serde = "^1.0.152"
|
||||||
|
serde_derive = "^1.0.152"
|
||||||
|
actix-admin = { version = "0.4.0", path = "../../" }
|
||||||
|
regex = "1.7.1"
|
52
basictest/basic/entity/comment.rs
Normal file
52
basictest/basic/entity/comment.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use actix_admin::prelude::*;
|
||||||
|
use super::Post;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, DeriveActixAdmin, DeriveActixAdminModel, DeriveActixAdminViewModel)]
|
||||||
|
#[sea_orm(table_name = "comment")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
|
#[actix_admin(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
pub comment: String,
|
||||||
|
#[sea_orm(column_type = "Text")]
|
||||||
|
#[actix_admin(html_input_type = "email", list_regex_mask= "^([a-zA-Z]*)")]
|
||||||
|
pub user: String,
|
||||||
|
#[sea_orm(column_type = "DateTime")]
|
||||||
|
pub insert_date: DateTime,
|
||||||
|
pub is_visible: bool,
|
||||||
|
#[actix_admin(select_list="Post")]
|
||||||
|
pub post_id: Option<i32>,
|
||||||
|
pub my_decimal: Decimal
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::post::Entity",
|
||||||
|
from = "Column::PostId",
|
||||||
|
to = "super::post::Column::Id"
|
||||||
|
)]
|
||||||
|
Post,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::post::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Post.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
|
impl ActixAdminModelValidationTrait<ActiveModel> for Entity {
|
||||||
|
fn validate(model: &ActiveModel) -> HashMap<String, String> {
|
||||||
|
let mut errors = HashMap::new();
|
||||||
|
if model.my_decimal.clone().unwrap() < Decimal::from(100 as i16) {
|
||||||
|
errors.insert("my_decimal".to_string(), "Must be larger than 100".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
}
|
91
basictest/basic/entity/mod.rs
Normal file
91
basictest/basic/entity/mod.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// setup
|
||||||
|
use sea_orm::sea_query::{ForeignKeyCreateStatement, ColumnDef, TableCreateStatement};
|
||||||
|
use sea_orm::{Set, EntityTrait, error::*, sea_query, ConnectionTrait, DbConn, ExecResult};
|
||||||
|
pub mod comment;
|
||||||
|
pub mod post;
|
||||||
|
pub use comment::Entity as Comment;
|
||||||
|
pub use post::Entity as Post;
|
||||||
|
use chrono::{Local, Duration, DurationRound};
|
||||||
|
use sea_orm::prelude::Decimal;
|
||||||
|
|
||||||
|
// setup
|
||||||
|
async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result<ExecResult, DbErr> {
|
||||||
|
let builder = db.get_database_backend();
|
||||||
|
db.execute(builder.build(stmt)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_post_table(db: &DbConn) -> Result<ExecResult, DbErr> {
|
||||||
|
let stmt = sea_query::Table::create()
|
||||||
|
.table(post::Entity)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(post::Column::Id)
|
||||||
|
.integer()
|
||||||
|
.not_null()
|
||||||
|
.auto_increment()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(post::Column::Title).string().not_null())
|
||||||
|
.col(ColumnDef::new(post::Column::Text).string().not_null())
|
||||||
|
.col(ColumnDef::new(post::Column::TeaMandatory).string().not_null())
|
||||||
|
.col(ColumnDef::new(post::Column::TeaOptional).string())
|
||||||
|
.col(ColumnDef::new(post::Column::InsertDate).date().not_null())
|
||||||
|
.col(ColumnDef::new(post::Column::Attachment).string())
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
let _result = create_table(db, &stmt).await;
|
||||||
|
|
||||||
|
let stmt = sea_query::Table::create()
|
||||||
|
.table(comment::Entity)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(post::Column::Id)
|
||||||
|
.integer()
|
||||||
|
.not_null()
|
||||||
|
.auto_increment()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(comment::Column::Comment).string().not_null())
|
||||||
|
.col(ColumnDef::new(comment::Column::User).string().not_null())
|
||||||
|
.col(ColumnDef::new(comment::Column::InsertDate).date_time().not_null())
|
||||||
|
.col(ColumnDef::new(comment::Column::IsVisible).boolean().not_null())
|
||||||
|
.col(ColumnDef::new(comment::Column::MyDecimal).decimal().not_null())
|
||||||
|
.col(ColumnDef::new(comment::Column::PostId).integer())
|
||||||
|
.foreign_key(
|
||||||
|
ForeignKeyCreateStatement::new()
|
||||||
|
.name("fk-comment-post")
|
||||||
|
.from_tbl(Comment)
|
||||||
|
.from_col(comment::Column::PostId)
|
||||||
|
.to_tbl(Post)
|
||||||
|
.to_col(post::Column::Id),
|
||||||
|
)
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
let res = create_table(db, &stmt).await;
|
||||||
|
|
||||||
|
for i in 1..1000 {
|
||||||
|
let row = post::ActiveModel {
|
||||||
|
title: Set(format!("Test {}", i)),
|
||||||
|
text: Set("some content".to_string()),
|
||||||
|
tea_mandatory: Set(post::Tea::EverydayTea),
|
||||||
|
tea_optional: Set(None),
|
||||||
|
insert_date: Set(Local::now().date_naive()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let _res = Post::insert(row).exec(db).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 1..1000 {
|
||||||
|
let row = comment::ActiveModel {
|
||||||
|
comment: Set(format!("Test {}", i)),
|
||||||
|
user: Set("me@home.com".to_string()),
|
||||||
|
my_decimal: Set(Decimal::new(105, 0)),
|
||||||
|
insert_date: Set(Local::now().naive_utc().duration_round(Duration::minutes(1)).unwrap()),
|
||||||
|
is_visible: Set(i%2 == 0),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let _res = Comment::insert(row).exec(db).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
74
basictest/basic/entity/post.rs
Normal file
74
basictest/basic/entity/post.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use actix_admin::prelude::*;
|
||||||
|
use std::fmt;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, DeriveActixAdmin, DeriveActixAdminViewModel, DeriveActixAdminModel, DeriveActixAdminModelSelectList)]
|
||||||
|
#[sea_orm(table_name = "post")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
|
#[actix_admin(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
#[actix_admin(searchable, not_empty)]
|
||||||
|
pub title: String,
|
||||||
|
#[sea_orm(column_type = "Text")]
|
||||||
|
#[actix_admin(searchable, textarea, list_hide_column)]
|
||||||
|
pub text: String,
|
||||||
|
#[actix_admin(select_list="Tea")]
|
||||||
|
pub tea_mandatory: Tea,
|
||||||
|
#[actix_admin(select_list="Tea")]
|
||||||
|
pub tea_optional: Option<Tea>,
|
||||||
|
#[sea_orm(column_type = "Date")]
|
||||||
|
#[actix_admin(list_sort_position="1")]
|
||||||
|
pub insert_date: Date,
|
||||||
|
#[actix_admin(file_upload)]
|
||||||
|
pub attachment: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Model {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match &*self {
|
||||||
|
_ => write!(formatter, "{} {}", &self.title, ""/* &self.insert_date*/),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(has_many = "super::comment::Entity")]
|
||||||
|
Comment,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::comment::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Comment.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Deserialize, Serialize, DeriveActixAdminEnumSelectList)]
|
||||||
|
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")]
|
||||||
|
pub enum Tea {
|
||||||
|
#[sea_orm(string_value = "EverydayTea")]
|
||||||
|
EverydayTea,
|
||||||
|
#[sea_orm(string_value = "BreakfastTea")]
|
||||||
|
BreakfastTea,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Tea {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(input: &str) -> Result<Tea, Self::Err> {
|
||||||
|
match input {
|
||||||
|
"EverydayTea" => Ok(Tea::EverydayTea),
|
||||||
|
"BreakfastTea" => Ok(Tea::BreakfastTea),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActixAdminModelValidationTrait<ActiveModel> for Entity {}
|
87
basictest/basic/main.rs
Normal file
87
basictest/basic/main.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
use actix_admin::prelude::*;
|
||||||
|
use actix_web::{web, App, HttpServer, middleware};
|
||||||
|
use sea_orm::{ConnectOptions, DatabaseConnection};
|
||||||
|
use std::time::Duration;
|
||||||
|
mod entity;
|
||||||
|
use entity::{Post, Comment};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AppState {
|
||||||
|
pub db: DatabaseConnection,
|
||||||
|
pub actix_admin: ActixAdmin,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActixAdminAppDataTrait for AppState {
|
||||||
|
fn get_db(&self) -> &DatabaseConnection {
|
||||||
|
&self.db
|
||||||
|
}
|
||||||
|
fn get_actix_admin(&self) -> &ActixAdmin {
|
||||||
|
&self.actix_admin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_actix_admin_builder() -> ActixAdminBuilder {
|
||||||
|
let configuration = ActixAdminConfiguration {
|
||||||
|
enable_auth: false,
|
||||||
|
user_is_logged_in: None,
|
||||||
|
login_link: None,
|
||||||
|
logout_link: None,
|
||||||
|
file_upload_directory: "./file_uploads"
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut admin_builder = ActixAdminBuilder::new(configuration);
|
||||||
|
|
||||||
|
|
||||||
|
let post_view_model = ActixAdminViewModel::from(Post);
|
||||||
|
admin_builder.add_entity::<AppState, Post>(&post_view_model);
|
||||||
|
|
||||||
|
let some_category = "Groupings";
|
||||||
|
let comment_view_model = ActixAdminViewModel::from(Comment);
|
||||||
|
admin_builder.add_entity_to_category::<AppState, Comment>(&comment_view_model, some_category);
|
||||||
|
|
||||||
|
admin_builder
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_db_options() -> ConnectOptions {
|
||||||
|
let db_url = "sqlite::memory:".to_string();
|
||||||
|
let mut opt = ConnectOptions::new(db_url);
|
||||||
|
opt.max_connections(100)
|
||||||
|
.min_connections(5)
|
||||||
|
.connect_timeout(Duration::from_secs(8))
|
||||||
|
.idle_timeout(Duration::from_secs(8))
|
||||||
|
.sqlx_logging(true);
|
||||||
|
opt
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::main]
|
||||||
|
async fn main() {
|
||||||
|
let opt = get_db_options();
|
||||||
|
let conn = sea_orm::Database::connect(opt).await.unwrap();
|
||||||
|
let _ = entity::create_post_table(&conn).await;
|
||||||
|
|
||||||
|
println!("The admin interface is available at http://localhost:5000/admin/");
|
||||||
|
|
||||||
|
HttpServer::new(move || {
|
||||||
|
|
||||||
|
let actix_admin_builder = create_actix_admin_builder();
|
||||||
|
|
||||||
|
let app_state = AppState {
|
||||||
|
db: conn.clone(),
|
||||||
|
actix_admin: actix_admin_builder.get_actix_admin(),
|
||||||
|
};
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
.app_data(web::Data::new(app_state))
|
||||||
|
.service(
|
||||||
|
actix_admin_builder.get_scope::<AppState>()
|
||||||
|
)
|
||||||
|
.wrap(middleware::Logger::default())
|
||||||
|
})
|
||||||
|
.bind("127.0.0.1:5000")
|
||||||
|
.expect("Can not bind to port 5000")
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
@ -16,4 +16,30 @@ The actix-admin crate aims at creating a web admin interface similar to other ad
|
|||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
Check the [examples](https://github.com/mgugger/actix-admin/tree/main/examples) and run ```cargo run --example basic``` from the root folder for a basic in-memory sqlite version. The admin interface is accessible under ```localhost:5000/admin/```.
|
Check the [examples](https://github.com/mgugger/actix-admin/tree/main/examples) and run ```cargo run --example basic``` from the root folder for a basic in-memory sqlite version. The admin interface is accessible under ```localhost:5000/admin/```.
|
||||||
|
|
||||||
|
## Minimal Cargo.toml
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "actix-admin-example"
|
||||||
|
description = "An admin interface for actix-web"
|
||||||
|
version = "0.4.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "actix-admin-example"
|
||||||
|
path = "main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-web = "^4.2.1"
|
||||||
|
actix-rt = "2.7.0"
|
||||||
|
actix-multipart = "^0.4.0"
|
||||||
|
sea-orm = { version = "^0.11.1", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
||||||
|
chrono = "0.4.23"
|
||||||
|
tera = "^1.17.1"
|
||||||
|
serde = "^1.0.152"
|
||||||
|
serde_derive = "^1.0.152"
|
||||||
|
actix-admin = { version = "0.4.0", path = "../../" }
|
||||||
|
regex = "1.7.1"
|
||||||
|
```
|
140
src/builder.rs
140
src/builder.rs
@ -1,10 +1,13 @@
|
|||||||
use crate::{prelude::*, ActixAdminMenuElement, routes::delete_file};
|
use crate::{prelude::*, ActixAdminMenuElement, routes::delete_file};
|
||||||
use actix_web::{web, Route};
|
use actix_web::{web, Route };
|
||||||
|
use tera::Tera;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use crate::routes::{
|
use crate::routes::{
|
||||||
create_get, create_post, delete, delete_many, edit_get, edit_post, index, list, not_found, show, download
|
create_get, create_post, delete, delete_many, edit_get, edit_post, index, list, not_found, show, download
|
||||||
};
|
};
|
||||||
|
use std::hash::BuildHasher;
|
||||||
|
use tera::{to_value, try_get_value, Result};
|
||||||
|
|
||||||
/// Represents a builder entity which helps generating the ActixAdmin configuration
|
/// Represents a builder entity which helps generating the ActixAdmin configuration
|
||||||
pub struct ActixAdminBuilder {
|
pub struct ActixAdminBuilder {
|
||||||
@ -62,6 +65,133 @@ pub trait ActixAdminBuilderTrait {
|
|||||||
fn get_actix_admin(&self) -> ActixAdmin;
|
fn get_actix_admin(&self) -> ActixAdmin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_html_input_class<S: BuildHasher>(
|
||||||
|
value: &tera::Value,
|
||||||
|
_: &HashMap<String, tera::Value, S>,
|
||||||
|
) -> Result<tera::Value> {
|
||||||
|
let field = try_get_value!(
|
||||||
|
"get_html_input_class",
|
||||||
|
"value",
|
||||||
|
ActixAdminViewModelField,
|
||||||
|
value
|
||||||
|
);
|
||||||
|
let html_input_type = match field.field_type {
|
||||||
|
ActixAdminViewModelFieldType::TextArea => "textarea",
|
||||||
|
ActixAdminViewModelFieldType::Checkbox => "checkbox",
|
||||||
|
_ => "input",
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(to_value(html_input_type).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_icon<S: BuildHasher>(
|
||||||
|
value: &tera::Value,
|
||||||
|
_: &HashMap<String, tera::Value, S>,
|
||||||
|
) -> Result<tera::Value> {
|
||||||
|
let field = try_get_value!("get_icon", "value", String, value);
|
||||||
|
let font_awesome_icon = match field.as_str() {
|
||||||
|
"true" => "<i class=\"fa-solid fa-check\"></i>",
|
||||||
|
"false" => "<i class=\"fa-solid fa-xmark\"></i>",
|
||||||
|
_ => panic!("not implemented icon"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(to_value(font_awesome_icon).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_regex_val<S: BuildHasher>(
|
||||||
|
value: &tera::Value,
|
||||||
|
args: &HashMap<String, tera::Value, S>,
|
||||||
|
) -> Result<tera::Value> {
|
||||||
|
let field = try_get_value!("get_regex_val", "value", ActixAdminViewModelField, value);
|
||||||
|
|
||||||
|
let s = args.get("values");
|
||||||
|
let field_val = s.unwrap().get(&field.field_name);
|
||||||
|
|
||||||
|
println!("field {} regex {:?}", field.field_name, field.list_regex_mask);
|
||||||
|
match (field_val, field.list_regex_mask) {
|
||||||
|
(Some(val), Some(r)) => {
|
||||||
|
let val_str = val.to_string();
|
||||||
|
let is_match = r.is_match(&val_str);
|
||||||
|
println!("is match: {}, regex {}", is_match, r.to_string());
|
||||||
|
let result_str = r.replace_all(&val_str, "*");
|
||||||
|
return Ok(to_value(result_str).unwrap());
|
||||||
|
},
|
||||||
|
(Some(val), None) => { return Ok(to_value(val).unwrap()); },
|
||||||
|
(_, _) => panic!("key {} not found in model values", &field.field_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_html_input_type<S: BuildHasher>(
|
||||||
|
value: &tera::Value,
|
||||||
|
_: &HashMap<String, tera::Value, S>,
|
||||||
|
) -> Result<tera::Value> {
|
||||||
|
let field = try_get_value!(
|
||||||
|
"get_html_input_type",
|
||||||
|
"value",
|
||||||
|
ActixAdminViewModelField,
|
||||||
|
value
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: convert to option
|
||||||
|
if field.html_input_type != "" {
|
||||||
|
return Ok(to_value(field.html_input_type).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let html_input_type = match field.field_type {
|
||||||
|
ActixAdminViewModelFieldType::Text => "text",
|
||||||
|
ActixAdminViewModelFieldType::DateTime => "datetime-local",
|
||||||
|
ActixAdminViewModelFieldType::Date => "date",
|
||||||
|
ActixAdminViewModelFieldType::Checkbox => "checkbox",
|
||||||
|
ActixAdminViewModelFieldType::FileUpload => "file",
|
||||||
|
_ => "text",
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(to_value(html_input_type).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_tera() -> Tera {
|
||||||
|
let mut tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "*")).unwrap();
|
||||||
|
tera.register_filter("get_html_input_type", get_html_input_type);
|
||||||
|
tera.register_filter("get_html_input_class", get_html_input_class);
|
||||||
|
tera.register_filter("get_icon", get_icon);
|
||||||
|
tera.register_filter("get_regex_val", get_regex_val);
|
||||||
|
|
||||||
|
let list_html = include_str!("templates/list.html");
|
||||||
|
let create_or_edit_html = include_str!("templates/create_or_edit.html");
|
||||||
|
let base_html = include_str!("templates/base.html");
|
||||||
|
let head_html = include_str!("templates/head.html");
|
||||||
|
let index_html = include_str!("templates/index.html");
|
||||||
|
let loader_html = include_str!("templates/loader.html");
|
||||||
|
let navbar_html = include_str!("templates/navbar.html");
|
||||||
|
let not_found_html = include_str!("templates/not_found.html");
|
||||||
|
let show_html = include_str!("templates/show.html");
|
||||||
|
let unauthorized_html = include_str!("templates/unauthorized.html");
|
||||||
|
|
||||||
|
// form elements
|
||||||
|
let checkbox_html = include_str!("templates/form_elements/checkbox.html");
|
||||||
|
let input_html = include_str!("templates/form_elements/input.html");
|
||||||
|
let selectlist_html = include_str!("templates/form_elements/selectlist.html");
|
||||||
|
|
||||||
|
let _res = tera.add_raw_templates(vec![
|
||||||
|
("base.html", base_html),
|
||||||
|
("list.html", list_html),
|
||||||
|
("create_or_edit.html", create_or_edit_html),
|
||||||
|
("head.html", head_html),
|
||||||
|
("index.html", index_html),
|
||||||
|
("loader.html", loader_html),
|
||||||
|
("navbar.html", navbar_html),
|
||||||
|
("not_found.html", not_found_html),
|
||||||
|
("show.html",show_html),
|
||||||
|
("unauthorized.html", unauthorized_html),
|
||||||
|
// form elements
|
||||||
|
("form_elements/checkbox.html", checkbox_html),
|
||||||
|
("form_elements/input.html", input_html),
|
||||||
|
("form_elements/selectlist.html", selectlist_html),
|
||||||
|
]);
|
||||||
|
|
||||||
|
tera
|
||||||
|
}
|
||||||
|
|
||||||
impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
||||||
fn new(configuration: ActixAdminConfiguration) -> Self {
|
fn new(configuration: ActixAdminConfiguration) -> Self {
|
||||||
ActixAdminBuilder {
|
ActixAdminBuilder {
|
||||||
@ -69,6 +199,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
|||||||
entity_names: HashMap::new(),
|
entity_names: HashMap::new(),
|
||||||
view_models: HashMap::new(),
|
view_models: HashMap::new(),
|
||||||
configuration: configuration,
|
configuration: configuration,
|
||||||
|
tera: get_tera()
|
||||||
},
|
},
|
||||||
custom_routes: Vec::new(),
|
custom_routes: Vec::new(),
|
||||||
scopes: HashMap::new(),
|
scopes: HashMap::new(),
|
||||||
@ -104,7 +235,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
|||||||
.route("/show/{id}", web::get().to(show::<T, E>))
|
.route("/show/{id}", web::get().to(show::<T, E>))
|
||||||
.route("/file/{id}/{column_name}", web::get().to(download::<T, E>))
|
.route("/file/{id}/{column_name}", web::get().to(download::<T, E>))
|
||||||
.route("/file/{id}/{column_name}", web::delete().to(delete_file::<T, E>))
|
.route("/file/{id}/{column_name}", web::delete().to(delete_file::<T, E>))
|
||||||
.default_service(web::to(not_found))
|
.default_service(web::to(not_found::<T>))
|
||||||
);
|
);
|
||||||
|
|
||||||
fs::create_dir_all(format!("{}/{}", &self.actix_admin.configuration.file_upload_directory, E::get_entity_name())).unwrap();
|
fs::create_dir_all(format!("{}/{}", &self.actix_admin.configuration.file_upload_directory, E::get_entity_name())).unwrap();
|
||||||
@ -232,8 +363,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
|||||||
};
|
};
|
||||||
let mut admin_scope = web::scope("/admin")
|
let mut admin_scope = web::scope("/admin")
|
||||||
.route("/", index_handler)
|
.route("/", index_handler)
|
||||||
.service(actix_files::Files::new("/static", "./static").show_files_listing())
|
.default_service(web::to(not_found::<T>));
|
||||||
.default_service(web::to(not_found));
|
|
||||||
|
|
||||||
for (_entity, scope) in self.scopes {
|
for (_entity, scope) in self.scopes {
|
||||||
admin_scope = admin_scope.service(scope);
|
admin_scope = admin_scope.service(scope);
|
||||||
@ -249,4 +379,4 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
|||||||
fn get_actix_admin(&self) -> ActixAdmin {
|
fn get_actix_admin(&self) -> ActixAdmin {
|
||||||
self.actix_admin.clone()
|
self.actix_admin.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
136
src/lib.rs
136
src/lib.rs
@ -12,12 +12,10 @@ use actix_web::{
|
|||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use derive_more::{Display, Error};
|
use derive_more::{Display, Error};
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use sea_orm::DatabaseConnection;
|
use sea_orm::DatabaseConnection;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use tera::Tera;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::hash::BuildHasher;
|
|
||||||
use tera::{to_value, try_get_value, Result, Tera};
|
|
||||||
|
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
@ -32,7 +30,6 @@ pub mod prelude {
|
|||||||
ActixAdminViewModel, ActixAdminViewModelField, ActixAdminViewModelFieldType,
|
ActixAdminViewModel, ActixAdminViewModelField, ActixAdminViewModelFieldType,
|
||||||
ActixAdminViewModelSerializable, ActixAdminViewModelTrait,
|
ActixAdminViewModelSerializable, ActixAdminViewModelTrait,
|
||||||
};
|
};
|
||||||
pub use crate::TERA;
|
|
||||||
pub use crate::{hashmap, ActixAdminSelectListTrait};
|
pub use crate::{hashmap, ActixAdminSelectListTrait};
|
||||||
pub use crate::{ActixAdmin, ActixAdminAppDataTrait, ActixAdminConfiguration, ActixAdminError};
|
pub use crate::{ActixAdmin, ActixAdminAppDataTrait, ActixAdminConfiguration, ActixAdminError};
|
||||||
pub use actix_admin_macros::{
|
pub use actix_admin_macros::{
|
||||||
@ -56,136 +53,6 @@ macro_rules! hashmap {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// globals
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref TERA: Tera = {
|
|
||||||
let mut tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap();
|
|
||||||
tera.register_filter("get_html_input_type", get_html_input_type);
|
|
||||||
tera.register_filter("get_html_input_class", get_html_input_class);
|
|
||||||
tera.register_filter("get_icon", get_icon);
|
|
||||||
tera.register_filter("get_regex_val", get_regex_val);
|
|
||||||
|
|
||||||
let list_html = include_str!("templates/list.html");
|
|
||||||
let create_or_edit_html = include_str!("templates/create_or_edit.html");
|
|
||||||
let base_html = include_str!("templates/base.html");
|
|
||||||
let head_html = include_str!("templates/head.html");
|
|
||||||
let index_html = include_str!("templates/index.html");
|
|
||||||
let loader_html = include_str!("templates/loader.html");
|
|
||||||
let navbar_html = include_str!("templates/navbar.html");
|
|
||||||
let not_found_html = include_str!("templates/not_found.html");
|
|
||||||
let show_html = include_str!("templates/show.html");
|
|
||||||
let unauthorized_html = include_str!("templates/unauthorized.html");
|
|
||||||
|
|
||||||
// form elements
|
|
||||||
let checkbox_html = include_str!("templates/form_elements/checkbox.html");
|
|
||||||
let input_html = include_str!("templates/form_elements/input.html");
|
|
||||||
let selectlist_html = include_str!("templates/form_elements/selectlist.html");
|
|
||||||
|
|
||||||
let _res = tera.add_raw_templates(vec![
|
|
||||||
("base.html", base_html),
|
|
||||||
("list.html", list_html),
|
|
||||||
("create_or_edit.html", create_or_edit_html),
|
|
||||||
("head.html", head_html),
|
|
||||||
("index.html", index_html),
|
|
||||||
("loader.html", loader_html),
|
|
||||||
("navbar.html", navbar_html),
|
|
||||||
("not_found.html", not_found_html),
|
|
||||||
("show.html",show_html),
|
|
||||||
("unauthorized.html", unauthorized_html),
|
|
||||||
// form elements
|
|
||||||
("form_elements/checkbox.html", checkbox_html),
|
|
||||||
("form_elements/input.html", input_html),
|
|
||||||
("form_elements/selectlist.html", selectlist_html)
|
|
||||||
]);
|
|
||||||
|
|
||||||
tera
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_html_input_class<S: BuildHasher>(
|
|
||||||
value: &tera::Value,
|
|
||||||
_: &HashMap<String, tera::Value, S>,
|
|
||||||
) -> Result<tera::Value> {
|
|
||||||
let field = try_get_value!(
|
|
||||||
"get_html_input_class",
|
|
||||||
"value",
|
|
||||||
ActixAdminViewModelField,
|
|
||||||
value
|
|
||||||
);
|
|
||||||
let html_input_type = match field.field_type {
|
|
||||||
ActixAdminViewModelFieldType::TextArea => "textarea",
|
|
||||||
ActixAdminViewModelFieldType::Checkbox => "checkbox",
|
|
||||||
_ => "input",
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(to_value(html_input_type).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_icon<S: BuildHasher>(
|
|
||||||
value: &tera::Value,
|
|
||||||
_: &HashMap<String, tera::Value, S>,
|
|
||||||
) -> Result<tera::Value> {
|
|
||||||
let field = try_get_value!("get_icon", "value", String, value);
|
|
||||||
let font_awesome_icon = match field.as_str() {
|
|
||||||
"true" => "<i class=\"fa-solid fa-check\"></i>",
|
|
||||||
"false" => "<i class=\"fa-solid fa-xmark\"></i>",
|
|
||||||
_ => panic!("not implemented icon"),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(to_value(font_awesome_icon).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_regex_val<S: BuildHasher>(
|
|
||||||
value: &tera::Value,
|
|
||||||
args: &HashMap<String, tera::Value, S>,
|
|
||||||
) -> Result<tera::Value> {
|
|
||||||
let field = try_get_value!("get_regex_val", "value", ActixAdminViewModelField, value);
|
|
||||||
|
|
||||||
let s = args.get("values");
|
|
||||||
let field_val = s.unwrap().get(&field.field_name);
|
|
||||||
|
|
||||||
println!("field {} regex {:?}", field.field_name, field.list_regex_mask);
|
|
||||||
match (field_val, field.list_regex_mask) {
|
|
||||||
(Some(val), Some(r)) => {
|
|
||||||
let val_str = val.to_string();
|
|
||||||
let is_match = r.is_match(&val_str);
|
|
||||||
println!("is match: {}, regex {}", is_match, r.to_string());
|
|
||||||
let result_str = r.replace_all(&val_str, "*");
|
|
||||||
return Ok(to_value(result_str).unwrap());
|
|
||||||
},
|
|
||||||
(Some(val), None) => { return Ok(to_value(val).unwrap()); },
|
|
||||||
(_, _) => panic!("key {} not found in model values", &field.field_name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_html_input_type<S: BuildHasher>(
|
|
||||||
value: &tera::Value,
|
|
||||||
_: &HashMap<String, tera::Value, S>,
|
|
||||||
) -> Result<tera::Value> {
|
|
||||||
let field = try_get_value!(
|
|
||||||
"get_html_input_type",
|
|
||||||
"value",
|
|
||||||
ActixAdminViewModelField,
|
|
||||||
value
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: convert to option
|
|
||||||
if field.html_input_type != "" {
|
|
||||||
return Ok(to_value(field.html_input_type).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
let html_input_type = match field.field_type {
|
|
||||||
ActixAdminViewModelFieldType::Text => "text",
|
|
||||||
ActixAdminViewModelFieldType::DateTime => "datetime-local",
|
|
||||||
ActixAdminViewModelFieldType::Date => "date",
|
|
||||||
ActixAdminViewModelFieldType::Checkbox => "checkbox",
|
|
||||||
ActixAdminViewModelFieldType::FileUpload => "file",
|
|
||||||
_ => "text",
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(to_value(html_input_type).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppDataTrait
|
// AppDataTrait
|
||||||
pub trait ActixAdminAppDataTrait {
|
pub trait ActixAdminAppDataTrait {
|
||||||
fn get_db(&self) -> &DatabaseConnection;
|
fn get_db(&self) -> &DatabaseConnection;
|
||||||
@ -214,6 +81,7 @@ pub struct ActixAdmin {
|
|||||||
pub entity_names: HashMap<String, Vec<ActixAdminMenuElement>>,
|
pub entity_names: HashMap<String, Vec<ActixAdminMenuElement>>,
|
||||||
pub view_models: HashMap<String, ActixAdminViewModel>,
|
pub view_models: HashMap<String, ActixAdminViewModel>,
|
||||||
pub configuration: ActixAdminConfiguration,
|
pub configuration: ActixAdminConfiguration,
|
||||||
|
pub tera: Tera
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Serialize)]
|
#[derive(PartialEq, Eq, Clone, Serialize)]
|
||||||
|
@ -5,7 +5,6 @@ use crate::ActixAdminError;
|
|||||||
use crate::ActixAdminNotification;
|
use crate::ActixAdminNotification;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::TERA;
|
|
||||||
use super::DEFAULT_ENTITIES_PER_PAGE;
|
use super::DEFAULT_ENTITIES_PER_PAGE;
|
||||||
use super::Params;
|
use super::Params;
|
||||||
use super::{ add_auth_context, user_can_access_page, render_unauthorized};
|
use super::{ add_auth_context, user_can_access_page, render_unauthorized};
|
||||||
@ -48,7 +47,7 @@ async fn create_or_edit_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTra
|
|||||||
let view_model = actix_admin.view_models.get(&entity_name).unwrap();
|
let view_model = actix_admin.view_models.get(&entity_name).unwrap();
|
||||||
|
|
||||||
if !user_can_access_page(&session, actix_admin, view_model) {
|
if !user_can_access_page(&session, actix_admin, view_model) {
|
||||||
return render_unauthorized(&ctx);
|
return render_unauthorized(&ctx, &actix_admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
let model;
|
let model;
|
||||||
@ -93,7 +92,7 @@ async fn create_or_edit_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTra
|
|||||||
ctx.insert("sort_order", &sort_order);
|
ctx.insert("sort_order", &sort_order);
|
||||||
ctx.insert("page", &page);
|
ctx.insert("page", &page);
|
||||||
|
|
||||||
let body = TERA
|
let body = actix_admin.tera
|
||||||
.render("create_or_edit.html", &ctx)
|
.render("create_or_edit.html", &ctx)
|
||||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||||
Ok(http_response_code.content_type("text/html").body(body))
|
Ok(http_response_code.content_type("text/html").body(body))
|
||||||
|
@ -3,7 +3,6 @@ use super::{Params, DEFAULT_ENTITIES_PER_PAGE};
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::ActixAdminError;
|
use crate::ActixAdminError;
|
||||||
use crate::ActixAdminNotification;
|
use crate::ActixAdminNotification;
|
||||||
use crate::TERA;
|
|
||||||
use actix_multipart::Multipart;
|
use actix_multipart::Multipart;
|
||||||
use actix_multipart::MultipartError;
|
use actix_multipart::MultipartError;
|
||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
@ -75,7 +74,7 @@ pub async fn create_or_edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewMod
|
|||||||
if !user_can_access_page(&session, actix_admin, view_model) {
|
if !user_can_access_page(&session, actix_admin, view_model) {
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
ctx.insert("render_partial", &true);
|
ctx.insert("render_partial", &true);
|
||||||
return render_unauthorized(&ctx);
|
return render_unauthorized(&ctx, &actix_admin);
|
||||||
}
|
}
|
||||||
let db = &data.get_db();
|
let db = &data.get_db();
|
||||||
|
|
||||||
@ -187,7 +186,7 @@ async fn render_form<E: ActixAdminViewModelTrait>(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
ctx.insert("notifications", ¬ifications);
|
ctx.insert("notifications", ¬ifications);
|
||||||
let body = TERA
|
let body = actix_admin.tera
|
||||||
.render("create_or_edit.html", &ctx)
|
.render("create_or_edit.html", &ctx)
|
||||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||||
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
||||||
|
@ -20,7 +20,7 @@ pub async fn delete<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
if !user_can_access_page(&session, actix_admin, view_model) {
|
if !user_can_access_page(&session, actix_admin, view_model) {
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
ctx.insert("render_partial", &true);
|
ctx.insert("render_partial", &true);
|
||||||
return render_unauthorized(&ctx);
|
return render_unauthorized(&ctx, &actix_admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
let db = &data.get_db();
|
let db = &data.get_db();
|
||||||
@ -68,7 +68,7 @@ pub async fn delete_many<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>
|
|||||||
if !user_can_access_page(&session, actix_admin, view_model) {
|
if !user_can_access_page(&session, actix_admin, view_model) {
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
ctx.insert("render_partial", &true);
|
ctx.insert("render_partial", &true);
|
||||||
return render_unauthorized(&ctx);
|
return render_unauthorized(&ctx, &actix_admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
let db = &data.get_db();
|
let db = &data.get_db();
|
||||||
|
@ -13,7 +13,7 @@ pub async fn download<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(re
|
|||||||
let entity_name = E::get_entity_name();
|
let entity_name = E::get_entity_name();
|
||||||
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
||||||
if !user_can_access_page(&session, actix_admin, view_model) {
|
if !user_can_access_page(&session, actix_admin, view_model) {
|
||||||
return render_unauthorized(&ctx);
|
return render_unauthorized(&ctx, &actix_admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (id, column_name) = params.into_inner();
|
let (id, column_name) = params.into_inner();
|
||||||
@ -49,7 +49,7 @@ pub async fn delete_file<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>
|
|||||||
let entity_name = E::get_entity_name();
|
let entity_name = E::get_entity_name();
|
||||||
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
||||||
if !user_can_access_page(&session, actix_admin, view_model) {
|
if !user_can_access_page(&session, actix_admin, view_model) {
|
||||||
return render_unauthorized(&ctx);
|
return render_unauthorized(&ctx, &actix_admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (id, column_name) = params.into_inner();
|
let (id, column_name) = params.into_inner();
|
||||||
@ -77,7 +77,7 @@ pub async fn delete_file<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>
|
|||||||
ctx.insert("base_path", &E::get_base_path(&entity_name));
|
ctx.insert("base_path", &E::get_base_path(&entity_name));
|
||||||
ctx.insert("model", &model);
|
ctx.insert("model", &model);
|
||||||
|
|
||||||
let body = TERA
|
let body = actix_admin.tera
|
||||||
.render("form_elements/input.html", &ctx)
|
.render("form_elements/input.html", &ctx)
|
||||||
.map_err(|err| error::ErrorInternalServerError(err))? ;
|
.map_err(|err| error::ErrorInternalServerError(err))? ;
|
||||||
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
||||||
|
@ -2,7 +2,6 @@ use actix_session::{Session};
|
|||||||
use tera::{Context};
|
use tera::{Context};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::TERA;
|
|
||||||
use actix_web::{error, Error, HttpResponse};
|
use actix_web::{error, Error, HttpResponse};
|
||||||
|
|
||||||
|
|
||||||
@ -29,8 +28,8 @@ pub fn user_can_access_page(session: &Session, actix_admin: &ActixAdmin, view_mo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_unauthorized(ctx: &Context) -> Result<HttpResponse, Error> {
|
pub fn render_unauthorized(ctx: &Context, actix_admin: &ActixAdmin) -> Result<HttpResponse, Error> {
|
||||||
let body = TERA
|
let body = actix_admin.tera
|
||||||
.render("unauthorized.html", &ctx)
|
.render("unauthorized.html", &ctx)
|
||||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||||
Ok(HttpResponse::Unauthorized().content_type("text/html").body(body))
|
Ok(HttpResponse::Unauthorized().content_type("text/html").body(body))
|
||||||
|
@ -4,8 +4,6 @@ use tera::{Context};
|
|||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::TERA;
|
|
||||||
|
|
||||||
use super::{ add_auth_context };
|
use super::{ add_auth_context };
|
||||||
|
|
||||||
pub fn get_admin_ctx<T: ActixAdminAppDataTrait>(session: Session, data: &web::Data<T>) -> Context {
|
pub fn get_admin_ctx<T: ActixAdminAppDataTrait>(session: Session, data: &web::Data<T>) -> Context {
|
||||||
@ -29,14 +27,14 @@ pub async fn index<T: ActixAdminAppDataTrait>(session: Session, data: web::Data<
|
|||||||
|
|
||||||
add_auth_context(&session, actix_admin, &mut ctx);
|
add_auth_context(&session, actix_admin, &mut ctx);
|
||||||
|
|
||||||
let body = TERA
|
let body = actix_admin.tera
|
||||||
.render("index.html", &ctx)
|
.render("index.html", &ctx)
|
||||||
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
|
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
|
||||||
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn not_found() -> Result<HttpResponse, Error> {
|
pub async fn not_found<T: ActixAdminAppDataTrait>(data: web::Data<T>) -> Result<HttpResponse, Error> {
|
||||||
let body = TERA
|
let body = data.get_actix_admin().tera
|
||||||
.render("not_found.html", &Context::new())
|
.render("not_found.html", &Context::new())
|
||||||
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
|
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
|
||||||
Ok(HttpResponse::NotFound().content_type("text/html").body(body))
|
Ok(HttpResponse::NotFound().content_type("text/html").body(body))
|
||||||
|
@ -13,7 +13,6 @@ use crate::ActixAdminModel;
|
|||||||
use crate::ActixAdminNotification;
|
use crate::ActixAdminNotification;
|
||||||
use crate::ActixAdminViewModel;
|
use crate::ActixAdminViewModel;
|
||||||
use crate::ActixAdminViewModelTrait;
|
use crate::ActixAdminViewModelTrait;
|
||||||
use crate::TERA;
|
|
||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
@ -63,7 +62,7 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
ctx.insert("entity_names", &actix_admin.entity_names);
|
ctx.insert("entity_names", &actix_admin.entity_names);
|
||||||
|
|
||||||
if !user_can_access_page(&session, actix_admin, view_model) {
|
if !user_can_access_page(&session, actix_admin, view_model) {
|
||||||
return render_unauthorized(&ctx);
|
return render_unauthorized(&ctx, actix_admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
let params = web::Query::<Params>::from_query(req.query_string()).unwrap();
|
let params = web::Query::<Params>::from_query(req.query_string()).unwrap();
|
||||||
@ -139,7 +138,7 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
ctx.insert("sort_by", &sort_by);
|
ctx.insert("sort_by", &sort_by);
|
||||||
ctx.insert("sort_order", &sort_order);
|
ctx.insert("sort_order", &sort_order);
|
||||||
|
|
||||||
let body = TERA
|
let body = actix_admin.tera
|
||||||
.render("list.html", &ctx)
|
.render("list.html", &ctx)
|
||||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||||
Ok(http_response_code.content_type("text/html").body(body))
|
Ok(http_response_code.content_type("text/html").body(body))
|
||||||
|
@ -6,7 +6,6 @@ use tera::{Context};
|
|||||||
use crate::ActixAdminNotification;
|
use crate::ActixAdminNotification;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::TERA;
|
|
||||||
use super::{Params, DEFAULT_ENTITIES_PER_PAGE};
|
use super::{Params, DEFAULT_ENTITIES_PER_PAGE};
|
||||||
use super::{ add_auth_context, user_can_access_page, render_unauthorized};
|
use super::{ add_auth_context, user_can_access_page, render_unauthorized};
|
||||||
|
|
||||||
@ -18,7 +17,7 @@ pub async fn show<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(sessio
|
|||||||
let entity_name = E::get_entity_name();
|
let entity_name = E::get_entity_name();
|
||||||
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
||||||
if !user_can_access_page(&session, actix_admin, view_model) {
|
if !user_can_access_page(&session, actix_admin, view_model) {
|
||||||
return render_unauthorized(&ctx);
|
return render_unauthorized(&ctx, &actix_admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut errors: Vec<crate::ActixAdminError> = Vec::new();
|
let mut errors: Vec<crate::ActixAdminError> = Vec::new();
|
||||||
@ -67,7 +66,7 @@ pub async fn show<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(sessio
|
|||||||
|
|
||||||
add_auth_context(&session, actix_admin, &mut ctx);
|
add_auth_context(&session, actix_admin, &mut ctx);
|
||||||
|
|
||||||
let body = TERA
|
let body = actix_admin.tera
|
||||||
.render("show.html", &ctx)
|
.render("show.html", &ctx)
|
||||||
.map_err(|err| error::ErrorInternalServerError(format!("{:?}", err)))?;
|
.map_err(|err| error::ErrorInternalServerError(format!("{:?}", err)))?;
|
||||||
Ok(http_response_code.content_type("text/html").body(body))
|
Ok(http_response_code.content_type("text/html").body(body))
|
||||||
|
@ -6,5 +6,89 @@
|
|||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css">
|
||||||
<script src="https://unpkg.com/htmx.org@1.8.4"></script>
|
<script src="https://unpkg.com/htmx.org@1.8.4"></script>
|
||||||
|
|
||||||
<link rel="stylesheet" href="/admin/static/css/default.css">
|
<script>
|
||||||
<script src="/admin/static/js/default.js"></script>
|
document.onkeydown = function (e) {
|
||||||
|
switch (e.which) {
|
||||||
|
case 37: // left
|
||||||
|
let left_el = document.getElementsByClassName('left-arrow-click').item(0);
|
||||||
|
if (left_el) { left_el.click(); };
|
||||||
|
break;
|
||||||
|
|
||||||
|
//case 38: // up
|
||||||
|
// break;
|
||||||
|
|
||||||
|
case 39: // right
|
||||||
|
let right_el = document.getElementsByClassName('right-arrow-click').item(0);
|
||||||
|
if (right_el) { right_el.click(); };
|
||||||
|
break;
|
||||||
|
|
||||||
|
//case 40: // down
|
||||||
|
// break;
|
||||||
|
|
||||||
|
default: return; // exit this handler for other keys
|
||||||
|
}
|
||||||
|
e.preventDefault(); // prevent the default action (scroll / move caret)
|
||||||
|
};
|
||||||
|
|
||||||
|
function checkAll(bx) {
|
||||||
|
var cbs = document.getElementsByTagName('input');
|
||||||
|
for (var i = 0; i < cbs.length; i++) {
|
||||||
|
if (cbs[i].type == 'checkbox') {
|
||||||
|
cbs[i].checked = bx.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sort_by(column) {
|
||||||
|
current_sort_order = document.getElementsByName("sort_order")[0].value;
|
||||||
|
if (current_sort_order == "Asc") {
|
||||||
|
document.getElementsByName("sort_order").forEach((e) => e.value = "Desc");
|
||||||
|
} else {
|
||||||
|
document.getElementsByName("sort_order").forEach((e) => e.value = "Asc");
|
||||||
|
}
|
||||||
|
document.getElementsByName("sort_by").forEach((e) => e.value = column);
|
||||||
|
document.getElementById('table_form').requestSubmit();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Get all "navbar-burger" elements
|
||||||
|
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
||||||
|
|
||||||
|
// Add a click event on each of them
|
||||||
|
$navbarBurgers.forEach(el => {
|
||||||
|
el.addEventListener('click', () => {
|
||||||
|
|
||||||
|
// Get the target from the "data-target" attribute
|
||||||
|
const target = el.dataset.target;
|
||||||
|
const $target = document.getElementById(target);
|
||||||
|
|
||||||
|
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
||||||
|
el.classList.toggle('is-active');
|
||||||
|
$target.classList.toggle('is-active');
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
htmx.on("htmx:responseError", function () {
|
||||||
|
document.getElementById("notifications").insertAdjacentHTML(
|
||||||
|
"afterend",
|
||||||
|
"<div class=\"notification mb-4 is-light is-danger\"><button class=\"delete\" onclick=\"this.parentElement.remove()\"></button>An Error occurred</div>");
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.loader-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 6;
|
||||||
|
pointer-events: none
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
.loader-wrapper {
|
|
||||||
position: absolute;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
background: rgba(255, 255, 255, 0.3);
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 6px;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 6;
|
|
||||||
pointer-events: none
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
document.onkeydown = function (e) {
|
|
||||||
switch (e.which) {
|
|
||||||
case 37: // left
|
|
||||||
let left_el = document.getElementsByClassName('left-arrow-click').item(0);
|
|
||||||
if (left_el) { left_el.click(); };
|
|
||||||
break;
|
|
||||||
|
|
||||||
//case 38: // up
|
|
||||||
// break;
|
|
||||||
|
|
||||||
case 39: // right
|
|
||||||
let right_el = document.getElementsByClassName('right-arrow-click').item(0);
|
|
||||||
if (right_el) { right_el.click(); };
|
|
||||||
break;
|
|
||||||
|
|
||||||
//case 40: // down
|
|
||||||
// break;
|
|
||||||
|
|
||||||
default: return; // exit this handler for other keys
|
|
||||||
}
|
|
||||||
e.preventDefault(); // prevent the default action (scroll / move caret)
|
|
||||||
};
|
|
||||||
|
|
||||||
function checkAll(bx) {
|
|
||||||
var cbs = document.getElementsByTagName('input');
|
|
||||||
for (var i = 0; i < cbs.length; i++) {
|
|
||||||
if (cbs[i].type == 'checkbox') {
|
|
||||||
cbs[i].checked = bx.checked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sort_by(column) {
|
|
||||||
current_sort_order = document.getElementsByName("sort_order")[0].value;
|
|
||||||
if (current_sort_order == "Asc") {
|
|
||||||
document.getElementsByName("sort_order").forEach((e) => e.value = "Desc");
|
|
||||||
} else {
|
|
||||||
document.getElementsByName("sort_order").forEach((e) => e.value = "Asc");
|
|
||||||
}
|
|
||||||
document.getElementsByName("sort_by").forEach((e) => e.value = column);
|
|
||||||
document.getElementById('table_form').requestSubmit();
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
// Get all "navbar-burger" elements
|
|
||||||
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
|
||||||
|
|
||||||
// Add a click event on each of them
|
|
||||||
$navbarBurgers.forEach(el => {
|
|
||||||
el.addEventListener('click', () => {
|
|
||||||
|
|
||||||
// Get the target from the "data-target" attribute
|
|
||||||
const target = el.dataset.target;
|
|
||||||
const $target = document.getElementById(target);
|
|
||||||
|
|
||||||
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
|
||||||
el.classList.toggle('is-active');
|
|
||||||
$target.classList.toggle('is-active');
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
htmx.on("htmx:responseError", function () {
|
|
||||||
document.getElementById("notifications").insertAdjacentHTML(
|
|
||||||
"afterend",
|
|
||||||
"<div class=\"notification mb-4 is-light is-danger\"><button class=\"delete\" onclick=\"this.parentElement.remove()\"></button>An Error occurred</div>");
|
|
||||||
})
|
|
Loading…
Reference in New Issue
Block a user