Merge pull request #916 from hargata/Hargata/oidc.userinfo
V1.4.7 Changes
This commit is contained in:
@@ -256,6 +256,15 @@ namespace CarCareTracker.Controllers
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Progress cannot be set to Done."));
|
||||
}
|
||||
//hardening - turns null values for List types into empty lists.
|
||||
if (input.Files == null)
|
||||
{
|
||||
input.Files = new List<UploadedFiles>();
|
||||
}
|
||||
if (input.ExtraFields == null)
|
||||
{
|
||||
input.ExtraFields = new List<ExtraField>();
|
||||
}
|
||||
try
|
||||
{
|
||||
var planRecord = new PlanRecord()
|
||||
@@ -346,6 +355,14 @@ namespace CarCareTracker.Controllers
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Progress cannot be set to Done."));
|
||||
}
|
||||
if (input.Files == null)
|
||||
{
|
||||
input.Files = new List<UploadedFiles>();
|
||||
}
|
||||
if (input.ExtraFields == null)
|
||||
{
|
||||
input.ExtraFields = new List<ExtraField>();
|
||||
}
|
||||
try
|
||||
{
|
||||
//retrieve existing record
|
||||
@@ -429,6 +446,14 @@ namespace CarCareTracker.Controllers
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Date, Description, Odometer, and Cost cannot be empty."));
|
||||
}
|
||||
if (input.Files == null)
|
||||
{
|
||||
input.Files = new List<UploadedFiles>();
|
||||
}
|
||||
if (input.ExtraFields == null)
|
||||
{
|
||||
input.ExtraFields = new List<ExtraField>();
|
||||
}
|
||||
try
|
||||
{
|
||||
var serviceRecord = new ServiceRecord()
|
||||
@@ -509,6 +534,14 @@ namespace CarCareTracker.Controllers
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Id, Date, Description, Odometer, and Cost cannot be empty."));
|
||||
}
|
||||
if (input.Files == null)
|
||||
{
|
||||
input.Files = new List<UploadedFiles>();
|
||||
}
|
||||
if (input.ExtraFields == null)
|
||||
{
|
||||
input.ExtraFields = new List<ExtraField>();
|
||||
}
|
||||
try
|
||||
{
|
||||
//retrieve existing record
|
||||
@@ -591,6 +624,14 @@ namespace CarCareTracker.Controllers
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Date, Description, Odometer, and Cost cannot be empty."));
|
||||
}
|
||||
if (input.Files == null)
|
||||
{
|
||||
input.Files = new List<UploadedFiles>();
|
||||
}
|
||||
if (input.ExtraFields == null)
|
||||
{
|
||||
input.ExtraFields = new List<ExtraField>();
|
||||
}
|
||||
try
|
||||
{
|
||||
var repairRecord = new CollisionRecord()
|
||||
@@ -672,6 +713,14 @@ namespace CarCareTracker.Controllers
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Id, Date, Description, Odometer, and Cost cannot be empty."));
|
||||
}
|
||||
if (input.Files == null)
|
||||
{
|
||||
input.Files = new List<UploadedFiles>();
|
||||
}
|
||||
if (input.ExtraFields == null)
|
||||
{
|
||||
input.ExtraFields = new List<ExtraField>();
|
||||
}
|
||||
try
|
||||
{
|
||||
//retrieve existing record
|
||||
@@ -755,6 +804,14 @@ namespace CarCareTracker.Controllers
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Date, Description, Odometer, and Cost cannot be empty."));
|
||||
}
|
||||
if (input.Files == null)
|
||||
{
|
||||
input.Files = new List<UploadedFiles>();
|
||||
}
|
||||
if (input.ExtraFields == null)
|
||||
{
|
||||
input.ExtraFields = new List<ExtraField>();
|
||||
}
|
||||
try
|
||||
{
|
||||
var upgradeRecord = new UpgradeRecord()
|
||||
@@ -835,6 +892,14 @@ namespace CarCareTracker.Controllers
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Id, Date, Description, Odometer, and Cost cannot be empty."));
|
||||
}
|
||||
if (input.Files == null)
|
||||
{
|
||||
input.Files = new List<UploadedFiles>();
|
||||
}
|
||||
if (input.ExtraFields == null)
|
||||
{
|
||||
input.ExtraFields = new List<ExtraField>();
|
||||
}
|
||||
try
|
||||
{
|
||||
//retrieve existing record
|
||||
@@ -951,6 +1016,14 @@ namespace CarCareTracker.Controllers
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Date, Description, and Cost cannot be empty."));
|
||||
}
|
||||
if (input.Files == null)
|
||||
{
|
||||
input.Files = new List<UploadedFiles>();
|
||||
}
|
||||
if (input.ExtraFields == null)
|
||||
{
|
||||
input.ExtraFields = new List<ExtraField>();
|
||||
}
|
||||
try
|
||||
{
|
||||
var taxRecord = new TaxRecord()
|
||||
@@ -1014,6 +1087,14 @@ namespace CarCareTracker.Controllers
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Id, Date, Description, and Cost cannot be empty."));
|
||||
}
|
||||
if (input.Files == null)
|
||||
{
|
||||
input.Files = new List<UploadedFiles>();
|
||||
}
|
||||
if (input.ExtraFields == null)
|
||||
{
|
||||
input.ExtraFields = new List<ExtraField>();
|
||||
}
|
||||
try
|
||||
{
|
||||
//retrieve existing record
|
||||
@@ -1113,6 +1194,14 @@ namespace CarCareTracker.Controllers
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Date, and Odometer cannot be empty."));
|
||||
}
|
||||
if (input.Files == null)
|
||||
{
|
||||
input.Files = new List<UploadedFiles>();
|
||||
}
|
||||
if (input.ExtraFields == null)
|
||||
{
|
||||
input.ExtraFields = new List<ExtraField>();
|
||||
}
|
||||
try
|
||||
{
|
||||
var odometerRecord = new OdometerRecord()
|
||||
@@ -1174,6 +1263,14 @@ namespace CarCareTracker.Controllers
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Id, Date, Initial Odometer, and Odometer cannot be empty."));
|
||||
}
|
||||
if (input.Files == null)
|
||||
{
|
||||
input.Files = new List<UploadedFiles>();
|
||||
}
|
||||
if (input.ExtraFields == null)
|
||||
{
|
||||
input.ExtraFields = new List<ExtraField>();
|
||||
}
|
||||
try
|
||||
{
|
||||
//retrieve existing record
|
||||
@@ -1273,6 +1370,14 @@ namespace CarCareTracker.Controllers
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Date, Odometer, FuelConsumed, IsFillToFull, MissedFuelUp, and Cost cannot be empty."));
|
||||
}
|
||||
if (input.Files == null)
|
||||
{
|
||||
input.Files = new List<UploadedFiles>();
|
||||
}
|
||||
if (input.ExtraFields == null)
|
||||
{
|
||||
input.ExtraFields = new List<ExtraField>();
|
||||
}
|
||||
try
|
||||
{
|
||||
var gasRecord = new GasRecord()
|
||||
@@ -1352,6 +1457,14 @@ namespace CarCareTracker.Controllers
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Id, Date, Odometer, FuelConsumed, IsFillToFull, MissedFuelUp, and Cost cannot be empty."));
|
||||
}
|
||||
if (input.Files == null)
|
||||
{
|
||||
input.Files = new List<UploadedFiles>();
|
||||
}
|
||||
if (input.ExtraFields == null)
|
||||
{
|
||||
input.ExtraFields = new List<ExtraField>();
|
||||
}
|
||||
try
|
||||
{
|
||||
//retrieve existing record
|
||||
@@ -1391,6 +1504,7 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#region ReminderRecord
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpGet]
|
||||
[Route("/api/vehicle/reminders")]
|
||||
@@ -1403,7 +1517,7 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
var currentMileage = _vehicleLogic.GetMaxMileage(vehicleId);
|
||||
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
|
||||
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).Select(x=> new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString()});
|
||||
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).Select(x=> new ReminderExportModel { Id = x.Id.ToString(), Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString(), Tags = string.Join(' ', x.Tags) });
|
||||
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
|
||||
{
|
||||
return Json(results, StaticHelper.GetInvariantOption());
|
||||
@@ -1413,6 +1527,183 @@ namespace CarCareTracker.Controllers
|
||||
return Json(results);
|
||||
}
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpPost]
|
||||
[Route("/api/vehicle/reminders/add")]
|
||||
[Consumes("application/json")]
|
||||
public IActionResult AddReminderRecordJson(int vehicleId, [FromBody] ReminderExportModel input) => AddReminderRecord(vehicleId, input);
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpPost]
|
||||
[Route("/api/vehicle/reminders/add")]
|
||||
public IActionResult AddReminderRecord(int vehicleId, ReminderExportModel input)
|
||||
{
|
||||
if (vehicleId == default)
|
||||
{
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(input.Description) ||
|
||||
string.IsNullOrWhiteSpace(input.Metric))
|
||||
{
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Description and Metric cannot be empty."));
|
||||
}
|
||||
bool validMetric = Enum.TryParse(input.Metric, out ReminderMetric parsedMetric);
|
||||
bool validDate = DateTime.TryParse(input.DueDate, out DateTime parsedDate);
|
||||
bool validOdometer = int.TryParse(input.DueOdometer, out int parsedOdometer);
|
||||
if (!validMetric)
|
||||
{
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, values for Metric(Date, Odometer, Both) is invalid."));
|
||||
}
|
||||
//validate metrics
|
||||
switch (parsedMetric)
|
||||
{
|
||||
case ReminderMetric.Both:
|
||||
//validate due date and odometer
|
||||
if (!validDate || !validOdometer)
|
||||
{
|
||||
return Json(OperationResponse.Failed("Input object invalid, DueDate and DueOdometer must be valid if Metric is Both"));
|
||||
}
|
||||
break;
|
||||
case ReminderMetric.Date:
|
||||
if (!validDate)
|
||||
{
|
||||
return Json(OperationResponse.Failed("Input object invalid, DueDate must be valid if Metric is Date"));
|
||||
}
|
||||
break;
|
||||
case ReminderMetric.Odometer:
|
||||
if (!validOdometer)
|
||||
{
|
||||
return Json(OperationResponse.Failed("Input object invalid, DueOdometer must be valid if Metric is Odometer"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
try
|
||||
{
|
||||
var reminderRecord = new ReminderRecord()
|
||||
{
|
||||
VehicleId = vehicleId,
|
||||
Description = input.Description,
|
||||
Mileage = parsedOdometer,
|
||||
Date = parsedDate,
|
||||
Metric = parsedMetric,
|
||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
|
||||
};
|
||||
_reminderRecordDataAccess.SaveReminderRecordToVehicle(reminderRecord);
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(reminderRecord, "reminderrecord.add.api", User.Identity.Name));
|
||||
return Json(OperationResponse.Succeed("Reminder Record Added"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Response.StatusCode = 500;
|
||||
return Json(OperationResponse.Failed(ex.Message));
|
||||
}
|
||||
}
|
||||
[HttpPut]
|
||||
[Route("/api/vehicle/reminders/update")]
|
||||
[Consumes("application/json")]
|
||||
public IActionResult UpdateReminderRecordJson([FromBody] ReminderExportModel input) => UpdateReminderRecord(input);
|
||||
[HttpPut]
|
||||
[Route("/api/vehicle/reminders/update")]
|
||||
public IActionResult UpdateReminderRecord(ReminderExportModel input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input.Id) ||
|
||||
string.IsNullOrWhiteSpace(input.Description) ||
|
||||
string.IsNullOrWhiteSpace(input.Metric))
|
||||
{
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, Id, Description and Metric cannot be empty."));
|
||||
}
|
||||
bool validMetric = Enum.TryParse(input.Metric, out ReminderMetric parsedMetric);
|
||||
bool validDate = DateTime.TryParse(input.DueDate, out DateTime parsedDate);
|
||||
bool validOdometer = int.TryParse(input.DueOdometer, out int parsedOdometer);
|
||||
if (!validMetric)
|
||||
{
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Input object invalid, values for Metric(Date, Odometer, Both) is invalid."));
|
||||
}
|
||||
//validate metrics
|
||||
switch (parsedMetric)
|
||||
{
|
||||
case ReminderMetric.Both:
|
||||
//validate due date and odometer
|
||||
if (!validDate || !validOdometer)
|
||||
{
|
||||
return Json(OperationResponse.Failed("Input object invalid, DueDate and DueOdometer must be valid if Metric is Both"));
|
||||
}
|
||||
break;
|
||||
case ReminderMetric.Date:
|
||||
if (!validDate)
|
||||
{
|
||||
return Json(OperationResponse.Failed("Input object invalid, DueDate must be valid if Metric is Date"));
|
||||
}
|
||||
break;
|
||||
case ReminderMetric.Odometer:
|
||||
if (!validOdometer)
|
||||
{
|
||||
return Json(OperationResponse.Failed("Input object invalid, DueOdometer must be valid if Metric is Odometer"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
try
|
||||
{
|
||||
//retrieve existing record
|
||||
var existingRecord = _reminderRecordDataAccess.GetReminderRecordById(int.Parse(input.Id));
|
||||
if (existingRecord != null && existingRecord.Id == int.Parse(input.Id))
|
||||
{
|
||||
//check if user has access to the vehicleId
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
Response.StatusCode = 401;
|
||||
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
|
||||
}
|
||||
existingRecord.Date = parsedDate;
|
||||
existingRecord.Mileage = parsedOdometer;
|
||||
existingRecord.Description = input.Description;
|
||||
existingRecord.Metric = parsedMetric;
|
||||
existingRecord.Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes;
|
||||
existingRecord.Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList();
|
||||
_reminderRecordDataAccess.SaveReminderRecordToVehicle(existingRecord);
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(existingRecord, "reminderrecord.update.api", User.Identity.Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Invalid Record Id"));
|
||||
}
|
||||
return Json(OperationResponse.Succeed("Reminder Record Updated"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Response.StatusCode = 500;
|
||||
return Json(OperationResponse.Failed(ex.Message));
|
||||
}
|
||||
}
|
||||
[HttpDelete]
|
||||
[Route("/api/vehicle/reminders/delete")]
|
||||
public IActionResult DeleteReminderRecord(int id)
|
||||
{
|
||||
var existingRecord = _reminderRecordDataAccess.GetReminderRecordById(id);
|
||||
if (existingRecord == null || existingRecord.Id == default)
|
||||
{
|
||||
Response.StatusCode = 400;
|
||||
return Json(OperationResponse.Failed("Invalid Record Id"));
|
||||
}
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
Response.StatusCode = 401;
|
||||
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
|
||||
}
|
||||
var result = _reminderRecordDataAccess.DeleteReminderRecordById(existingRecord.Id);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(existingRecord, "reminderrecord.delete.api", User.Identity.Name));
|
||||
}
|
||||
return Json(OperationResponse.Conditional(result, "Reminder Record Deleted"));
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("/api/calendar")]
|
||||
public IActionResult Calendar()
|
||||
@@ -1426,6 +1717,7 @@ namespace CarCareTracker.Controllers
|
||||
var calendarContent = StaticHelper.RemindersToCalendar(reminders);
|
||||
return File(calendarContent, "text/calendar");
|
||||
}
|
||||
#endregion
|
||||
[HttpPost]
|
||||
[Route("/api/documents/upload")]
|
||||
public IActionResult UploadDocument(List<IFormFile> documents)
|
||||
|
||||
@@ -49,21 +49,31 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
return View(model: redirectURL);
|
||||
}
|
||||
public IActionResult Registration()
|
||||
public IActionResult Registration(string token = "", string email = "")
|
||||
{
|
||||
if (_config.GetServerDisabledRegistration())
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
return View();
|
||||
var viewModel = new LoginModel
|
||||
{
|
||||
EmailAddress = string.IsNullOrWhiteSpace(email) ? string.Empty : email,
|
||||
Token = string.IsNullOrWhiteSpace(token) ? string.Empty : token
|
||||
};
|
||||
return View(viewModel);
|
||||
}
|
||||
public IActionResult ForgotPassword()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
public IActionResult ResetPassword()
|
||||
public IActionResult ResetPassword(string token = "", string email = "")
|
||||
{
|
||||
return View();
|
||||
var viewModel = new LoginModel
|
||||
{
|
||||
EmailAddress = string.IsNullOrWhiteSpace(email) ? string.Empty : email,
|
||||
Token = string.IsNullOrWhiteSpace(token) ? string.Empty : token
|
||||
};
|
||||
return View(viewModel);
|
||||
}
|
||||
public IActionResult GetRemoteLoginLink()
|
||||
{
|
||||
@@ -130,7 +140,9 @@ namespace CarCareTracker.Controllers
|
||||
Content = new FormUrlEncodedContent(httpParams)
|
||||
};
|
||||
var tokenResult = await httpClient.SendAsync(httpRequest).Result.Content.ReadAsStringAsync();
|
||||
var userJwt = JsonSerializer.Deserialize<OpenIDResult>(tokenResult)?.id_token ?? string.Empty;
|
||||
var decodedToken = JsonSerializer.Deserialize<OpenIDResult>(tokenResult);
|
||||
var userJwt = decodedToken?.id_token ?? string.Empty;
|
||||
var userAccessToken = decodedToken?.access_token ?? string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(userJwt))
|
||||
{
|
||||
//validate JWT token
|
||||
@@ -140,7 +152,23 @@ namespace CarCareTracker.Controllers
|
||||
if (parsedToken.Claims.Any(x => x.Type == "email"))
|
||||
{
|
||||
userEmailAddress = parsedToken.Claims.First(x => x.Type == "email").Value;
|
||||
} else
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(openIdConfig.UserInfoURL) && !string.IsNullOrWhiteSpace(userAccessToken))
|
||||
{
|
||||
//retrieve claims from userinfo endpoint if no email claims are returned within id_token
|
||||
var userInfoHttpRequest = new HttpRequestMessage(HttpMethod.Get, openIdConfig.UserInfoURL);
|
||||
userInfoHttpRequest.Headers.Add("Authorization", $"Bearer {userAccessToken}");
|
||||
var userInfoResult = await httpClient.SendAsync(userInfoHttpRequest).Result.Content.ReadAsStringAsync();
|
||||
var userInfo = JsonSerializer.Deserialize<OpenIDUserInfo>(userInfoResult);
|
||||
if (!string.IsNullOrWhiteSpace(userInfo?.email ?? string.Empty))
|
||||
{
|
||||
userEmailAddress = userInfo?.email ?? string.Empty;
|
||||
} else
|
||||
{
|
||||
_logger.LogError($"OpenID Provider did not provide an email claim via UserInfo endpoint");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var returnedClaims = parsedToken.Claims.Select(x => x.Type);
|
||||
_logger.LogError($"OpenID Provider did not provide an email claim, claims returned: {string.Join(",", returnedClaims)}");
|
||||
@@ -239,7 +267,9 @@ namespace CarCareTracker.Controllers
|
||||
Content = new FormUrlEncodedContent(httpParams)
|
||||
};
|
||||
var tokenResult = await httpClient.SendAsync(httpRequest).Result.Content.ReadAsStringAsync();
|
||||
var userJwt = JsonSerializer.Deserialize<OpenIDResult>(tokenResult)?.id_token ?? string.Empty;
|
||||
var decodedToken = JsonSerializer.Deserialize<OpenIDResult>(tokenResult);
|
||||
var userJwt = decodedToken?.id_token ?? string.Empty;
|
||||
var userAccessToken = decodedToken?.access_token ?? string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(userJwt))
|
||||
{
|
||||
results.Add(OperationResponse.Succeed($"Passed JWT Parsing - id_token: {userJwt}"));
|
||||
@@ -252,6 +282,22 @@ namespace CarCareTracker.Controllers
|
||||
userEmailAddress = parsedToken.Claims.First(x => x.Type == "email").Value;
|
||||
results.Add(OperationResponse.Succeed($"Passed Claim Validation - email"));
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(openIdConfig.UserInfoURL) && !string.IsNullOrWhiteSpace(userAccessToken))
|
||||
{
|
||||
//retrieve claims from userinfo endpoint if no email claims are returned within id_token
|
||||
var userInfoHttpRequest = new HttpRequestMessage(HttpMethod.Get, openIdConfig.UserInfoURL);
|
||||
userInfoHttpRequest.Headers.Add("Authorization", $"Bearer {userAccessToken}");
|
||||
var userInfoResult = await httpClient.SendAsync(userInfoHttpRequest).Result.Content.ReadAsStringAsync();
|
||||
var userInfo = JsonSerializer.Deserialize<OpenIDUserInfo>(userInfoResult);
|
||||
if (!string.IsNullOrWhiteSpace(userInfo?.email ?? string.Empty))
|
||||
{
|
||||
userEmailAddress = userInfo?.email ?? string.Empty;
|
||||
results.Add(OperationResponse.Succeed($"Passed Claim Validation - Retrieved email via UserInfo endpoint"));
|
||||
} else
|
||||
{
|
||||
results.Add(OperationResponse.Failed($"Failed Claim Validation - Unable to retrieve email via UserInfo endpoint: {openIdConfig.UserInfoURL} using access_token: {userAccessToken} - Received {userInfoResult}"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var returnedClaims = parsedToken.Claims.Select(x => x.Type);
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace CarCareTracker.Helper
|
||||
bool GetServerEnableShopSupplies();
|
||||
string GetServerPostgresConnection();
|
||||
string GetAllowedFileUploadExtensions();
|
||||
string GetServerDomain();
|
||||
bool DeleteUserConfig(int userId);
|
||||
bool GetInvariantApi();
|
||||
bool GetServerOpenRegistration();
|
||||
@@ -62,6 +63,11 @@ namespace CarCareTracker.Helper
|
||||
var motd = CheckString("LUBELOGGER_MOTD");
|
||||
return motd;
|
||||
}
|
||||
public string GetServerDomain()
|
||||
{
|
||||
var domain = CheckString("LUBELOGGER_DOMAIN");
|
||||
return domain;
|
||||
}
|
||||
public bool GetServerOpenRegistration()
|
||||
{
|
||||
return CheckBool(CheckString("LUBELOGGER_OPEN_REGISTRATION"));
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
private readonly MailConfig mailConfig;
|
||||
private readonly string serverLanguage;
|
||||
private readonly string serverDomain;
|
||||
private readonly IFileHelper _fileHelper;
|
||||
private readonly ITranslationHelper _translator;
|
||||
private readonly ILogger<MailHelper> _logger;
|
||||
@@ -29,6 +30,7 @@ namespace CarCareTracker.Helper
|
||||
//load mailConfig from Configuration
|
||||
mailConfig = config.GetMailConfig();
|
||||
serverLanguage = config.GetServerLanguage();
|
||||
serverDomain = config.GetServerDomain();
|
||||
_fileHelper = fileHelper;
|
||||
_translator = translationHelper;
|
||||
_logger = logger;
|
||||
@@ -43,7 +45,14 @@ namespace CarCareTracker.Helper
|
||||
return OperationResponse.Failed("Email Address or Token is invalid");
|
||||
}
|
||||
string emailSubject = _translator.Translate(serverLanguage, "Your Registration Token for LubeLogger");
|
||||
string emailBody = $"{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please complete your registration for LubeLogger using the token")}: {token}";
|
||||
string tokenHtml = token;
|
||||
if (!string.IsNullOrWhiteSpace(serverDomain))
|
||||
{
|
||||
string cleanedURL = serverDomain.EndsWith('/') ? serverDomain.TrimEnd('/') : serverDomain;
|
||||
//construct registration URL.
|
||||
tokenHtml = $"<a href='{cleanedURL}/Login/Registration?email={emailAddress}&token={token}' target='_blank'>{token}</a>";
|
||||
}
|
||||
string emailBody = $"<span>{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please complete your registration for LubeLogger using the token")}: {tokenHtml}</span>";
|
||||
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
|
||||
if (result)
|
||||
{
|
||||
@@ -64,7 +73,14 @@ namespace CarCareTracker.Helper
|
||||
return OperationResponse.Failed("Email Address or Token is invalid");
|
||||
}
|
||||
string emailSubject = _translator.Translate(serverLanguage, "Your Password Reset Token for LubeLogger");
|
||||
string emailBody = $"{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please reset your password for LubeLogger using the token")}: {token}";
|
||||
string tokenHtml = token;
|
||||
if (!string.IsNullOrWhiteSpace(serverDomain))
|
||||
{
|
||||
string cleanedURL = serverDomain.EndsWith('/') ? serverDomain.TrimEnd('/') : serverDomain;
|
||||
//construct registration URL.
|
||||
tokenHtml = $"<a href='{cleanedURL}/Login/ResetPassword?email={emailAddress}&token={token}' target='_blank'>{token}</a>";
|
||||
}
|
||||
string emailBody = $"<span>{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please reset your password for LubeLogger using the token")}: {tokenHtml}</span>";
|
||||
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
|
||||
if (result)
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace CarCareTracker.Helper
|
||||
/// </summary>
|
||||
public static class StaticHelper
|
||||
{
|
||||
public const string VersionNumber = "1.4.6";
|
||||
public const string VersionNumber = "1.4.7";
|
||||
public const string DbName = "data/cartracker.db";
|
||||
public const string UserConfigPath = "data/config/userConfig.json";
|
||||
public const string LegacyUserConfigPath = "config/userConfig.json";
|
||||
|
||||
@@ -303,11 +303,11 @@ namespace CarCareTracker.Logic
|
||||
//set next reminder
|
||||
if (results.Any(x => (x.Metric == ReminderMetric.Date || x.Metric == ReminderMetric.Both) && x.Date >= DateTime.Now.Date))
|
||||
{
|
||||
resultToAdd.NextReminder = results.Where(x => x.Date >= DateTime.Now.Date).OrderBy(x => x.Date).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
|
||||
resultToAdd.NextReminder = results.Where(x => x.Date >= DateTime.Now.Date).OrderBy(x => x.Date).Select(x => new ReminderExportModel { Id = x.Id.ToString(), Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString(), Tags = string.Join(' ', x.Tags) }).First();
|
||||
}
|
||||
else if (results.Any(x => (x.Metric == ReminderMetric.Odometer || x.Metric == ReminderMetric.Both) && x.Mileage >= currentMileage))
|
||||
{
|
||||
resultToAdd.NextReminder = results.Where(x => x.Mileage >= currentMileage).OrderBy(x => x.Mileage).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
|
||||
resultToAdd.NextReminder = results.Where(x => x.Mileage >= currentMileage).OrderBy(x => x.Mileage).Select(x => new ReminderExportModel { Id = x.Id.ToString(), Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString(), Tags = string.Join(' ', x.Tags) }).First();
|
||||
}
|
||||
apiResult.Add(resultToAdd);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
public bool DisableRegularLogin { get; set; } = false;
|
||||
public bool UsePKCE { get; set; } = false;
|
||||
public string LogOutURL { get; set; } = "";
|
||||
public string UserInfoURL { get; set; } = "";
|
||||
public string RemoteAuthURL { get {
|
||||
var redirectUrl = $"{AuthURL}?client_id={ClientId}&response_type=code&redirect_uri={RedirectURL}&scope={Scope}&state={State}";
|
||||
if (UsePKCE)
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
public class OpenIDResult
|
||||
{
|
||||
public string id_token { get; set; }
|
||||
public string access_token { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
7
Models/OIDC/OpenIDUserInfo.cs
Normal file
7
Models/OIDC/OpenIDUserInfo.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class OpenIDUserInfo
|
||||
{
|
||||
public string email { get; set; } = "";
|
||||
}
|
||||
}
|
||||
@@ -115,6 +115,8 @@ namespace CarCareTracker.Models
|
||||
}
|
||||
public class ReminderExportModel
|
||||
{
|
||||
[JsonConverter(typeof(FromIntOptional))]
|
||||
public string Id { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Urgency { get; set; }
|
||||
public string Metric { get; set; }
|
||||
@@ -123,6 +125,7 @@ namespace CarCareTracker.Models
|
||||
public string DueDate { get; set; }
|
||||
[JsonConverter(typeof(FromIntOptional))]
|
||||
public string DueOdometer { get; set; }
|
||||
public string Tags { get; set; }
|
||||
}
|
||||
public class PlanRecordExportModel
|
||||
{
|
||||
|
||||
@@ -669,6 +669,65 @@
|
||||
vehicleId - Id of Vehicle
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge bg-primary">POST</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/reminders/add</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Adds Reminder Record to the vehicle
|
||||
</div>
|
||||
<div class="col-3">
|
||||
vehicleId - Id of Vehicle
|
||||
<br />
|
||||
Body(form-data): {<br />
|
||||
description - Description<br />
|
||||
dueDate - Due Date<br />
|
||||
dueOdometer - Due Odometer reading<br />
|
||||
metric - Date/Odometer/Both<br />
|
||||
notes - notes(optional)<br />
|
||||
tags - tags separated by space(optional)<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge text-bg-warning">PUT</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/reminders/update</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Updates Reminder Record
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Body(form-data): {<br />
|
||||
Id - Id of Reminder Record<br />
|
||||
description - Description<br />
|
||||
dueDate - Due Date<br />
|
||||
dueOdometer - Due Odometer reading<br />
|
||||
metric - Date/Odometer/Both<br />
|
||||
notes - notes(optional)<br />
|
||||
tags - tags separated by space(optional)<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge text-bg-danger">DELETE</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/reminders/delete</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Deletes Reminder Record
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Id - Id of Reminder Record
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge bg-success">GET</span>
|
||||
|
||||
@@ -81,9 +81,9 @@
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-6 garage-item-add">
|
||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-6 garage-item-add user-select-none">
|
||||
<div class="card" onclick="showAddVehicleModal()" style="height:100%;">
|
||||
<img src="/defaults/addnew_vehicle.png" style="object-fit:scale-down;height:100%;" />
|
||||
<img src="/defaults/addnew_vehicle.png" style="object-fit:scale-down;height:100%;pointer-events:none;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -165,6 +165,14 @@
|
||||
<div class="col-md-6 col-12">
|
||||
<input type="text" readonly id="inputOIDCToken" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.TokenURL">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="inputOIDCUserInfo">@translator.Translate(userLanguage, "OIDC UserInfo URL")</label>
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<input type="text" readonly id="inputOIDCUserInfo" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.UserInfoURL">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-6 col-12">
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
var openRegistrationEnabled = config.GetServerOpenRegistration();
|
||||
}
|
||||
@model LoginModel
|
||||
@{
|
||||
ViewData["Title"] = "Register";
|
||||
}
|
||||
@@ -19,18 +20,18 @@
|
||||
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
||||
@if (openRegistrationEnabled) {
|
||||
<div class="input-group">
|
||||
<input type="text" id="inputToken" class="form-control">
|
||||
<input type="text" id="inputToken" class="form-control" value="@Model.Token">
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="sendRegistrationToken()"><i class="bi bi-send"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
} else {
|
||||
<input type="text" id="inputToken" class="form-control">
|
||||
<input type="text" id="inputToken" class="form-control" value="@Model.Token">
|
||||
}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputEmail">@translator.Translate(userLanguage, "Email Address")</label>
|
||||
<input type="text" id="inputEmail" class="form-control">
|
||||
<input type="text" id="inputEmail" class="form-control" value="@Model.EmailAddress">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
@{
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
}
|
||||
@model LoginModel
|
||||
@{
|
||||
ViewData["Title"] = "Reset Password";
|
||||
}
|
||||
@@ -16,11 +17,11 @@
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
<div class="form-group">
|
||||
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
||||
<input type="text" id="inputToken" class="form-control">
|
||||
<input type="text" id="inputToken" class="form-control" value="@Model.Token">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputUserName">@translator.Translate(userLanguage, "Email Address")</label>
|
||||
<input type="text" id="inputEmail" class="form-control">
|
||||
<input type="text" id="inputEmail" class="form-control" value="@Model.EmailAddress">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputUserPassword">@translator.Translate(userLanguage, "New Password")</label>
|
||||
|
||||
49
Views/Vehicle/_ExtraFieldMultiple.cshtml
Normal file
49
Views/Vehicle/_ExtraFieldMultiple.cshtml
Normal file
@@ -0,0 +1,49 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@model List<ExtraField>
|
||||
@if (Model.Any()){
|
||||
@foreach (ExtraField field in Model)
|
||||
{
|
||||
var elementId = Guid.NewGuid();
|
||||
<div class="extra-field">
|
||||
<label for="@elementId">@field.Name</label>
|
||||
@switch(field.FieldType){
|
||||
case (ExtraFieldType.Text):
|
||||
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
break;
|
||||
case (ExtraFieldType.Number):
|
||||
<input type="number" inputmode="numeric" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
break;
|
||||
case (ExtraFieldType.Decimal):
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
break;
|
||||
case (ExtraFieldType.Date):
|
||||
<div class="input-group">
|
||||
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<script>initExtraFieldDatePicker('@elementId')</script>
|
||||
break;
|
||||
case (ExtraFieldType.Time):
|
||||
<input type="time" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
break;
|
||||
case (ExtraFieldType.Location):
|
||||
<div class="input-group">
|
||||
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="populateLocationField('@elementId')"><i class="bi bi-geo-alt"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
break;
|
||||
default:
|
||||
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
break;
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
{
|
||||
<li class="list-group-item">
|
||||
<div class="d-flex justify-content-between">
|
||||
<a type="button" class="btn btn-link text-truncate uploadedFileName" href="@filesUploaded.Location" target="_blank">@filesUploaded.Name</a>
|
||||
<a type="button" class="btn btn-link text-truncate uploadedFileName" href="@filesUploaded.Location" title="@filesUploaded.Name" target="_blank">@filesUploaded.Name</a>
|
||||
<div class="d-flex align-items-center">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary me-2" onclick="editFileName('@filesUploaded.Location', this)"><i class="bi bi-pencil"></i></button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteFileFromUploadedFiles('@filesUploaded.Location', this)"><i class="bi bi-trash"></i></button>
|
||||
|
||||
@@ -97,14 +97,7 @@
|
||||
<!option value="@tag">@tag</!option>
|
||||
}
|
||||
</select>
|
||||
@foreach (ExtraField field in Model.GasRecord.ExtraFields)
|
||||
{
|
||||
var elementId = Guid.NewGuid();
|
||||
<div class="extra-field">
|
||||
<label for="@elementId">@field.Name</label>
|
||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||
</div>
|
||||
}
|
||||
@await Html.PartialAsync("_ExtraField", Model.GasRecord.ExtraFields)
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="gasRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||
|
||||
@@ -30,14 +30,7 @@
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="@(useThreeDecimals ? "fixDecimalInput(this, 3)" : "fixDecimalInput(this, 2)")" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
<label for="gasRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
|
||||
<select multiple class="form-select" id="gasRecordTag"></select>
|
||||
@foreach (ExtraField field in Model.EditRecord.ExtraFields)
|
||||
{
|
||||
var elementId = Guid.NewGuid();
|
||||
<div class="extra-field">
|
||||
<label for="@elementId">@field.Name</label>
|
||||
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
</div>
|
||||
}
|
||||
@await Html.PartialAsync("_ExtraFieldMultiple", Model.EditRecord.ExtraFields)
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="gasRecordNotes">@translator.Translate(userLanguage, "Notes(use --- to clear all existing notes)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||
|
||||
@@ -28,14 +28,7 @@
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="genericRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
<label for="genericRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
|
||||
<select multiple class="form-select" id="genericRecordTag"></select>
|
||||
@foreach (ExtraField field in Model.EditRecord.ExtraFields)
|
||||
{
|
||||
var elementId = Guid.NewGuid();
|
||||
<div class="extra-field">
|
||||
<label for="@elementId">@field.Name</label>
|
||||
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
</div>
|
||||
}
|
||||
@await Html.PartialAsync("_ExtraFieldMultiple", Model.EditRecord.ExtraFields)
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="genericRecordNotes">@translator.Translate(userLanguage, "Notes(use --- to clear all existing notes)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||
|
||||
@@ -26,14 +26,7 @@
|
||||
<input type="number" inputmode="numeric" id="odometerRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
<label for="odometerRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
|
||||
<select multiple class="form-select" id="odometerRecordTag"></select>
|
||||
@foreach (ExtraField field in Model.EditRecord.ExtraFields)
|
||||
{
|
||||
var elementId = Guid.NewGuid();
|
||||
<div class="extra-field">
|
||||
<label for="@elementId">@field.Name</label>
|
||||
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
</div>
|
||||
}
|
||||
@await Html.PartialAsync("_ExtraFieldMultiple", Model.EditRecord.ExtraFields)
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="odometerRecordNotes">@translator.Translate(userLanguage, "Notes(use --- to clear all existing notes)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row swimlane">
|
||||
<div class="col-3 d-flex flex-column swimlane mid" ondragover="dragOver(event)" ondrop="dropBox(event, 'Backlog')">
|
||||
<div class="col-3 d-flex flex-column swimlane" ondragover="dragOver(event)" ondrop="dropBox(event, 'Backlog')">
|
||||
<div class="row">
|
||||
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
|
||||
<span class="display-7">@translator.Translate(userLanguage,"Planned")</span>
|
||||
@@ -59,7 +59,7 @@
|
||||
@await Html.PartialAsync("_PlanRecordItem", planRecord)
|
||||
}
|
||||
</div>
|
||||
<div class="col-3 d-flex flex-column swimlane mid" ondragover="dragOver(event)" ondrop="dropBox(event, 'InProgress')">
|
||||
<div class="col-3 d-flex flex-column swimlane" ondragover="dragOver(event)" ondrop="dropBox(event, 'InProgress')">
|
||||
<div class="row">
|
||||
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
|
||||
<span class="display-7">@translator.Translate(userLanguage,"Doing")</span>
|
||||
@@ -81,7 +81,7 @@
|
||||
@await Html.PartialAsync("_PlanRecordItem", planRecord)
|
||||
}
|
||||
</div>
|
||||
<div class="col-3 d-flex flex-column swimlane end" ondragover="dragOver(event)" ondrop="dropBox(event, 'Done')">
|
||||
<div class="col-3 d-flex flex-column swimlane" ondragover="dragOver(event)" ondrop="dropBox(event, 'Done')">
|
||||
<div class="row">
|
||||
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
|
||||
<span class="display-7">@translator.Translate(userLanguage,"Done")</span>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
{
|
||||
<li class="list-group-item">
|
||||
<div class="d-flex justify-content-between">
|
||||
<a type="button" class="btn btn-link text-truncate uploadedFileName" href="@filesUploaded.Location" target="_blank">@filesUploaded.Name</a>
|
||||
<a type="button" class="btn btn-link text-truncate uploadedFileName" href="@filesUploaded.Location" title="@filesUploaded.Name" target="_blank">@filesUploaded.Name</a>
|
||||
<div class="d-flex align-items-center">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary me-2" onclick="editFileName('@filesUploaded.Location', this)"><i class="bi bi-pencil"></i></button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteFileFromUploadedFiles('@filesUploaded.Location', this)"><i class="bi bi-trash"></i></button>
|
||||
|
||||
@@ -150,6 +150,11 @@
|
||||
<input type="text" id="inputOIDCTokenURL" class="form-control">
|
||||
<small class="text-body-secondary">Token URL from Provider</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputOIDCUserInfoURL">User Info URL</label>
|
||||
<input type="text" id="inputOIDCUserInfoURL" class="form-control">
|
||||
<small class="text-body-secondary">Required by some Providers</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputOIDCRedirectURL">LubeLogger URL</label>
|
||||
<input type="text" id="inputOIDCRedirectURL" class="form-control">
|
||||
@@ -332,6 +337,7 @@ function generateConfig(){
|
||||
ClientSecret: $("#inputOIDCClientSecret").val(),
|
||||
AuthURL: $("#inputOIDCAuthURL").val(),
|
||||
TokenURL: $("#inputOIDCTokenURL").val(),
|
||||
UserInfoURL: $("#inputOIDCUserInfoURL").val(),
|
||||
RedirectURL: redirectUrl,
|
||||
Scope: $("#inputOIDCScope").val(),
|
||||
ValidateState: $("#inputOIDCValidateState").is(":checked"),
|
||||
@@ -405,6 +411,7 @@ function generateConfig(){
|
||||
dockerConfig.push(`OpenIDConfig__ClientSecret="${$('#inputOIDCClientSecret').val()}"`);
|
||||
dockerConfig.push(`OpenIDConfig__AuthURL="${$('#inputOIDCAuthURL').val()}"`);
|
||||
dockerConfig.push(`OpenIDConfig__TokenURL="${$('#inputOIDCTokenURL').val()}"`);
|
||||
dockerConfig.push(`OpenIDConfig__UserInfoURL="${$('#inputOIDCUserInfoURL').val()}"`);
|
||||
dockerConfig.push(`OpenIDConfig__RedirectURL="${redirectUrl}"`);
|
||||
dockerConfig.push(`OpenIDConfig__Scope="${$('#inputOIDCScope').val()}"`);
|
||||
dockerConfig.push(`OpenIDConfig__ValidateState=${$('#inputOIDCValidateState').is(':checked')}`);
|
||||
|
||||
@@ -48,12 +48,10 @@ html {
|
||||
.swimlane{
|
||||
height:100%;
|
||||
}
|
||||
.swimlane.mid {
|
||||
|
||||
.swimlane:not(:last-child) {
|
||||
border-right-style: solid;
|
||||
}
|
||||
.swimlane.end {
|
||||
border-left-style: solid;
|
||||
}
|
||||
|
||||
.showOnPrint {
|
||||
display: none;
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user