Fixed several features

This commit is contained in:
Johannes Randerath 2024-08-28 19:49:12 +02:00
parent c06ddc9498
commit 45b34f5fc8
13 changed files with 650 additions and 452 deletions

View File

@ -1,20 +0,0 @@
[default]
template_dir = "templates"
secret_key = "${ROCKET_SECRET_KEY}"
address = "0.0.0.0"
port = 8001
ident = "Randerath Inventur"
[default.tls]
certs="private/cert.pem"
key="private/key.pem"
#[default.databases.inventur]
#url = "${ROCKET_DATABASE_URL}"
[default.oauth.oauth]
auth_uri = "https://ldap.randerath.eu/realms/master/protocol/openid-connect/auth"
token_uri = "https://ldap.randerath.eu/realms/master/protocol/openid-connect/token"
client_id = "randy_inventur"
client_secret = "${ROCKET_CLIENT_SECRET}"
redirect_uri = "https://inventur.rander.at/auth"

View File

@ -17,5 +17,5 @@ fn main() {
.expect("Usage: add_column <tblid> <name> <uid>."); .expect("Usage: add_column <tblid> <name> <uid>.");
let conn = &mut establish_connection(); let conn = &mut establish_connection();
println!("{}", add_column(conn, table_id, name, uid).expect("Error")); println!("{}", add_column(conn, table_id, name, crate::FIELDTYPE::TEXT, uid).expect("Error"));
} }

View File

@ -34,5 +34,5 @@ fn main() {
} }
println!("{}", create_table(conn, name.to_string(), fields, uid).unwrap()); println!("{}", create_table(conn, name.to_string(), fields.clone(), vec![crate::FIELDTYPE::TEXT; fields.len()], uid).unwrap());
} }

View File

