Merge pull request #1218 from hargata/Hargata/1217

1.5.8 Changes
This commit is contained in:
Hargata Softworks 2026-01-25 11:10:58 -07:00 committed by GitHub
commit 4be59d793d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 171 additions and 49 deletions

View File

@ -14,7 +14,7 @@
<PackageReference Include="CsvHelper" Version="33.1.0" />
<PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="MailKit" Version="4.14.1" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.14.0" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.15.0" />
<PackageReference Include="Npgsql" Version="9.0.4" />
</ItemGroup>

View File

@ -6,6 +6,7 @@ using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using System.Text.Json;
namespace CarCareTracker.Controllers
@ -31,11 +32,12 @@ namespace CarCareTracker.Controllers
_config = config;
_httpClientFactory = httpClientFactory;
}
public IActionResult Index(string redirectURL = "")
public IActionResult Index(string redirectURL = "", string redirectURLBase64 = "")
{
var remoteAuthConfig = _config.GetOpenIDConfig();
if (remoteAuthConfig.DisableRegularLogin && !string.IsNullOrWhiteSpace(remoteAuthConfig.LogOutURL))
{
//OIDC login flow
var generatedState = Guid.NewGuid().ToString().Substring(0, 8);
remoteAuthConfig.State = generatedState;
var pkceKeyPair = _loginLogic.GetPKCEChallengeCode();
@ -48,9 +50,25 @@ namespace CarCareTracker.Controllers
{
Response.Cookies.Append("OIDC_VERIFIER", pkceKeyPair.Key, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
}
if (!string.IsNullOrWhiteSpace(redirectURLBase64))
{
Response.Cookies.Append("OIDC_REDIRECTURL", redirectURLBase64, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
}
var remoteAuthURL = remoteAuthConfig.RemoteAuthURL;
return Redirect(remoteAuthURL);
}
//regular login flow
if (!string.IsNullOrWhiteSpace(redirectURLBase64))
{
try
{
redirectURL = Encoding.UTF8.GetString(Convert.FromBase64String(redirectURLBase64));
}
catch
{
redirectURL = string.Empty;
}
}
return View(model: redirectURL);
}
public IActionResult Registration(string token = "", string email = "")
@ -79,7 +97,7 @@ namespace CarCareTracker.Controllers
};
return View(viewModel);
}
public IActionResult GetRemoteLoginLink()
public IActionResult GetRemoteLoginLink(string redirectURLBase64)
{
var remoteAuthConfig = _config.GetOpenIDConfig();
var generatedState = Guid.NewGuid().ToString().Substring(0, 8);
@ -94,11 +112,20 @@ namespace CarCareTracker.Controllers
{
Response.Cookies.Append("OIDC_VERIFIER", pkceKeyPair.Key, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
}
if (!string.IsNullOrWhiteSpace(redirectURLBase64))
{
Response.Cookies.Append("OIDC_REDIRECTURL", redirectURLBase64, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
}
var remoteAuthURL = remoteAuthConfig.RemoteAuthURL;
return Json(remoteAuthURL);
}
public async Task<IActionResult> RemoteAuth(string code, string state = "")
{
var storedRedirectURL = Request.Cookies["OIDC_REDIRECTURL"];
if (!string.IsNullOrWhiteSpace(storedRedirectURL))
{
Response.Cookies.Delete("OIDC_REDIRECTURL");
}
try
{
if (!string.IsNullOrWhiteSpace(code))
@ -221,6 +248,17 @@ namespace CarCareTracker.Controllers
var serializedCookie = JsonSerializer.Serialize(authCookie);
var encryptedCookie = _dataProtector.Protect(serializedCookie);
Response.Cookies.Append(StaticHelper.LoginCookieName, encryptedCookie, new CookieOptions { Expires = new DateTimeOffset(authCookie.ExpiresOn) });
if (!string.IsNullOrWhiteSpace(storedRedirectURL))
{
try
{
var decodedRedirectURL = Encoding.UTF8.GetString(Convert.FromBase64String(storedRedirectURL));
return new RedirectResult(decodedRedirectURL);
} catch
{
return new RedirectResult("/Home");
}
}
return new RedirectResult("/Home");
}
else
@ -264,6 +302,12 @@ namespace CarCareTracker.Controllers
public async Task<IActionResult> RemoteAuthDebug(string code, string state = "")
{
List<OperationResponse> results = new List<OperationResponse>();
var storedRedirectURL = Request.Cookies["OIDC_REDIRECTURL"];
if (!string.IsNullOrWhiteSpace(storedRedirectURL))
{
Response.Cookies.Delete("OIDC_REDIRECTURL");
results.Add(OperationResponse.Succeed($"Redirect URL Configured: {storedRedirectURL}"));
}
try
{
if (!string.IsNullOrWhiteSpace(code))

View File

@ -699,7 +699,7 @@ namespace CarCareTracker.Controllers
break;
}
}
return Json(result);
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
public IActionResult MoveRecords(List<int> recordIds, ImportMode source, ImportMode destination)
{

View File

@ -424,6 +424,7 @@ namespace CarCareTracker.Helper
UseThreeDecimalGasConsumption = CheckBool(CheckString(nameof(UserConfig.UseThreeDecimalGasConsumption)), true),
EnableAutoReminderRefresh = CheckBool(CheckString(nameof(UserConfig.EnableAutoReminderRefresh))),
EnableAutoOdometerInsert = CheckBool(CheckString(nameof(UserConfig.EnableAutoOdometerInsert))),
EnableAutoFillOdometer = CheckBool(CheckString(nameof(UserConfig.EnableAutoFillOdometer))),
PreferredGasMileageUnit = CheckString(nameof(UserConfig.PreferredGasMileageUnit)),
PreferredGasUnit = CheckString(nameof(UserConfig.PreferredGasUnit)),
UseUnitForFuelCost = CheckBool(CheckString(nameof(UserConfig.UseUnitForFuelCost))),

View File

@ -14,7 +14,7 @@ namespace CarCareTracker.Helper
/// </summary>
public static class StaticHelper
{
public const string VersionNumber = "1.5.7";
public const string VersionNumber = "1.5.8";
public const string DbName = "data/cartracker.db";
public const string UserConfigPath = "data/config/userConfig.json";
public const string ServerConfigPath = "data/config/serverConfig.json";

View File

@ -163,7 +163,8 @@ namespace CarCareTracker.Middleware
}
if (Request.Path.Value == "/Vehicle/Index" && Request.QueryString.HasValue)
{
Response.Redirect($"/Login/Index?redirectURL={Request.Path.Value}{Request.QueryString.Value}");
var encodedRedirectUrl = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Request.Path.Value}{Request.QueryString.Value}"));
Response.Redirect($"/Login/Index?redirectURLBase64={encodedRedirectUrl}");
} else
{
Response.Redirect("/Login/Index");

View File

@ -15,6 +15,7 @@
public bool UseMarkDownOnSavedNotes { get; set; }
public bool EnableAutoReminderRefresh { get; set; }
public bool EnableAutoOdometerInsert { get; set; }
public bool EnableAutoFillOdometer { get; set; }
public bool EnableShopSupplies { get; set; }
public bool EnableExtraFieldColumns { get; set; }
public bool HideSoldVehicles { get; set; }

View File

@ -64,11 +64,11 @@
<hr />
<div class="form-check form-switch">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useMPG" checked="@Model.UserConfig.UseMPG">
<label class="form-check-label" for="useMPG">@translator.Translate(userLanguage, "Use Imperial Calculation for Fuel Economy Calculations(MPG)")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "This Will Also Change Units to Miles and Gallons")</small></label>
<label class="form-check-label" for="useMPG">@translator.Translate(userLanguage, "Use Imperial Calculation for Fuel Economy Calculations(MPG)")<span class="ms-2 settingsToolTip" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="@translator.Translate(userLanguage, "This Will Also Change Units to Miles and Gallons")"><i class="bi bi-question-circle"></i></span></label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useUKMPG" checked="@Model.UserConfig.UseUKMPG">
<label class="form-check-label" for="useUKMPG">@translator.Translate(userLanguage, "Use UK MPG Calculation")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Input Gas Consumption in Liters, it will be converted to UK Gals for MPG Calculation")</small></label>
<label class="form-check-label" for="useUKMPG">@translator.Translate(userLanguage, "Use UK MPG Calculation")<span class="ms-2 settingsToolTip" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="@translator.Translate(userLanguage, "Input Gas Consumption in Liters, it will be converted to UK Gals for MPG Calculation")"><i class="bi bi-question-circle"></i></span></label>
</div>
<div>
<div class="form-check form-switch form-check-inline">
@ -85,13 +85,17 @@
<label class="form-check-label" for="useThreeDecimalGasConsumption">@translator.Translate(userLanguage, "Use Three Decimals For Fuel Consumption")</label>
</div>
<hr />
<div class="form-check form-switch">
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableAutoReminderRefresh" checked="@Model.UserConfig.EnableAutoReminderRefresh">
<label class="form-check-label" for="enableAutoReminderRefresh">@translator.Translate(userLanguage, "Auto Refresh Lapsed Recurring Reminders")</label>
</div>
<div class="form-check form-switch">
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableAutoOdometerInsert" checked="@Model.UserConfig.EnableAutoOdometerInsert">
<label class="form-check-label" for="enableAutoOdometerInsert">@translator.Translate(userLanguage, "Auto Insert Odometer Records")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Only when Adding Service/Repair/Upgrade/Fuel Record or Completing a Plan")</small></label>
<label class="form-check-label" for="enableAutoOdometerInsert">@translator.Translate(userLanguage, "Auto Insert Odometer Records")<span class="ms-2 settingsToolTip" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="@translator.Translate(userLanguage, "Only when Adding Service/Repair/Upgrade/Fuel Record or Completing a Plan")"><i class="bi bi-question-circle"></i></span></label>
</div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableAutoFillOdometer" checked="@Model.UserConfig.EnableAutoFillOdometer">
<label class="form-check-label" for="enableAutoFillOdometer">@translator.Translate(userLanguage, "Auto Fill Odometer Input")<span class="ms-2 settingsToolTip" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="@translator.Translate(userLanguage, "Only when Adding Service/Repair/Upgrade/Fuel Record or Completing a Plan")"><i class="bi bi-question-circle"></i></span></label>
</div>
<hr />
<div>
@ -451,4 +455,5 @@
}
}
loadSponsors();
loadTooltips();
</script>

View File

@ -113,4 +113,10 @@
function getCollisionRecordModelData() {
return { id: @Model.Id}
}
</script>
</script>
@if (isNew && userConfig.EnableAutoFillOdometer)
{
<script>
setLastOdometer('collisionRecordMileage');
</script>
}

View File

@ -131,4 +131,10 @@
function getGasRecordModelData(){
return { id: @Model.GasRecord.Id}
}
</script>
</script>
@if (isNew && userConfig.EnableAutoFillOdometer)
{
<script>
setLastOdometer('gasRecordMileage');
</script>
}

View File

@ -132,4 +132,10 @@
{
@:recurringReminderRecordId.push(@reminderRecordId);
}
</script>
</script>
@if (userConfig.EnableAutoFillOdometer)
{
<script>
setLastOdometer('inspectionRecordMileage');
</script>
}

View File

@ -10,6 +10,7 @@
var inProgressItems = Model.Where(x => x.Progress == PlanProgress.InProgress).OrderBy(x => x.Priority);
var testingItems = Model.Where(x => x.Progress == PlanProgress.Testing).OrderBy(x => x.Priority);
var doneItems = Model.Where(x => x.Progress == PlanProgress.Done).OrderBy(x => x.Priority);
var autoFillOdometer = userConfig.EnableAutoFillOdometer;
}
@model List<PlanRecord>
<div class="row">
@ -131,4 +132,11 @@
<li><a class="dropdown-item context-menu-move move-header context-menu-print-tab-sticker" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="context-menu-move move-header dropdown-divider"></li>
<li><a class="dropdown-item context-menu-move move-header text-danger context-menu-delete" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
</ul>
</ul>
<script>
function getPlanUserConfig(){
return {
autoFillOdometer: "@autoFillOdometer" == "True"
}
}
</script>

