add editing

This commit is contained in:
OMGeeky
2024-10-12 13:04:09 +02:00
parent 080c1a006c
commit 5ce5921481
4 changed files with 200 additions and 40 deletions

View File

@@ -1,11 +1,11 @@
use derive_more::{FromStr, FromStrError};
use rocket::State;
#[macro_use] #[macro_use]
extern crate rocket; extern crate rocket;
use derive_more::FromStr;
use derive_more::FromStrError;
use rocket::request::FromParam; use rocket::request::FromParam;
use rocket::response::Responder; use rocket::response::Responder;
use rocket::tokio::time::{sleep, Duration}; use rocket::tokio::time::{sleep, Duration};
use rocket::State;
use rocket_dyn_templates::Template; use rocket_dyn_templates::Template;
use std::sync::OnceLock; use std::sync::OnceLock;
use twba_common::init_tracing; use twba_common::init_tracing;
@@ -51,8 +51,9 @@ async fn main() -> Result<(), MainError> {
.mount( .mount(
"/services/", "/services/",
routes![ routes![
services::service, services::service_index,
services::service_info, services::task_edit,
services::service_edit,
services::update_progress, services::update_progress,
services::increment_progress, services::increment_progress,
services::increment_task_progress, services::increment_task_progress,

View File

@@ -1,11 +1,16 @@
use crate::DatabaseConnection; use crate::DatabaseConnection;
use crate::{AvailableServices, ResponderError}; use crate::ResponderError;
use rocket::form::Form;
use rocket::State; use rocket::State;
use rocket_dyn_templates::{context, Template}; use rocket_dyn_templates::{context, Template};
use twba_common::prelude::twba_local_db::prelude::{Services, Tasks}; use twba_common::prelude::twba_local_db::prelude::Services;
use twba_common::prelude::twba_local_db::prelude::ServicesModel;
use twba_common::prelude::twba_local_db::prelude::Tasks;
use twba_common::prelude::twba_local_db::prelude::TasksModel;
use twba_common::prelude::twba_local_db::re_exports::sea_orm::ActiveModelTrait; use twba_common::prelude::twba_local_db::re_exports::sea_orm::ActiveModelTrait;
use twba_common::prelude::twba_local_db::re_exports::sea_orm::ActiveValue; use twba_common::prelude::twba_local_db::re_exports::sea_orm::ActiveValue;
use twba_common::prelude::twba_local_db::re_exports::sea_orm::{EntityTrait, IntoActiveModel}; use twba_common::prelude::twba_local_db::re_exports::sea_orm::EntityTrait;
use twba_common::prelude::twba_local_db::re_exports::sea_orm::IntoActiveModel;
async fn get_services(db: &DatabaseConnection) -> Result<Vec<Service>, ResponderError> { async fn get_services(db: &DatabaseConnection) -> Result<Vec<Service>, ResponderError> {
let mut list = vec![]; let mut list = vec![];
@@ -41,6 +46,11 @@ pub struct Service {
last_update: String, last_update: String,
} }
#[derive(serde::Serialize, FromForm)]
pub struct IdValueForm<T> {
id: i32,
value: T,
}
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
pub struct Task { pub struct Task {
id: i32, id: i32,
@@ -50,12 +60,37 @@ pub struct Task {
max_progress: i32, max_progress: i32,
} }
#[get("/<service>/info")] #[post("/edit-service", data = "<data>")]
pub(super) fn service_info(service: AvailableServices) -> String { pub async fn service_edit(
format!("Here is some info about the service: name: {service}") db: &State<DatabaseConnection>,
data: Form<IdValueForm<String>>,
) -> Result<(), ResponderError> {
let data = data.into_inner();
let model = get_service_by_id(data.id, db.inner()).await?;
let mut model = model.into_active_model();
let name_value: String = data.value;
model.name = ActiveValue::Set(name_value);
model.save(db.inner()).await?;
Ok(())
} }
#[post("/edit-task", data = "<data>")]
pub async fn task_edit(
db: &State<DatabaseConnection>,
data: Form<IdValueForm<String>>,
) -> Result<(), ResponderError> {
let data = data.into_inner();
let model = get_task_by_id(data.id, db.inner()).await?;
let mut model = model.into_active_model();
let description: String = data.value;
model.description = ActiveValue::Set(Some(description));
model.save(db.inner()).await?;
Ok(())
}
#[get("/")] #[get("/")]
pub(super) async fn service(db: &State<DatabaseConnection>) -> Result<Template, ResponderError> { pub(super) async fn service_index(
db: &State<DatabaseConnection>,
) -> Result<Template, ResponderError> {
let services = get_services(db.inner()).await?; let services = get_services(db.inner()).await?;
Ok(Template::render( Ok(Template::render(
@@ -80,6 +115,15 @@ pub async fn increment_task_progress(
db: &State<DatabaseConnection>, db: &State<DatabaseConnection>,
) -> Result<(), ResponderError> { ) -> Result<(), ResponderError> {
let db = db.inner(); let db = db.inner();
let task = get_task_by_id(task, db).await?;
let progress = task.progress;
let mut task = task.into_active_model();
task.progress = ActiveValue::Set(progress + 1);
task.save(db).await?;
Ok(())
}
async fn get_task_by_id(task: i32, db: &DatabaseConnection) -> Result<TasksModel, ResponderError> {
let task = Tasks::find_by_id(task) let task = Tasks::find_by_id(task)
.one(db) .one(db)
.await? .await?
@@ -87,12 +131,9 @@ pub async fn increment_task_progress(
table: "Tasks", table: "Tasks",
key: format!("{task}"), key: format!("{task}"),
})?; })?;
let progress = task.progress; Ok(task)
let mut task = task.into_active_model();
task.progress = ActiveValue::Set(progress + 1);
task.save(db).await?;
Ok(())
} }
#[post("/<service>/increment-progress/<task>")] #[post("/<service>/increment-progress/<task>")]
pub async fn increment_progress( pub async fn increment_progress(
service: i32, service: i32,
@@ -101,14 +142,7 @@ pub async fn increment_progress(
) -> Result<(), ResponderError> { ) -> Result<(), ResponderError> {
let db = db_state.inner(); let db = db_state.inner();
let service = let service = get_service_by_id(service, db).await?;
Services::find_by_id(service)
.one(db)
.await?
.ok_or(ResponderError::DbEntityNotFound {
table: "Services",
key: format!("{service}"),
})?;
let datetime = chrono::offset::Utc::now().to_rfc3339(); let datetime = chrono::offset::Utc::now().to_rfc3339();
@@ -120,6 +154,22 @@ pub async fn increment_progress(
service.save(db).await?; service.save(db).await?;
Ok(()) Ok(())
} }
async fn get_service_by_id(
service: i32,
db: &DatabaseConnection,
) -> Result<ServicesModel, ResponderError> {
let service =
Services::find_by_id(service)
.one(db)
.await?
.ok_or(ResponderError::DbEntityNotFound {
table: "Services",
key: format!("{service}"),
})?;
Ok(service)
}
#[get("/update_progress")] #[get("/update_progress")]
pub async fn update_progress(db: &State<DatabaseConnection>) -> Result<Template, ResponderError> { pub async fn update_progress(db: &State<DatabaseConnection>) -> Result<Template, ResponderError> {
let services = get_services(db.inner()).await?; let services = get_services(db.inner()).await?;

View File

@@ -5,6 +5,7 @@
<title>Services and Tasks</title> <title>Services and Tasks</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
@@ -13,14 +14,62 @@
<div class="row" id="services-container"> <div class="row" id="services-container">
</div> </div>
<div class="modal" id="editIdStringModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form id="editForm" method="post" action="/services/edit-service/test">
<input type="hidden" id="idField" name="id">
<div class="form-group">
<label for="valueField">Name:</label>
<input type="text" class="form-control" id="valueField" name="value" required>
</div>
<button type="submit" class="btn btn-primary">Save changes</button>
</form>
</div>
</div>
</div>
</div>
<button class="btn btn-primary" id="add-service">Add Service</button> <button class="btn btn-primary" id="add-service">Add Service</button>
<div style="flex-direction: row; display: flex" class="container"> <div style="flex-direction: row; display: flex" class="container">
<button class="btn btn-primary" id="update-button">Update Progress</button> <button class="btn btn-primary" id="update-button">Update Progress</button>
<div id="last-update-element" data-timestamp="0">hi</div> <div id="last-update-element" data-timestamp="0">hi</div>
<input type="checkbox" id="auto-update-toggle" checked>
<label for="auto-update-toggle">Enable Auto Update</label>
</div> </div>
</div> </div>
<script> <script>
function openEditServiceModal(serviceId, serviceName) {
setAutoUpdateActive(false);
$('#idField').val(serviceId);
$('#valueField').val(serviceName);
$('#valueField').name = "Name";
$('#editForm').attr("action", "/services/edit-service/");
$('#editIdStringModal').modal('show');
console.log('Edit Service ID: ' + serviceId);
}
function openEditTaskModal(taskId, taskName) {
setAutoUpdateActive(false);
$('#idField').val(taskId);
$('#valueField').val(taskName);
$('#valueField').name = "Description";
$('#editForm').attr("action", "/services/edit-task/");
$('#editIdStringModal').modal('show');
console.log('Edit Task ID: ' + taskId);
}
function add_service() { function add_service() {
$.ajax({ $.ajax({
type: "POST", type: "POST",
@@ -65,36 +114,92 @@
console.log('updating progress: ' + lastUpdateElement.text()); console.log('updating progress: ' + lastUpdateElement.text());
} }
// Auto-update functionality
const normalUpdateInterval = 5000;
const slowUpdateInterval = 30000;
let autoUpdateCheckboxValue = true;
let autoUpdateActiveOverride = autoUpdateCheckboxValue;
let updateInterval; let updateInterval;
function startUpdateInterval(interval) { function startUpdateInterval(interval) {
clearInterval(updateInterval); // Clear any existing interval clearInterval(updateInterval);
updateInterval = setInterval(updateProgress, interval); updateInterval = setInterval(updateProgress, interval);
} }
function stopUpdateInterval() {
clearInterval(updateInterval);
}
function setAutoUpdateActive(value) {
autoUpdateActiveOverride = value;
console.log("autoUpdateActive: " + autoUpdateActiveOverride);
if (autoUpdateActiveOverride) {
// Checkbox is checked, start auto-update
startUpdateInterval(normalUpdateInterval);
} else {
// Checkbox is unchecked, stop auto-update
stopUpdateInterval();
}
}
function reloadAutoUpdateActiveFromCheckbox() {
setAutoUpdateActive(autoUpdateCheckboxValue);
}
$(document).ready(function () { $(document).ready(function () {
const normalUpdateInterval = 5000;
const slowUpdateInterval = 30000;
$("#update-button").click(updateProgress); $("#update-button").click(updateProgress);
$("#add-service").click(add_service); $("#add-service").click(add_service);
updateProgress();
// Initial update and fast interval // Initial update and fast interval
updateProgress(); updateProgress();
startUpdateInterval(normalUpdateInterval); startUpdateInterval(normalUpdateInterval);
// Detect visibility changes $('#editForm').submit(function (event) {
event.preventDefault();
var form = $(this);
var url = form.attr('action');
var type = form.attr('method');
$.ajax({
type: type,
url: url,
data: form.serialize(),
success: function (data) {
$('#editIdStringModal').modal('hide');
updateProgress();
},
error: function (error) {
console.error("Error editing:", error);
}
});
})
// Toggle auto-update
$("#auto-update-toggle").change(function () {
autoUpdateCheckboxValue = this.checked;
reloadAutoUpdateActiveFromCheckbox();
});
autoUpdateCheckboxValue = $("#auto-update-toggle").is(":checked");
$('#editIdStringModal').on('hidden.bs.modal', function () {
reloadAutoUpdateActiveFromCheckbox();
});
// Detect visibility changes (adjust interval when tab is hidden)
document.addEventListener("visibilitychange", function () { document.addEventListener("visibilitychange", function () {
if (autoUpdateActiveOverride) {
if (document.visibilityState === "visible") { if (document.visibilityState === "visible") {
// Fast updates when tab is visible
startUpdateInterval(normalUpdateInterval); startUpdateInterval(normalUpdateInterval);
} else { } else {
// Slow updates when tab is hidden
startUpdateInterval(slowUpdateInterval); startUpdateInterval(slowUpdateInterval);
} }
}
}); });
}); });
</script> </script>

View File

@@ -1,12 +1,15 @@
{% for service in services %} {% for service in services %}
<div class="col-md-6"> <div class="col-md-6">
<h2>{{ service.name }}</h2> <h2>{{ service.name }}
<button class="btn btn-sm btn-primary" onclick="openEditServiceModal('{{ service.id }}', '{{ service.name }}')">Edit</button>
</h2>
<ul class="list-group"> <ul class="list-group">
{% for task in service.tasks %} {% for task in service.tasks %}
<li class="list-group-item"> <li class="list-group-item">
{{ task.name }} {{ task.description }}
<button class="btn btn-sm btn-primary" onclick="openEditTaskModal('{{ task.id }}', '{{ task.description }}')">Edit</button>
<div class="progress"> <div class="progress">
<div class="progress-bar" role="progressbar" style="width:{{ task.max_progress / task.progress * 100 }}%;" aria-valuenow="{{ task.progress }}" aria-valuemin="0" aria-valuemax="{{ task.max_progress }}">{{ task.progress }}%</div> <div class="progress-bar" role="progressbar" style="width:{{ task.progress/task.max_progress *100 }}%;" aria-valuenow="{{ task.progress }}" aria-valuemin="0" aria-valuemax="{{ task.max_progress }}">{{ task.progress }}%</div>
</div> </div>
<button id="increment-button" onclick="update_task_progress( '{{service.id}}','{{task.id}}' )">+</button> <button id="increment-button" onclick="update_task_progress( '{{service.id}}','{{task.id}}' )">+</button>
</li> </li>
@@ -14,3 +17,4 @@
</ul> </ul>
</div> </div>
{% endfor %} {% endfor %}