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"
|
||||
description = "An admin interface for actix-web"
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
repository = "https://github.com/mgugger/actix-admin"
|
||||
edition = "2021"
|
||||
exclude = [
|
||||
@ -19,26 +19,26 @@ name = "actix_admin"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "^4.2.1"
|
||||
actix-web = "^4.3.1"
|
||||
actix-session = { version = "^0.7.2", features = [] }
|
||||
actix-multipart = "^0.4.0"
|
||||
actix-multipart = "^0.6.0"
|
||||
actix-files = "^0.6.2"
|
||||
futures-util = "0.3.25"
|
||||
chrono = "0.4.23"
|
||||
tera = "^1.17.1"
|
||||
async-trait = "^0.1.60"
|
||||
futures-util = "0.3.27"
|
||||
chrono = "0.4.24"
|
||||
tera = "^1.18.1"
|
||||
async-trait = "^0.1.67"
|
||||
lazy_static = "^1.4.0"
|
||||
itertools = "^0.10.5"
|
||||
serde = "^1.0.152"
|
||||
serde_derive = "^1.0.152"
|
||||
sea-orm = { version = "^0.10.6", features = [], default-features = false }
|
||||
actix-admin-macros = { version = "0.3.0", path = "actix_admin_macros" }
|
||||
serde = "^1.0.158"
|
||||
serde_derive = "^1.0.158"
|
||||
sea-orm = { version = "^0.11.1", features = [], default-features = false }
|
||||
actix-admin-macros = { version = "0.4.0", path = "actix_admin_macros" }
|
||||
derive_more = "0.99.17"
|
||||
regex = "1.7.1"
|
||||
|
||||
[dev-dependencies]
|
||||
sea-orm = { version = "^0.10.6", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
||||
actix-rt = "2.7.0"
|
||||
sea-orm = { version = "^0.11.1", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
||||
actix-rt = "2.8.0"
|
||||
azure_auth = { path = "./examples/azure_auth/azure_auth" }
|
||||
oauth2 = "4.3"
|
||||
dotenv = "0.15"
|
||||
|
@ -3,7 +3,7 @@ name = "actix-admin-macros"
|
||||
description = "macros to be used with actix-admin crate"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/mgugger/actix-admin"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
edition = "2021"
|
||||
exclude = [
|
||||
"tests/*"
|
||||
@ -14,7 +14,7 @@ proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
bae = "0.1.7"
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0", features = ["full", "extra-traits"] }
|
||||
proc-macro2 = { version = "1.0.36", default-features = false }
|
||||
quote = "1.0.26"
|
||||
syn = { version = "1.0.109", features = ["full", "extra-traits"] }
|
||||
proc-macro2 = { version = "1.0.52", default-features = false }
|
||||
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
|
||||
|
||||
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 actix_web::{web, Route};
|
||||
use actix_web::{web, Route };
|
||||
use tera::Tera;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use crate::routes::{
|
||||
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
|
||||
pub struct ActixAdminBuilder {
|
||||
@ -62,6 +65,133 @@ pub trait ActixAdminBuilderTrait {
|
||||
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 {
|
||||
fn new(configuration: ActixAdminConfiguration) -> Self {
|
||||
ActixAdminBuilder {
|
||||
@ -69,6 +199,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
||||
entity_names: HashMap::new(),
|
||||
view_models: HashMap::new(),
|
||||
configuration: configuration,
|
||||
tera: get_tera()
|
||||
},
|
||||
custom_routes: Vec::new(),
|
||||
scopes: HashMap::new(),
|
||||
@ -104,7 +235,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
||||
.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::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();
|
||||
@ -232,8 +363,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
||||
};
|
||||
let mut admin_scope = web::scope("/admin")
|
||||
.route("/", index_handler)
|
||||
.service(actix_files::Files::new("/static", "./static").show_files_listing())
|
||||
.default_service(web::to(not_found));
|
||||
.default_service(web::to(not_found::<T>));
|
||||
|
||||
for (_entity, scope) in self.scopes {
|
||||
admin_scope = admin_scope.service(scope);
|
||||
@ -249,4 +379,4 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
||||
fn get_actix_admin(&self) -> ActixAdmin {
|
||||
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 derive_more::{Display, Error};
|
||||
use lazy_static::lazy_static;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use serde::Serialize;
|
||||
use tera::Tera;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::BuildHasher;
|
||||
use tera::{to_value, try_get_value, Result, Tera};
|
||||
|
||||
pub mod builder;
|
||||
pub mod model;
|
||||
@ -32,7 +30,6 @@ pub mod prelude {
|
||||
ActixAdminViewModel, ActixAdminViewModelField, ActixAdminViewModelFieldType,
|
||||
ActixAdminViewModelSerializable, ActixAdminViewModelTrait,
|
||||
};
|
||||
pub use crate::TERA;
|
||||
pub use crate::{hashmap, ActixAdminSelectListTrait};
|
||||
pub use crate::{ActixAdmin, ActixAdminAppDataTrait, ActixAdminConfiguration, ActixAdminError};
|
||||
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
|
||||
pub trait ActixAdminAppDataTrait {
|
||||
fn get_db(&self) -> &DatabaseConnection;
|
||||
@ -214,6 +81,7 @@ pub struct ActixAdmin {
|
||||
pub entity_names: HashMap<String, Vec<ActixAdminMenuElement>>,
|
||||
pub view_models: HashMap<String, ActixAdminViewModel>,
|
||||
pub configuration: ActixAdminConfiguration,
|
||||
pub tera: Tera
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Serialize)]
|
||||
|
@ -5,7 +5,6 @@ use crate::ActixAdminError;
|
||||
use crate::ActixAdminNotification;
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::TERA;
|
||||
use super::DEFAULT_ENTITIES_PER_PAGE;
|
||||
use super::Params;
|
||||
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();
|
||||
|
||||
if !user_can_access_page(&session, actix_admin, view_model) {
|
||||
return render_unauthorized(&ctx);
|
||||
return render_unauthorized(&ctx, &actix_admin);
|
||||
}
|
||||
|
||||
let model;
|
||||
@ -93,7 +92,7 @@ async fn create_or_edit_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTra
|
||||
ctx.insert("sort_order", &sort_order);
|
||||
ctx.insert("page", &page);
|
||||
|
||||
let body = TERA
|
||||
let body = actix_admin.tera
|
||||
.render("create_or_edit.html", &ctx)
|
||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||
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::ActixAdminError;
|
||||
use crate::ActixAdminNotification;
|
||||
use crate::TERA;
|
||||
use actix_multipart::Multipart;
|
||||
use actix_multipart::MultipartError;
|
||||
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) {
|
||||
let mut ctx = Context::new();
|
||||
ctx.insert("render_partial", &true);
|
||||
return render_unauthorized(&ctx);
|
||||
return render_unauthorized(&ctx, &actix_admin);
|
||||
}
|
||||
let db = &data.get_db();
|
||||
|
||||
@ -187,7 +186,7 @@ async fn render_form<E: ActixAdminViewModelTrait>(
|
||||
.collect();
|
||||
|
||||
ctx.insert("notifications", ¬ifications);
|
||||
let body = TERA
|
||||
let body = actix_admin.tera
|
||||
.render("create_or_edit.html", &ctx)
|
||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||
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) {
|
||||
let mut ctx = Context::new();
|
||||
ctx.insert("render_partial", &true);
|
||||
return render_unauthorized(&ctx);
|
||||
return render_unauthorized(&ctx, &actix_admin);
|
||||
}
|
||||
|
||||
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) {
|
||||
let mut ctx = Context::new();
|
||||
ctx.insert("render_partial", &true);
|
||||
return render_unauthorized(&ctx);
|
||||
return render_unauthorized(&ctx, &actix_admin);
|
||||
}
|
||||
|
||||
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 view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
||||
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();
|
||||
@ -49,7 +49,7 @@ pub async fn delete_file<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>
|
||||
let entity_name = E::get_entity_name();
|
||||
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
||||
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();
|
||||
@ -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("model", &model);
|
||||
|
||||
let body = TERA
|
||||
let body = actix_admin.tera
|
||||
.render("form_elements/input.html", &ctx)
|
||||
.map_err(|err| error::ErrorInternalServerError(err))? ;
|
||||
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
||||
|
@ -2,7 +2,6 @@ use actix_session::{Session};
|
||||
use tera::{Context};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::TERA;
|
||||
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> {
|
||||
let body = TERA
|
||||
pub fn render_unauthorized(ctx: &Context, actix_admin: &ActixAdmin) -> Result<HttpResponse, Error> {
|
||||
let body = actix_admin.tera
|
||||
.render("unauthorized.html", &ctx)
|
||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||
Ok(HttpResponse::Unauthorized().content_type("text/html").body(body))
|
||||
|
@ -4,8 +4,6 @@ use tera::{Context};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::TERA;
|
||||
|
||||
use super::{ add_auth_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);
|
||||
|
||||
let body = TERA
|
||||
let body = actix_admin.tera
|
||||
.render("index.html", &ctx)
|
||||
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
|
||||
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
||||
}
|
||||
|
||||
pub async fn not_found() -> Result<HttpResponse, Error> {
|
||||
let body = TERA
|
||||
pub async fn not_found<T: ActixAdminAppDataTrait>(data: web::Data<T>) -> Result<HttpResponse, Error> {
|
||||
let body = data.get_actix_admin().tera
|
||||
.render("not_found.html", &Context::new())
|
||||
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
|
||||
Ok(HttpResponse::NotFound().content_type("text/html").body(body))
|
||||
|
@ -13,7 +13,6 @@ use crate::ActixAdminModel;
|
||||
use crate::ActixAdminNotification;
|
||||
use crate::ActixAdminViewModel;
|
||||
use crate::ActixAdminViewModelTrait;
|
||||
use crate::TERA;
|
||||
use actix_session::Session;
|
||||
|
||||
#[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);
|
||||
|
||||
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();
|
||||
@ -139,7 +138,7 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||
ctx.insert("sort_by", &sort_by);
|
||||
ctx.insert("sort_order", &sort_order);
|
||||
|
||||
let body = TERA
|
||||
let body = actix_admin.tera
|
||||
.render("list.html", &ctx)
|
||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||
Ok(http_response_code.content_type("text/html").body(body))
|
||||
|
@ -6,7 +6,6 @@ use tera::{Context};
|
||||
use crate::ActixAdminNotification;
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::TERA;
|
||||
use super::{Params, DEFAULT_ENTITIES_PER_PAGE};
|
||||
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 view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
||||
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();
|
||||
@ -67,7 +66,7 @@ pub async fn show<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(sessio
|
||||
|
||||
add_auth_context(&session, actix_admin, &mut ctx);
|
||||
|
||||
let body = TERA
|
||||
let body = actix_admin.tera
|
||||
.render("show.html", &ctx)
|
||||
.map_err(|err| error::ErrorInternalServerError(format!("{:?}", err)))?;
|
||||
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">
|
||||
<script src="https://unpkg.com/htmx.org@1.8.4"></script>
|
||||
|
||||
<link rel="stylesheet" href="/admin/static/css/default.css">
|
||||
<script src="/admin/static/js/default.js"></script>
|
||||
<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