View File

@ -113,4 +113,10 @@
function getServiceRecordModelData(){
return {id: @Model.Id}
}
</script>
</script>
@if (isNew && userConfig.EnableAutoFillOdometer)
{
<script>
setLastOdometer('serviceRecordMileage');
</script>
}

View File

@ -113,4 +113,10 @@
function getUpgradeRecordModelData() {
return { id: @Model.Id}
}
</script>
</script>
@if (isNew && userConfig.EnableAutoFillOdometer)
{
<script>
setLastOdometer('upgradeRecordMileage');
</script>
}

View File

@ -30,6 +30,6 @@
var uploadedFiles = [];
@foreach (UploadedFiles filesUploaded in Model)
{
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
@:uploadedFiles.push({ name: decodeHTMLEntities("@filesUploaded.Name"), location: "@filesUploaded.Location" });
}
</script>

View File

@ -18,6 +18,7 @@
"AutomaticDecimalFormat": false,
"EnableAutoReminderRefresh": false,
"EnableAutoOdometerInsert": false,
"EnableAutoFillOdometer": false,
"EnableShopSupplies": false,
"ShowCalendar": true,
"ShowVehicleThumbnail": true,

View File

@ -1,9 +1,22 @@
---
services:
postgres:
image: postgres:18
restart: unless-stopped
environment:
POSTGRES_USER: "lubelogger"
POSTGRES_PASSWORD: "lubepass"
POSTGRES_DB: "lubelogger"
volumes:
- postgres:/var/lib/postgresql/
- /etc/localtime:/etc/localtime:ro
app:
image: ghcr.io/hargata/lubelogger:latest
restart: unless-stopped
environment:
POSTGRES_CONNECTION: "Host=postgres:5432;Username=lubelogger;Password=lubepass;Database=lubelogger;"
# volumes used to keep data persistent
volumes:
- data:/App/data
@ -12,18 +25,6 @@ services:
ports:
- 8080:8080
postgres:
image: postgres:14
restart: unless-stopped
environment:
POSTGRES_USER: "lubelogger"
POSTGRES_PASSWORD: "lubepass"
POSTGRES_DB: "lubelogger"
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
- postgres:/var/lib/postgresql/data
- /etc/localtime:/etc/localtime:ro
volumes:
data:
keys:

