mirror of
https://github.com/hargata/lubelog.git
synced 2025-12-10 18:36:38 -06:00
Merge pull request #695 from hargata/Hargata/custom.widgets
Custom Widgets
This commit is contained in:
commit
ada0715343
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,3 +12,4 @@ config/userConfig.json
|
||||
CarCareTracker.csproj.user
|
||||
Properties/launchSettings.json
|
||||
data/cartracker-log.db
|
||||
data/widgets.html
|
||||
|
||||
@ -523,6 +523,27 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
return PartialView("_VehicleSelector", vehiclesStored);
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpGet]
|
||||
public IActionResult GetCustomWidgetEditor()
|
||||
{
|
||||
var customWidgetData = _fileHelper.GetWidgets();
|
||||
return PartialView("_WidgetEditor", customWidgetData);
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpPost]
|
||||
public IActionResult SaveCustomWidgets(string widgetsData)
|
||||
{
|
||||
var saveResult = _fileHelper.SaveWidgets(widgetsData);
|
||||
return Json(saveResult);
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpPost]
|
||||
public IActionResult DeleteCustomWidgets()
|
||||
{
|
||||
var deleteResult = _fileHelper.DeleteWidgets();
|
||||
return Json(deleteResult);
|
||||
}
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
|
||||
@ -22,6 +22,8 @@ namespace CarCareTracker.Controllers
|
||||
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||
var userConfig = _config.GetUserConfig(User);
|
||||
var viewModel = new ReportViewModel();
|
||||
//check if custom widgets are configured
|
||||
viewModel.CustomWidgetsConfigured = _fileHelper.WidgetsExist();
|
||||
//get totalCostMakeUp
|
||||
viewModel.CostMakeUpForVehicle = new CostMakeUpForVehicle
|
||||
{
|
||||
@ -528,5 +530,11 @@ namespace CarCareTracker.Controllers
|
||||
}).ToList();
|
||||
return PartialView("_GasCostByMonthReport", groupedRecord);
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult GetAdditionalWidgets()
|
||||
{
|
||||
var widgets = _fileHelper.GetWidgets();
|
||||
return PartialView("_ReportWidgets", widgets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,10 @@ namespace CarCareTracker.Helper
|
||||
int ClearTempFolder();
|
||||
int ClearUnlinkedThumbnails(List<string> linkedImages);
|
||||
int ClearUnlinkedDocuments(List<string> linkedDocuments);
|
||||
string GetWidgets();
|
||||
bool WidgetsExist();
|
||||
bool SaveWidgets(string widgetsData);
|
||||
bool DeleteWidgets();
|
||||
}
|
||||
public class FileHelper : IFileHelper
|
||||
{
|
||||
@ -100,6 +104,7 @@ namespace CarCareTracker.Helper
|
||||
var documentPath = Path.Combine(tempPath, "documents");
|
||||
var translationPath = Path.Combine(tempPath, "translations");
|
||||
var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
|
||||
var widgetPath = Path.Combine(tempPath, StaticHelper.AdditionalWidgetsPath);
|
||||
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath);
|
||||
if (Directory.Exists(imagePath))
|
||||
{
|
||||
@ -174,6 +179,10 @@ namespace CarCareTracker.Helper
|
||||
//data path will always exist as it is created on startup if not.
|
||||
File.Move(dataPath, StaticHelper.DbName, true);
|
||||
}
|
||||
if (File.Exists(widgetPath))
|
||||
{
|
||||
File.Move(widgetPath, StaticHelper.AdditionalWidgetsPath, true);
|
||||
}
|
||||
if (File.Exists(configPath))
|
||||
{
|
||||
//check if config folder exists.
|
||||
@ -223,6 +232,7 @@ namespace CarCareTracker.Helper
|
||||
var documentPath = Path.Combine(_webEnv.WebRootPath, "documents");
|
||||
var translationPath = Path.Combine(_webEnv.WebRootPath, "translations");
|
||||
var dataPath = StaticHelper.DbName;
|
||||
var widgetPath = StaticHelper.AdditionalWidgetsPath;
|
||||
var configPath = StaticHelper.UserConfigPath;
|
||||
if (!Directory.Exists(tempPath))
|
||||
Directory.CreateDirectory(tempPath);
|
||||
@ -262,6 +272,12 @@ namespace CarCareTracker.Helper
|
||||
Directory.CreateDirectory(newPath);
|
||||
File.Copy(dataPath, $"{newPath}/{Path.GetFileName(dataPath)}");
|
||||
}
|
||||
if (File.Exists(widgetPath))
|
||||
{
|
||||
var newPath = Path.Combine(tempPath, "data");
|
||||
Directory.CreateDirectory(newPath);
|
||||
File.Copy(widgetPath, $"{newPath}/{Path.GetFileName(widgetPath)}");
|
||||
}
|
||||
if (File.Exists(configPath))
|
||||
{
|
||||
var newPath = Path.Combine(tempPath, "config");
|
||||
@ -323,12 +339,20 @@ namespace CarCareTracker.Helper
|
||||
var tempPath = GetFullFilePath("temp", false);
|
||||
if (Directory.Exists(tempPath))
|
||||
{
|
||||
//delete files
|
||||
var files = Directory.GetFiles(tempPath);
|
||||
foreach (var file in files)
|
||||
{
|
||||
File.Delete(file);
|
||||
filesDeleted++;
|
||||
}
|
||||
//delete folders
|
||||
var folders = Directory.GetDirectories(tempPath);
|
||||
foreach(var folder in folders)
|
||||
{
|
||||
Directory.Delete(folder, true);
|
||||
filesDeleted++;
|
||||
}
|
||||
}
|
||||
return filesDeleted;
|
||||
}
|
||||
@ -368,5 +392,57 @@ namespace CarCareTracker.Helper
|
||||
}
|
||||
return filesDeleted;
|
||||
}
|
||||
public string GetWidgets()
|
||||
{
|
||||
if (File.Exists(StaticHelper.AdditionalWidgetsPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
//read file
|
||||
var widgets = File.ReadAllText(StaticHelper.AdditionalWidgetsPath);
|
||||
return widgets;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
public bool WidgetsExist()
|
||||
{
|
||||
return File.Exists(StaticHelper.AdditionalWidgetsPath);
|
||||
}
|
||||
public bool SaveWidgets(string widgetsData)
|
||||
{
|
||||
try
|
||||
{
|
||||
//Delete Widgets if exists
|
||||
DeleteWidgets();
|
||||
File.WriteAllText(StaticHelper.AdditionalWidgetsPath, widgetsData);
|
||||
return true;
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteWidgets()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(StaticHelper.AdditionalWidgetsPath))
|
||||
{
|
||||
File.Delete(StaticHelper.AdditionalWidgetsPath);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ namespace CarCareTracker.Helper
|
||||
public static string VersionNumber = "1.4.0";
|
||||
public static string DbName = "data/cartracker.db";
|
||||
public static string UserConfigPath = "config/userConfig.json";
|
||||
public static string AdditionalWidgetsPath = "data/widgets.html";
|
||||
public static string GenericErrorMessage = "An error occurred, please try again later";
|
||||
public static string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
|
||||
public static string DefaultAllowedFileExtensions = ".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx";
|
||||
|
||||
@ -8,5 +8,6 @@
|
||||
public ReminderMakeUpForVehicle ReminderMakeUpForVehicle { get; set; } = new ReminderMakeUpForVehicle();
|
||||
public List<int> Years { get; set; } = new List<int>();
|
||||
public List<UserCollaborator> Collaborators { get; set; } = new List<UserCollaborator>();
|
||||
public bool CustomWidgetsConfigured { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -335,6 +335,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" data-bs-focus="false" id="customWidgetModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="customWidgetModalContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" data-bs-focus="false" id="tabReorderModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="tabReorderModalContent">
|
||||
|
||||
28
Views/Home/_WidgetEditor.cshtml
Normal file
28
Views/Home/_WidgetEditor.cshtml
Normal file
@ -0,0 +1,28 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model string
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="widgetEditorModalLabel">@translator.Translate(userLanguage, "Custom Widgets Editor")</h5>
|
||||
<button type="button" class="btn-close" onclick="hideCustomWidgets()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" onkeydown="handleEnter(this)">
|
||||
<form class="form-inline">
|
||||
<div class="form-group" style="height:50vh; overflow-x:hidden; overflow-y:auto;">
|
||||
<div class="row">
|
||||
<div class="col-12" style="height:48vh;">
|
||||
<textarea id="widgetEditor" style="width:100%; height:100%;">@Model</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-danger me-auto" onclick="deleteCustomWidgets()">@translator.Translate(userLanguage, "Delete")</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideCustomWidgets()">@translator.Translate(userLanguage, "Cancel")</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveCustomWidgets()">@translator.Translate(userLanguage, "Save")</button>
|
||||
</div>
|
||||
@ -126,6 +126,12 @@
|
||||
<div class="d-grid">
|
||||
<button onclick="exportAttachments()" class="btn btn-secondary btn-md mt-1 mb-1">@translator.Translate(userLanguage, "Export Attachments")<i class="bi ms-2 bi-box-arrow-in-up-right"></i></button>
|
||||
</div>
|
||||
@if (Model.CustomWidgetsConfigured)
|
||||
{
|
||||
<div class="d-grid">
|
||||
<button onclick="loadCustomWidgets()" class="btn btn-secondary btn-md mt-1 mb-1">@translator.Translate(userLanguage, "Additional Widgets")<i class="bi ms-2 bi-box-arrow-in-up-right"></i></button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -153,6 +159,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (Model.CustomWidgetsConfigured)
|
||||
{
|
||||
<div class="modal fade" data-bs-focus="false" id="vehicleCustomWidgetsModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="vehicleCustomWidgetsModalContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div id="vehicleHistoryReport" class="showOnPrint"></div>
|
||||
|
||||
<script>
|
||||
|
||||
2
Views/Vehicle/_ReportWidgets.cshtml
Normal file
2
Views/Vehicle/_ReportWidgets.cshtml
Normal file
@ -0,0 +1,2 @@
|
||||
@model string
|
||||
@Html.Raw(Model)
|
||||
File diff suppressed because one or more lines are too long
@ -264,4 +264,13 @@ function loadGlobalSearchResult(recordId, recordType) {
|
||||
waitForElement('#planRecordModalContent', showEditPlanRecordModal, recordId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
function loadCustomWidgets() {
|
||||
$.get('/Vehicle/GetAdditionalWidgets', function (data) {
|
||||
$("#vehicleCustomWidgetsModalContent").html(data);
|
||||
$("#vehicleCustomWidgetsModal").modal('show');
|
||||
})
|
||||
}
|
||||
function hideCustomWidgetsModal() {
|
||||
$("#vehicleCustomWidgetsModal").modal('hide');
|
||||
}
|
||||
@ -351,4 +351,59 @@ function resetTabOrder() {
|
||||
$(elem).css('order', -1);
|
||||
})
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
function hideCustomWidgets() {
|
||||
$("#customWidgetModal").modal('hide');
|
||||
}
|
||||
function saveCustomWidgets() {
|
||||
$.post('/Home/SaveCustomWidgets', { widgetsData: $("#widgetEditor").val() }, function (data) {
|
||||
if (data) {
|
||||
successToast("Custom Widgets Saved!");
|
||||
updateSettings();
|
||||
} else {
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
})
|
||||
}
|
||||
function deleteCustomWidgets() {
|
||||
$.post('/Home/DeleteCustomWidgets', function (data) {
|
||||
if (data) {
|
||||
successToast("Custom Widgets Deleted!");
|
||||
updateSettings();
|
||||
} else {
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
})
|
||||
}
|
||||
function showCustomWidgets() {
|
||||
Swal.fire({
|
||||
title: 'Warning',
|
||||
icon: "warning",
|
||||
html: `
|
||||
<span>
|
||||
You are about to use the Custom Widgets Editor, this is a developer-focused feature that can lead to security vulnerabilities if you don't understand what you're doing.
|
||||
<br />Zero support will be provided from the developer(s) of LubeLogger regarding Custom Widgets, Read the Documentation.
|
||||
<br />By proceeding, you acknowledge that you are solely responsible for all consequences from utilizing the Custom Widgets Editor.
|
||||
<br />To proceed, enter 'acknowledge' into the text field below.
|
||||
</span>
|
||||
<input type="text" id="inputAcknowledge" class="swal2-input" placeholder="acknowledge" onkeydown="handleSwalEnter(event)">
|
||||
`,
|
||||
confirmButtonText: 'Proceed',
|
||||
focusConfirm: false,
|
||||
preConfirm: () => {
|
||||
const userAcknowledge = $("#inputAcknowledge").val();
|
||||
if (!userAcknowledge || userAcknowledge != 'acknowledge') {
|
||||
Swal.showValidationMessage(`Please acknowledge before proceeding.`)
|
||||
}
|
||||
return { userAcknowledge }
|
||||
},
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
$.get('/Home/GetCustomWidgetEditor', function (data) {
|
||||
$("#customWidgetModalContent").html(data);
|
||||
$("#customWidgetModal").modal('show');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user