@ -24,7 +24,7 @@ pub fn create_jrcell_relative(conn: &mut MysqlConnection, tblid: i32, rowpos: i3
create_jrcell(conn, ntrid, clmid, &value) create_jrcell(conn, ntrid, clmid, &value)
} }
pub fn change_jrcell_value_relative(conn: &mut MysqlConnection, tblid: i32, rowpos: i32, idintbl: i32, new_value: String) -> Result<usize, diesel::result::Error> { pub fn change_jrcell_value_relative(conn: &mut MysqlConnection, tblid: i32, rowpos: i32, idintbl: i32, new_value: &String) -> Result<usize, diesel::result::Error> {
let ntrid = jrentries::get_jrentry_id(conn, tblid, rowpos); let ntrid = jrentries::get_jrentry_id(conn, tblid, rowpos);
if ntrid.is_err() { if ntrid.is_err() {
return Err(ntrid.err().unwrap()); return Err(ntrid.err().unwrap());
@ -78,7 +78,7 @@ pub fn create_jrcell(conn: &mut MysqlConnection, entryid: i32, columnid: i32, va
} }
pub fn change_jrcell_value(conn: &mut MysqlConnection, entryid: i32, columnid: i32, new_value: String) -> Result<usize, diesel::result::Error> { pub fn change_jrcell_value(conn: &mut MysqlConnection, entryid: i32, columnid: i32, new_value: &String) -> Result<usize, diesel::result::Error> {
diesel::update(jrcells.filter(jrentry_id.eq(entryid)).filter(jrcolumn_id.eq(columnid))) diesel::update(jrcells.filter(jrentry_id.eq(entryid)).filter(jrcolumn_id.eq(columnid)))
.set(cell_value.eq(new_value)) .set(cell_value.eq(new_value))
.execute(conn) .execute(conn)
@ -96,3 +96,10 @@ pub fn get_entry_cells(conn: &mut MysqlConnection, entryid: i32) -> Result<Vec<J
.select(Jrcell::as_select()) .select(Jrcell::as_select())
.load(conn) .load(conn)
} }
pub fn get_entry_cell_ids(conn: &mut MysqlConnection, entryid: i32) -> Result<Vec<i32>, diesel::result::Error> {
jrcells
.filter(jrentry_id.eq(entryid))
.select(id)
.load(conn)
}

View File

@ -7,7 +7,7 @@ use models::{ Jrcolumn, NewJrcolumn };
use schema::jrcolumns::dsl::{ jrcolumns, id, name, jrtable_id, id_in_table }; use schema::jrcolumns::dsl::{ jrcolumns, id, name, jrtable_id, id_in_table };
pub fn create_jrcolumn(conn: &mut MysqlConnection, tblid: i32, clmnname: String) -> Result<usize, diesel::result::Error> { pub fn create_jrcolumn(conn: &mut MysqlConnection, tblid: i32, clmnname: String, clmtype: crate::FIELDTYPE) -> Result<usize, diesel::result::Error> {
use schema::jrentries::dsl::{ jrentries, jrtable_id }; use schema::jrentries::dsl::{ jrentries, jrtable_id };
use models::{ Jrentry, NewJrcell }; use models::{ Jrentry, NewJrcell };
@ -16,7 +16,7 @@ pub fn create_jrcolumn(conn: &mut MysqlConnection, tblid: i32, clmnname: String)
return ncols; return ncols;
} }
let ncols = ncols.unwrap() as i32; let ncols = ncols.unwrap() as i32;
let jrcolumn = NewJrcolumn { name: clmnname, jrtable_id: tblid, id_in_table: ncols + 1 }; let jrcolumn = NewJrcolumn { name: clmnname.clone(), jrtable_id: tblid, column_type: clmtype as i32, id_in_table: (ncols + 1) as i32 };
let res = diesel::insert_into(crate::schema::jrcolumns::table) let res = diesel::insert_into(crate::schema::jrcolumns::table)
.values(&jrcolumn) .values(&jrcolumn)
.execute(conn); .execute(conn);
@ -48,16 +48,11 @@ pub fn create_jrcolumn(conn: &mut MysqlConnection, tblid: i32, clmnname: String)
} }
pub fn create_jrcolumns_empty_tbl(conn: &mut MysqlConnection, tblid: i32, names: Vec<String>) -> Result<usize, diesel::result::Error> { pub fn create_jrcolumns_empty_tbl(conn: &mut MysqlConnection, tblid: i32, names: Vec<String>, types: Vec<crate::FIELDTYPE>) -> Result<usize, diesel::result::Error> {
let mut cols : Vec<NewJrcolumn> = Vec::new(); let mut cols : Vec<NewJrcolumn> = Vec::new();
let ncols = jrtables::get_ncols(conn, tblid);
if ncols.is_err() { for i in 0..names.len() {
return ncols; cols.push(NewJrcolumn { name: names[i].clone(), column_type: types[i] as i32, jrtable_id: tblid, id_in_table: (i+1) as i32});
}
let mut ncols = ncols.unwrap() as i32;
for clmnname in names {
ncols += 1;
cols.push(NewJrcolumn { name: clmnname, jrtable_id: tblid, id_in_table: ncols});
} }
let cols = cols; let cols = cols;
@ -169,8 +164,8 @@ pub fn move_column(conn: &mut MysqlConnection, clmnid: i32, new_id_in_table: i32
.execute(conn) .execute(conn)
} }
pub fn delete_column_relative(conn: &mut MysqlConnection, tblid: i32, idinclm: i32) -> Result<usize, diesel::result::Error> { pub fn delete_column_relative(conn: &mut MysqlConnection, tblid: i32, idintbl: i32) -> Result<usize, diesel::result::Error> {
let clmid = get_clmid_relative(conn, tblid, idinclm); let clmid = get_clmid_relative(conn, tblid, idintbl);
if clmid.is_err() { if clmid.is_err() {
return Err(clmid.err().unwrap()); return Err(clmid.err().unwrap());
} }

View File

@ -76,7 +76,6 @@ pub fn delete_jrentry_relative(conn: &mut MysqlConnection, tblid: i32, rowpos:
return Err(entryid.err().unwrap()); return Err(entryid.err().unwrap());
} }
delete_jrentry(conn, entryid.unwrap()) delete_jrentry(conn, entryid.unwrap())
} }
pub fn delete_jrentry(conn: &mut MysqlConnection, entryid: i32) -> Result<usize, diesel::result::Error> { pub fn delete_jrentry(conn: &mut MysqlConnection, entryid: i32) -> Result<usize, diesel::result::Error> {

View File

@ -6,7 +6,7 @@ use crate::jrcolumns;
use models::{ Jrtable, NewJrtable }; use models::{ Jrtable, NewJrtable };
use schema::jrtables::dsl::{ jrtables, id, name, owner_id }; use schema::jrtables::dsl::{ jrtables, id, name, owner_id };
pub fn create_jrtable(conn: &mut MysqlConnection, tblname: &String, field_names: Vec<String>, uid: i32) -> Result<i32, diesel::result::Error> { pub fn create_jrtable(conn: &mut MysqlConnection, tblname: &String, field_names: Vec<String>, field_types: Vec<crate::FIELDTYPE>, uid: i32) -> Result<i32, diesel::result::Error> {
let jrtable = NewJrtable { name: tblname.to_string(), owner_id: uid }; let jrtable = NewJrtable { name: tblname.to_string(), owner_id: uid };
let tbl = diesel::insert_into(crate::schema::jrtables::table) let tbl = diesel::insert_into(crate::schema::jrtables::table)
@ -24,7 +24,7 @@ pub fn create_jrtable(conn: &mut MysqlConnection, tblname: &String, field_names:
return Err(tblid.err().unwrap()); return Err(tblid.err().unwrap());
} }
let tblid = tblid.unwrap(); let tblid = tblid.unwrap();
let cols = jrcolumns::create_jrcolumns_empty_tbl(conn, tblid, field_names); let cols = jrcolumns::create_jrcolumns_empty_tbl(conn, tblid, field_names, field_types);
if cols.is_err() { if cols.is_err() {
return Err(cols.err().unwrap()); return Err(cols.err().unwrap());
} }

View File

@ -1,3 +1,4 @@
#![recursion_limit = "512"]
//! Database API using the table style system provided by this crate //! Database API using the table style system provided by this crate
mod models; mod models;
mod schema; mod schema;
@ -17,6 +18,8 @@ use diesel::deserialize::FromSql;
use diesel::deserialize; use diesel::deserialize;
use diesel::backend::Backend; use diesel::backend::Backend;
use diesel::sql_types::Integer; use diesel::sql_types::Integer;
use diesel::expression::Expression;
#[derive(PartialEq, Clone, Copy, diesel::FromSqlRow)] #[derive(PartialEq, Clone, Copy, diesel::FromSqlRow)]
#[repr(i32)] #[repr(i32)]
@ -25,6 +28,10 @@ pub enum FIELDTYPE {
NUMBER = 1, NUMBER = 1,
} }
impl Expression for FIELDTYPE {
type SqlType = Integer;
}
impl<DB> FromSql<Integer, DB> for FIELDTYPE impl<DB> FromSql<Integer, DB> for FIELDTYPE
where DB: Backend, i32: FromSql<Integer,DB> { where DB: Backend, i32: FromSql<Integer,DB> {
fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> { fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
@ -36,6 +43,15 @@ where DB: Backend, i32: FromSql<Integer,DB> {
} }
} }
impl From<isize> for FIELDTYPE {
fn from(value: isize) -> Self {
match value {
1 => FIELDTYPE::NUMBER,
x => FIELDTYPE::TEXT,
}
}
}
/// represents and summarised all relevant data of a table. /// represents and summarised all relevant data of a table.
/// Standard return type if whole tables should be returned /// Standard return type if whole tables should be returned
pub struct Tbl { pub struct Tbl {
@ -169,7 +185,7 @@ pub fn search_table(tbl: Tbl, search_fields: Vec<i32>, search_value: String) ->
let mut field_sets = HashSet::new(); let mut field_sets = HashSet::new();
for field in search_fields { for field in search_fields {
for row in &rows { for row in &rows {
if row.cells[field as usize].contains(&search_value) { if row.cells[field as usize].to_lowercase().contains(&search_value) {
field_sets.insert(row.clone()); field_sets.insert(row.clone());
} }
} }
@ -241,11 +257,11 @@ pub fn delete_table(conn: &mut MysqlConnection, tblid: i32, uid: i32) -> Option<
/// Returns the id of the newly created table if the action seems to have been successful and the requesting user is the /// Returns the id of the newly created table if the action seems to have been successful and the requesting user is the
/// table's owner. /// table's owner.
/// None otherwise. /// None otherwise.
pub fn create_table(conn: &mut MysqlConnection, name: String, field_names: Vec<String>, uid: i32) -> Option<i32> { pub fn create_table(conn: &mut MysqlConnection, name: String, field_names: Vec<String>, field_types: Vec<crate::FIELDTYPE>, uid: i32) -> Option<i32> {
if jrtables::get_tbl_by_name_uid(conn, &name, uid).is_ok() { if jrtables::get_tbl_by_name_uid(conn, &name, uid).is_ok() {
return None; return None;
} }
if jrtables::create_jrtable(conn, &name, field_names, uid).is_err() { if jrtables::create_jrtable(conn, &name, field_names, field_types, uid).is_err() {
return None; return None;
} }
let tblid = jrtables::get_tbl_by_name_uid(conn, &name, uid); let tblid = jrtables::get_tbl_by_name_uid(conn, &name, uid);
@ -327,8 +343,21 @@ pub fn move_row(conn: &mut MysqlConnection, tblid: i32, rowpos: i32, newpos: i32
Some(true) Some(true)
} }
pub fn edit_row(conn: &mut MysqlConnection, tblid: i32, cells: Vec<String>, row_pos: i32, uid: i32) -> Option<bool> {
let owner = jrtables::get_owner_id(conn, tblid);
if owner.is_err() || owner.unwrap() != uid {
return None;
}
for (i, cell) in cells.iter().enumerate() {
if jrcells::change_jrcell_value_relative(conn, tblid, row_pos, (i+1) as i32, cell).is_err() {
return None;
}
}
Some(true)
}
pub fn edit_cell(conn: &mut MysqlConnection, tblid: i32, row_pos: i32, column_pos: i32, new_value: String, uid: i32) -> Option<bool> {
pub fn edit_cell(conn: &mut MysqlConnection, tblid: i32, row_pos: i32, column_pos: i32, new_value: &String, uid: i32) -> Option<bool> {
let owner = jrtables::get_owner_id(conn, tblid); let owner = jrtables::get_owner_id(conn, tblid);
if owner.is_err() || if owner.is_err() ||
owner.unwrap() != uid || owner.unwrap() != uid ||
@ -350,11 +379,11 @@ pub fn move_column(conn: &mut MysqlConnection, tblid:i32, column_pos: i32, new_c
Some(true) Some(true)
} }
pub fn add_column(conn: &mut MysqlConnection, tblid: i32, name: String, uid: i32) -> Option<i32> { pub fn add_column(conn: &mut MysqlConnection, tblid: i32, name: String, clmtype: FIELDTYPE, uid: i32) -> Option<i32> {
let owner = jrtables::get_owner_id(conn, tblid); let owner = jrtables::get_owner_id(conn, tblid);
if owner.is_err() || if owner.is_err() ||
owner.unwrap() != uid || owner.unwrap() != uid ||
jrcolumns::create_jrcolumn(conn, tblid, name).is_err() jrcolumns::create_jrcolumn(conn, tblid, name, clmtype).is_err()
{ {
return None; return None;
} }

View File

@ -37,6 +37,7 @@ pub struct Jrcolumn {
pub struct NewJrcolumn { pub struct NewJrcolumn {
pub name: String, pub name: String,
pub jrtable_id: i32, pub jrtable_id: i32,
pub column_type: i32,
pub id_in_table: i32, pub id_in_table: i32,
} }

View File

@ -65,8 +65,9 @@ async fn rocket() -> _ {
.attach(Db::fairing()) .attach(Db::fairing())
.attach(OAuth2::<auth::RanderathIdentity>::fairing("oauth")) .attach(OAuth2::<auth::RanderathIdentity>::fairing("oauth"))
.mount("/", routes![auth::oauth_login, auth::oauth_callback, home, login_home]) .mount("/", routes![auth::oauth_login, auth::oauth_callback, home, login_home])
.mount("/table", routes![table::table, table::table_sec, table::edit_tname, table::create, table::import_table]) .mount("/table", routes![table::table, table::table_sec, table::edit_tname, table::create, table::import_table, table::delete_table])
.mount("/entry", routes![table::new_entry]) .mount("/row", routes![table::new_entry, table::edit_entry, table::delete_entry])
.mount("/column", routes![table::delete_column])
.register("/", catchers![auth::redirect_to_login]) .register("/", catchers![auth::redirect_to_login])
.mount("/static", FileServer::from(relative!("static"))) .mount("/static", FileServer::from(relative!("static")))

View File

@ -9,8 +9,13 @@ use rocket::response::Redirect;
use inventur_db; use inventur_db;
use rocket::serde::{Serialize, Deserialize, json::Json}; use rocket::serde::{Serialize, Deserialize, json::Json};
use rocket::http::{Status, Cookie, CookieJar, SameSite}; use rocket::http::{Status, Cookie, CookieJar, SameSite};
use inventur_db::FIELDTYPE;
#[derive(FromForm)]
struct DeleteColumn {
tblid: i32,
id_in_table: i32,
}
#[derive(FromForm)] #[derive(FromForm)]
struct NewEntry { struct NewEntry {
@ -18,6 +23,19 @@ struct NewEntry {
cells: Vec<String>, cells: Vec<String>,
} }
#[derive(FromForm)]
struct EditEntry {
tblid: i32,
cells: Vec<String>,
row_pos: i32,
}
#[derive(FromForm)]
struct DeleteEntry {
tblid: i32,
row_pos: i32,
}
#[derive(FromForm)] #[derive(FromForm)]
struct EditTname { struct EditTname {
tblid: i32, tblid: i32,
@ -27,7 +45,8 @@ struct EditTname {
#[derive(FromForm)] #[derive(FromForm)]
struct NewTable { struct NewTable {
name: String, name: String,
fields: Vec<String>, field_names: Vec<String>,
field_types: Vec<isize>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -38,6 +57,11 @@ struct ImportTable {
rows: Vec<Vec<String>>, rows: Vec<Vec<String>>,
} }
#[derive(FromForm)]
struct DeleteTable {
tblid: i32,
}
#[derive(Serialize)] #[derive(Serialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
struct JRows { struct JRows {
@ -109,7 +133,8 @@ pub async fn table(conn: Db, tid: i32, sort_dir: Option<u8>, sort_field: Option<
let table = inventur_db::sort_table(table, sortfield, sortdir); let table = inventur_db::sort_table(table, sortfield, sortdir);
let tname = table.name; let tname = table.name;
let columns = table.column_names; let column_names = table.column_names;
let column_types = table.column_types.iter().map(|x: &inventur_db::FIELDTYPE| *x as i32).collect::<Vec<i32>>();
let rows : Vec<Vec<String>>= table.rows.iter().map(|v| {let mut r = v.cells.clone(); r.insert(0, v.row_pos.to_string()); r}).collect(); let rows : Vec<Vec<String>>= table.rows.iter().map(|v| {let mut r = v.cells.clone(); r.insert(0, v.row_pos.to_string()); r}).collect();
let (tids, tnames) = get_tids(&conn, uid).await; let (tids, tnames) = get_tids(&conn, uid).await;
@ -124,13 +149,24 @@ pub async fn table(conn: Db, tid: i32, sort_dir: Option<u8>, sort_field: Option<
tblname: tname, tblname: tname,
tids: tids, tids: tids,
tnames: tnames, tnames: tnames,
columns: columns, column_names: column_names,
column_types: column_types,
rows: rows, rows: rows,
} }
) )
) )
} }
#[post("/delete", data="<data>")]
pub async fn delete_table(conn: Db, data: Form<DeleteTable>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
let tblid = data.tblid;
if conn.run(move |c| inventur_db::delete_table(c, tblid, uid)).await.is_none() {
return Err(Status::Forbidden);
}
Ok(Redirect::to(uri!("/")))
}
#[post("/new", data="<data>")] #[post("/new", data="<data>")]
pub async fn new_entry(conn: Db, data: Form<NewEntry>, user: AuthUser) -> Result<Redirect, Status> { pub async fn new_entry(conn: Db, data: Form<NewEntry>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid; let uid = user.uid;
@ -142,6 +178,40 @@ pub async fn new_entry(conn: Db, data: Form<NewEntry>, user: AuthUser) -> Result
Ok(Redirect::to(uri!("/table", table_sec(tblid)))) Ok(Redirect::to(uri!("/table", table_sec(tblid))))
} }
#[post("/edit", data="<data>")]
pub async fn edit_entry(conn: Db, data: Form<EditEntry>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
let cells = data.cells.clone();
let tblid = data.tblid;
let row_pos = data.row_pos;
if conn.run(move |c| inventur_db::edit_row(c, tblid, cells, row_pos, uid)).await.is_none() {
return Err(Status::Forbidden);
}
Ok(Redirect::to(uri!("/table", table_sec(tblid))))
}
#[post("/delete", data="<data>")]
pub async fn delete_entry(conn: Db, data: Form<DeleteEntry>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
let tblid = data.tblid;
let row_pos = data.row_pos;
if conn.run(move |c| inventur_db::delete_row(c, tblid, row_pos, uid)).await.is_none() {
return Err(Status::Forbidden);
}
Ok(Redirect::to(uri!("/table", table_sec(tblid))))
}
#[post("/delete", data="<data>")]
pub async fn delete_column(conn: Db, data: Form<DeleteColumn>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
let tblid = data.tblid;
let idintbl = data.id_in_table;
if conn.run(move |c| inventur_db::delete_column(c, tblid, idintbl, uid)).await.is_none() {
return Err(Status::Forbidden);
}
Ok(Redirect::to(uri!("/table", table_sec(tblid))))
}
#[post("/name/edit", data="<data>")] #[post("/name/edit", data="<data>")]
pub async fn edit_tname(conn: Db, data: Form<EditTname>, user: AuthUser) -> Result<Redirect, Status> { pub async fn edit_tname(conn: Db, data: Form<EditTname>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid; let uid = user.uid;
@ -155,7 +225,7 @@ pub async fn edit_tname(conn: Db, data: Form<EditTname>, user: AuthUser) -> Resu
#[post("/create", data="<data>")] #[post("/create", data="<data>")]
pub async fn create(conn: Db, data: Form<NewTable>, user: AuthUser) -> Result<Redirect, Status> { pub async fn create(conn: Db, data: Form<NewTable>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid; let uid = user.uid;
let tblid = conn.run(move |c| inventur_db::create_table(c, data.name.clone(), data.fields.clone(), uid)).await; let tblid = conn.run(move |c| inventur_db::create_table(c, data.name.clone(), data.field_names.clone(), data.field_types.iter().map(|x: &isize| FIELDTYPE::from(*x)).collect::<Vec<FIELDTYPE>>(), uid)).await;
if tblid.is_none() { if tblid.is_none() {
return Err(Status::Forbidden); return Err(Status::Forbidden);
} }
@ -167,7 +237,7 @@ pub async fn import_table(conn: Db, data: Json<ImportTable>, user: AuthUser) ->
let uid = user.uid; let uid = user.uid;
let columns = data.columns.clone(); let columns = data.columns.clone();
let rows = data.rows.clone(); let rows = data.rows.clone();
let tblid = conn.run(move |c| inventur_db::create_table(c, data.name.clone(), columns, uid)).await; let tblid = conn.run(move |c| inventur_db::create_table(c, data.name.clone(), columns.clone(), vec![FIELDTYPE::TEXT; columns.len()], uid)).await;
if tblid.is_none() { if tblid.is_none() {
return Err(Status::UnprocessableEntity); return Err(Status::UnprocessableEntity);
} }
@ -177,4 +247,3 @@ pub async fn import_table(conn: Db, data: Json<ImportTable>, user: AuthUser) ->
} }
Ok(Redirect::to(uri!("/table", table_sec(tblid)))) Ok(Redirect::to(uri!("/table", table_sec(tblid))))
} }

View File

@ -8,7 +8,7 @@
Foobar is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. Foobar is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Inventory. If not, see <https://www.gnu.org/licenses/>. You should have received a copy of the GNU General Public License along with Inventory. If not, see <https://www.gnu.org/licenses/>.
--> -->
<html> <html>
<head lang="en"> <head lang="en">
<meta name="author" content="Johannes Randerath" /> <meta name="author" content="Johannes Randerath" />
@ -19,8 +19,9 @@
<script src="https://unpkg.com/papaparse@latest/papaparse.min.js"></script> <script src="https://unpkg.com/papaparse@latest/papaparse.min.js"></script>
<title>Inventory - {% block pagename %}{% endblock pagename %}</title> <title>Inventory - {% block pagename %}{% endblock pagename %}</title>
<script> <script>
let todelete = "";
function create_table() { function create_table() {
document.getElementById('create_table').submit().clear(); document.getElementById('create_table').submit().reset();
document.getElementById('dynamic_columns').innerHTML = ""; document.getElementById('dynamic_columns').innerHTML = "";
ncol = 1; ncol = 1;
} }
@ -35,8 +36,8 @@
if (req.readyState == XMLHttpRequest.DONE) { if (req.readyState == XMLHttpRequest.DONE) {
callback(); callback();
} }
} };
} };
function import_table() { function import_table() {
console.log("1"); console.log("1");
Papa.parse(document.getElementById('import_file').files[0], { Papa.parse(document.getElementById('import_file').files[0], {
@ -64,11 +65,39 @@
}; };
} }
}); });
};
function add_column() {
ncol += 1;
document.getElementById('dynamic_columns').innerHTML += `
<div class='row mt-3' id='columns'>
<div class='col-auto align-bottom'>
<div class='form-text me-1' id='field_index'>
<strong>${ncol}</strong>
</div>
</div>
<div class='col-auto'>
<input type='text' class='form-control' name='field_names' id='field_name' aria-describedby='field_name_label'>
</div>
<div class='col-auto'>
<select class='form-select' aria-label='data type' name='field_types'>
<option value='0' selected>Text</option>
<option value='1'>Number</option>
</select>
</div>
</div>`;
};
function confirm_delete(form) {
document.getElementById(`form_edit_${form}`).reset();
$('#confirm_delete_modal').modal('show');
document.getElementById('confirm_delete_modal_entity').innerHTML = form;
document.getElementById('confirm_delete_modal_button').onclick = function () {
document.getElementById(`form_delete_${form}`).submit();
}
} }
</script> </script>
{% block script %}{% endblock script %} {% block script %}{% endblock script %}
</head> </head>
<body>
<nav class='navbar navbar-expand-lg bg-body bg-body-tertiary'> <nav class='navbar navbar-expand-lg bg-body bg-body-tertiary'>
<div class='container-fluid'> <div class='container-fluid'>
<a class='navbar-brand' href='/'>Inventory</a> <a class='navbar-brand' href='/'>Inventory</a>
@ -102,7 +131,7 @@
</div> </div>
</nav> </nav>
{% block body %}{% endblock body %} {% block body %}{% endblock body %}
<!-- Modals --> <!-- Modals -->
<!-- Create new table --> <!-- Create new table -->
@ -128,14 +157,13 @@
</div> </div>
<div class="col-auto"> <div class="col-auto">
<label for="fields" class="form-label">Column name</label> <label for="fields" class="form-label">Column name</label>
<input type="text" class="form-control" id="field_name" aria-describedby="field_name_label" name="fields"> <input type="text" class="form-control" id="field_name" aria-describedby="field_name_label" name="field_names">
</div> </div>
<div class="col-auto"> <div class="col-auto">
<label for="field_name" class="form-label">Data type</label> <label for="field_type" class="form-label">Data type</label>
<select class="form-select" aria-label="data type"> <select class="form-select" aria-label="data type" name="field_types">
<option value="text" selected>Text</option> <option value="0" selected>Text</option>
<option value="number">Number</option> <option value="1">Number</option>
<option value="date">Date</option>
</select> </select>
</div> </div>
</div> </div>
@ -154,13 +182,13 @@
<button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Close</button> <button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Close</button>
<button class="btn btn-primary" type="button" onclick="create_table()">Create</button> <button class="btn btn-primary" type="button" onclick="create_table()">Create</button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<!-- Import new table --> <!-- Import new table -->
<div class="modal fade" id="import_table" tabindex="-1" aria-hidden="true"> <div class="modal fade" id="import_table" tabindex="-1" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -183,11 +211,29 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Confirm delete -->
<div class="modal fade" id="confirm_delete_modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5">Confirm deletion</h1>
</div> </div>
<div class="modal-body">
<h5>Are you sure you want to delete this <span id="confirm_delete_modal_entity"></span>?</h5>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal" type="button"><i class="bi bi-x"></i></button>
<button class="btn btn-danger" type="button" data-bs-dismiss="modal" id="confirm_delete_modal_button"><i class="bi bi-trash3-fill"></i></button>
</div>
</div>
</div>
</div>
{% block more_modals %}{% endblock more_modals %} {% block more_modals %}{% endblock more_modals %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
</body> </body>
</html> </html>

View File

@ -2,74 +2,84 @@
{% extends "base" %} {% extends "base" %}
{% block pagename %}Table{% endblock pagename %} {% block pagename %}Table{% endblock pagename %}
{% block script %}
<script type="text/javascript"> {% block script %}
<script type="text/javascript">
let ncol = 1; let ncol = 1;
let edit_mode = false; let edit_mode = false;
let column_names = [{% for col in column_names %} '{{ col }}', {% endfor %}];
let column_types = [{% for col in column_types %} {{ col }}, {% endfor %}];
document.addEventListener("DOMContentLoaded", (event) => { document.addEventListener("DOMContentLoaded", (event) => {
for (let row of document.getElementById("content_table").getElementsByTagName('tbody')[0].rows) { let rows = document.getElementById("content_table").getElementsByTagName('tbody')[0].rows;
let createClickHandler = function(r) { for (let i = 0; i < rows.length; i++)
return function() { {
$('#edit_item').modal('show'); let row = rows[i];
let createClickHandler = function(r)
{
return function()
{
$('#edit_entry_modal').modal('show');
document.getElementById('modal_caller').innerHTML = i+1;
document.getElementById('form_edit_entry_rowpos').value = i+1;
document.getElementById('form_delete_entry_rowpos').value = i+1;
let cells = row.cells;
for (let j = 1; j < cells.length; j++) {
document.getElementById(`form_edit_entry_${j}`).value = cells[j].innerHTML;
}
}; };
}; };
row.onclick = createClickHandler(row); row.onclick = createClickHandler(row);
} }
}); });
function add_column() {
ncol += 1;
document.getElementById('dynamic_columns').innerHTML += `
<div class='row mt-3' id='columns'>
<div class='col-auto align-bottom'>
<div class='form-text me-1' id='field_index'>
<strong>${ncol}</strong>
</div>
</div>
<div class='col-auto'>
<input type='text' class='form-control' name='fields' id='field_name' aria-describedby='field_name_label'>
</div>
<div class='col-auto'>
<select class='form-select' aria-label='data type'>
<option value='text' selected>Text</option>
<option value='number'>Number</option>
<option value='date'>Date</option>
</select>
</div>
</div>`;
}
function toggle_edit_tname() { function toggle_edit_tname() {
let span = document.getElementById('tname'); let span = document.getElementById('tname');
if (!edit_mode) { if (!edit_mode) {
span.innerHTML = ` span.innerHTML = `
<div class='row'> <div class='row'>
<div class='col-auto ml-1 p-2'> <div class='col-auto ml-1 p-2'>
<form id='form_edit_tname' action='/table/name/edit' method='post'> <form id='form_edit_table' action='/table/name/edit' method='post'>
<input name='tblid' value='{{ tblid }}' hidden /> <input name='tblid' value='{{ tblid }}' hidden />
<input type='text' class='form-control' id='tname_edit' name='new_name' value='{{ tblname }}' /> <input type='text' class='form-control' id='tname_edit' name='new_name' value='{{ tblname }}' />
</form> </form>
</div> </div>
<div class='col-auto mt-0 pt-0 pr-1'> <div class='col-auto mt-0 pt-0 pr-1'>
<button class='btn btn-success' type='button' onclick='toggle_edit_tname()'> <div class="btn-group" role="group">
<button class="btn btn-secondary" onclick="toggle_edit_tname();" type="button"><i class="bi bi-x"></i></button>
<button class='btn btn-success' type='button' onclick='edit_tname()'>
<i class='bi bi-check'></i> <i class='bi bi-check'></i>
</button> </button>
</div> </div>
</div>`; <button class='btn btn-danger' type="button" onclick='confirm_delete("table");'><i class="bi bi-trash3-fill"></i></button>
</div>
<form id="form_delete_table" action="/table/delete" method="post">
<input value="{{ tblid}}" name="tblid" hidden />
</form>`;
edit_mode = true; edit_mode = true;
document.getElementById('pencil_button_edit_tname').hidden = true;
} else { } else {
document.getElementById('form_edit_tname').submit().clear(); document.getElementById('pencil_button_edit_tname').hidden = false;
span.innerHTML = '{{ tblname }}'; span.innerHTML = '{{ tblname }}';
edit_mode = false; edit_mode = false;
} }
} }
function add_entry() { function edit_tname() {
document.getElementById('form_add_entry').submit().clear(); document.getElementById('form_edit_tname').submit();
toggle_edit_tname();
} }
</script> function edit_column(clmn_index) {
{% endblock script %} document.getElementById('form_edit_column_name').value = column_names[clmn_index];
{% block body %} document.getElementById('form_edit_column_type').value = column_types[clmn_index];
<div class="row justify-content-between"> document.getElementById('modal_caller').innerHTML = column_names[clmn_index];
document.getElementById('form_delete_column_idintbl').value = clmn_index + 1;
}
</script>
{% endblock script %}
{% block body %}
<div class="row justify-content-between">
<div class="col-auto"> <div class="col-auto">
<h1><div class='input-group'><span id="tname">{{ tblname }}</span><button class="btn" type="button" onclick="toggle_edit_tname();"><i class="bi bi-pencil"></i></button></div></h1> <h1><div class='input-group'><span id="tname">{{ tblname }}</span><button class="btn" type="button" onclick="toggle_edit_tname();" id="pencil_button_edit_tname"><i class="bi bi-pencil"></i></button></div></h1>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<div class="btn-toolbar mt-2" role="toolbar"> <div class="btn-toolbar mt-2" role="toolbar">
@ -86,7 +96,7 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<select name="search_fields" class="form-select" multiple> <select name="search_fields" class="form-select" multiple>
<li><option disabled class="dropdown-header">Search in</option></li> <li><option disabled class="dropdown-header">Search in</option></li>
{% for column in columns %} {% for column in column_names %}
<li><option class="dropdown-option" value="{{ loop.index - 1 }}" {% if (loop.index - 1) in search_fields %}selected{% endif %}>{{ column }}</option></li> <li><option class="dropdown-option" value="{{ loop.index - 1 }}" {% if (loop.index - 1) in search_fields %}selected{% endif %}>{{ column }}</option></li>
{% endfor %} {% endfor %}
</select> </select>
@ -94,9 +104,9 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<br> <br>
<table id="content_table" class="table table-striped table-hover table-responsive table-bordered mt-2"> <table id="content_table" class="table table-striped table-hover table-responsive table-bordered mt-2">
<thead> <thead>
<tr> <tr>
<th style="width:7%"> <th style="width:7%">
@ -106,7 +116,6 @@
</div> </div>
<div class="col-auto"> <div class="col-auto">
<div class="btn-toolbar"> <div class="btn-toolbar">
<button class="btn p-0 me-2 mdl" type="button" data-bs-toggle="modal" data-bs-target="#filter"><i class="bi bi-filter"></i></button>
<form method="GET" action="/table/{{ tblid }}"> <form method="GET" action="/table/{{ tblid }}">
<input value="0" name="sort_field" hidden /> <input value="0" name="sort_field" hidden />
<input value="{% if sort_field == 0 %}{{ (sort_dir + 1) % 2}}{% else %}0{% endif %}" name="sort_dir" hidden /> <input value="{% if sort_field == 0 %}{{ (sort_dir + 1) % 2}}{% else %}0{% endif %}" name="sort_dir" hidden />
@ -121,7 +130,7 @@
</div> </div>
</div> </div>
</th> </th>
{% for column in columns %} {% for column in column_names %}
<th> <th>
<div class="row justify-content-between"> <div class="row justify-content-between">
<div class="col-auto"> <div class="col-auto">
@ -129,7 +138,7 @@
</div> </div>
<div class="col-auto"> <div class="col-auto">
<div class="btn-toolbar"> <div class="btn-toolbar">
<button class="btn p-0 me-2" type="button" data-bs-toggle="modal" data-bs-target="#filter" onclick="set_modal_target('{{ column }}');"><i class="bi bi-filter"></i></button> <button class="btn p-0 me-2" type="button" data-bs-toggle="modal" data-bs-target="#edit_column_modal" onclick="edit_column({{ loop.index0 }});"><i class="bi bi-pencil"></i></button>
<form method="GET" action="/table/{{ tblid }}"> <form method="GET" action="/table/{{ tblid }}">
<input value="{{ loop.index }}" name="sort_field" hidden /> <input value="{{ loop.index }}" name="sort_field" hidden />
<input value="{% if sort_field == loop.index %}{{ (sort_dir + 1) % 2}}{% else %}0{% endif %}" name="sort_dir" hidden /> <input value="{% if sort_field == loop.index %}{{ (sort_dir + 1) % 2}}{% else %}0{% endif %}" name="sort_dir" hidden />
@ -156,12 +165,12 @@
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% endblock body %} {% endblock body %}
{% block more_modals %} {% block more_modals %}
<!-- Add new item --> <!-- Add new item -->
<div class="modal fade" id="new_item" tabindex="-1" aria-hidden="true"> <div class="modal fade" id="new_item" tabindex="-1" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -171,9 +180,9 @@
<div class="modal-body"> <div class="modal-body">
<h5>Cell values:</h5> <h5>Cell values:</h5>
<br> <br>
<form id="form_add_entry" action="/entry/new" method="post"> <form id="form_add_entry" action="/row/new" method="post">
<input name="tblid" value="{{ tblid }}" hidden /> <input name="tblid" value="{{ tblid }}" hidden />
{% for column in columns %} {% for column in column_names %}
<div class="mb-3"> <div class="mb-3">
<div class="row"> <div class="row">
<div class="col-auto"> <div class="col-auto">
@ -188,46 +197,108 @@
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Close</button> <div class="container row justify-content-between">
<button class="btn btn-primary" type="button" onclick="add_entry()">Add</button> <div class="col-auto">
</div>
<div class="col-auto">
<button class="btn btn-secondary" data-bs-dismiss="modal" type="button" onclick="document.getElementById('form_add_entry').reset();"><i class="bi bi-x"></i></button>
<button class="btn btn-primary" type="button" onclick="document.getElementById('form_add_entry').submit().reset();"><i class="bi bi-check-lg"></i></button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<!-- Edit item --> <!-- Edit entry -->
<div class="modal fade" id="edit_item" tabindex="-1" aria-hidden="true"> <div class="modal fade" id="edit_entry_modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h1 class="modal-title fs-5">Edit entry</h1> <h1 class="modal-title fs-5"><div class="row"><div class="col-auto">Row <span id="modal_caller"></span></div><div class="col-auto"><i class="bi bi-pencil-fill"></i></div></div></h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form action="/row/edit" method="post" id="form_edit_entry">
<input name="tblid" value="{{ tblid }}" hidden>
<input name="row_pos" id="form_edit_entry_rowpos" hidden>
{% for column in column_names %}
<div class="mb-3">
<div class="row">
<div class="col-auto">
<label for="form_edit_entry_{{ loop.index }}" class="form-label">{{ column }}</label>
</div>
<div class="col-auto">
<input type="text" class="form-control" name="cells" id="form_edit_entry_{{ loop.index }}" />
</div>
</div>
</div>
{% endfor %}
</form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Close</button> <div class="container row justify-content-between">
<button class="btn btn-primary" type="button">Save</button> <div class="col-auto">
<form action="/row/delete" method="post" id="form_delete_entry">
<input name="tblid" value="{{ tblid }}" hidden>
<input id="form_delete_entry_rowpos" name="row_pos" hidden>
<button class="btn btn-danger" data-bs-dismiss="modal" type="button" onclick="confirm_delete('entry');"><i class="bi bi-trash3-fill"></i></button>
</form>
</div>
<div class="col-auto">
<button class="btn btn-secondary" data-bs-dismiss="modal" type="button" onclick="document.getElementById('form_edit_entry').reset();"><i class="bi bi-x"></i></button>
<button class="btn btn-primary" type="button" onclick="document.getElementById('form_edit_entry').submit().reset();"><i class="bi bi-check-lg"></i></button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<!-- Filter column --> <!-- Edit column -->
<div class="modal fade" id="filter" tabindex="-1" aria-hidden="true"> <div class="modal fade" id="edit_column_modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h1 class="modal-title fs-5">Edit filter on <span id="modal_caller"></span></h1> <h1 class="modal-title fs-5"><div class="row"><div class="col-auto"><span id="modal_caller"></span></div><div class="col-auto"><i class="bi bi-pencil-fill"></i></div></div></h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form action="/column/edit" method="post" id="form_edit_column">
<input name="tblid" value="{{ tblid }}" hidden>
<div class="mb-3">
<div class="row">
<div class="col-auto">
<label for="form_edit_column_name" class="form-label">Name:</label>
<input type="text" class="form-control" name="name" id="form_edit_column_name" />
</div>
<div class="col-auto">
<label for="form_edit_column_type" class="form-label">Type</label>
<select class="form-select" name="column_type" id="form_edit_column_type">
<option value="0">Text</option>
<option value="1">Number</option>
</select>
</div>
</div>
</div>
</form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Close</button> <div class="container row justify-content-between">
<button class="btn btn-primary" type="button">Filter</button> <div class="col-auto">
<form action="/column/delete" method="post" id="form_delete_column">
<input name="id_in_table" id="form_delete_column_idintbl" hidden>
<input name="tblid" value="{{ tblid }}" hidden>
<button class="btn btn-danger" data-bs-dismiss="modal" type="button" onclick="confirm_delete('column');"><i class="bi bi-trash3-fill"></i></button>
</form>
</div>
<div class="col-auto">
<button class="btn btn-secondary" data-bs-dismiss="modal" type="button" onclick="document.getElementById('form_edit_column').reset();"><i class="bi bi-x"></i></button>
<button class="btn btn-primary" type="button" onclick="document.getElementById('form_edit_column').submit().reset();"><i class="bi bi-check-lg"></i></button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
{% endblock more_modals %} {% endblock more_modals %}