View File

@ -1,6 +0,0 @@
DO $$
BEGIN
IF NOT EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'app') THEN
CREATE SCHEMA app;
END IF;
END $$;

View File

@ -702,4 +702,8 @@ html[data-bs-theme="dark"] .swal2-validation-message {
.nav-item-more .dropdown-menu.show{
display:flex;
flex-direction:column;
}
.settingsToolTip {
color: var(--bs-link-color);
}

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
function performLogin() {
var userName = $("#inputUserName").val();
var userPassword = $("#inputUserPassword").val();
var isPersistent = $("#inputPersistent").is(":checked");
let userName = $("#inputUserName").val();
let userPassword = $("#inputUserPassword").val();
let isPersistent = $("#inputPersistent").is(":checked");
$.post('/Login/Login', {userName: userName, password: userPassword, isPersistent: isPersistent}, function (data) {
if (data) {
//check for redirectURL
@ -17,10 +17,10 @@
})
}
function performRegistration() {
var token = $("#inputToken").val();
var userName = $("#inputUserName").val();
var userPassword = $("#inputUserPassword").val();
var userEmail = $("#inputEmail").val();
let token = $("#inputToken").val();
let userName = $("#inputUserName").val();
let userPassword = $("#inputUserPassword").val();
let userEmail = $("#inputEmail").val();
$.post('/Login/Register', { userName: userName, password: userPassword, token: token, emailAddress: userEmail }, function (data) {
if (data.success) {
successToast(data.message);
@ -31,7 +31,7 @@ function performRegistration() {
});
}
function requestPasswordReset() {
var userName = $("#inputUserName").val();
let userName = $("#inputUserName").val();
$.post('/Login/RequestResetPassword', { userName: userName }, function (data) {
if (data.success) {
successToast(data.message);
@ -42,9 +42,9 @@ function requestPasswordReset() {
})
}
function performPasswordReset() {
var token = $("#inputToken").val();
var userPassword = $("#inputUserPassword").val();
var userEmail = $("#inputEmail").val();
let token = $("#inputToken").val();
let userPassword = $("#inputUserPassword").val();
let userEmail = $("#inputEmail").val();
$.post('/Login/PerformPasswordReset', { password: userPassword, token: token, emailAddress: userEmail }, function (data) {
if (data.success) {
successToast(data.message);
@ -56,7 +56,12 @@ function performPasswordReset() {
}
function remoteLogin() {
$.get('/Login/GetRemoteLoginLink', function (data) {
let currentParams = new URLSearchParams(window.location.search);
let redirectUrl = currentParams.get('redirectURLBase64');
if (redirectUrl == null) {
redirectUrl = '';
}
$.get(`/Login/GetRemoteLoginLink?redirectURLBase64=${redirectUrl}`, { redirectURLBase64: redirectUrl }, function (data) {
if (data) {
window.location.href = data;
}

View File

@ -306,6 +306,11 @@ function updatePlanRecordProgress(newProgress) {
}
return { odometer }
},
didOpen: () => {
if (getPlanUserConfig().autoFillOdometer) {
setLastOdometer('inputOdometer');
}
}
}).then(function (result) {
if (result.isConfirmed) {
//Odometer Adjustments

View File

@ -57,6 +57,7 @@ function updateSettings() {
useMarkDownOnSavedNotes: $("#useMarkDownOnSavedNotes").is(":checked"),
enableAutoReminderRefresh: $("#enableAutoReminderRefresh").is(":checked"),
enableAutoOdometerInsert: $("#enableAutoOdometerInsert").is(":checked"),
enableAutoFillOdometer: $("#enableAutoFillOdometer").is(":checked"),
enableShopSupplies: $("#enableShopSupplies").is(":checked"),
showCalendar: $("#showCalendar").is(":checked"),
showVehicleThumbnail: $("#showVehicleThumbnail").is(":checked"),
@ -473,4 +474,13 @@ function showCustomWidgets() {
});
}
});
}
function loadTooltips() {
$('.settingsToolTip').map((index, elem) => {
new bootstrap.Tooltip(elem);
})
$('.settingsToolTip').on('click', (event) => {
event.stopPropagation();
event.preventDefault();
})
}

View File

@ -879,4 +879,12 @@ function getDefaultTabName() {
return 'equipment';
break;
}
}
function setLastOdometer(mileageInputId) {
$.get(`/Vehicle/GetMaxMileage?vehicleId=${GetVehicleId().vehicleId}`, function (data) {
if (isNaN(data)) {
return;
}
$(`#${mileageInputId}`).val(data);
});
}

View File

@ -11,6 +11,7 @@
var rx_space = /\t|\r|\uf8ff/g;
var rx_escape = /\\([\\\|`*_{}\[\]()#+\-~])/g;
var rx_hr = /^([*\-=_] *){3,}$/gm;
var rx_lb = /\s{2}\r?\n/gm;
var rx_blockquote = /\n *&gt; *([^]*?)(?=(\n|$){2})/g;
var rx_list = /\n( *)(?:[*\-+]|((\d+)|([a-z])|[A-Z])[.)]) +([^]*?)(?=(\n|$){2})/g;
var rx_listjoin = /<\/(ol|ul)>\n\n<\1>/g;
@ -122,6 +123,9 @@
// paragraph
replace(rx_para, function(all, content) { return element('p', unesc(highlight(content))) });
// line breaks
replace(rx_lb, '<br />');
// stash
replace(rx_stash, function(all) { return stash[parseInt(all)] });