From d4b4a2b014b03c9a54bc2101f4e0299d922acb93 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 29 Mar 2018 23:30:56 -0400 Subject: [PATCH] admin logs --- src/Admin/Controllers/LogsController.cs | 70 +++++++++++++++++++++++++ src/Admin/Models/CursorPagedModel.cs | 12 +++++ src/Admin/Models/LogModel.cs | 28 ++++++++++ src/Admin/Views/Logs/Index.cshtml | 61 +++++++++++++++++++++ src/Admin/Views/Logs/View.cshtml | 30 +++++++++++ src/Admin/Views/Shared/_Layout.cshtml | 7 +++ 6 files changed, 208 insertions(+) create mode 100644 src/Admin/Controllers/LogsController.cs create mode 100644 src/Admin/Models/CursorPagedModel.cs create mode 100644 src/Admin/Models/LogModel.cs create mode 100644 src/Admin/Views/Logs/Index.cshtml create mode 100644 src/Admin/Views/Logs/View.cshtml diff --git a/src/Admin/Controllers/LogsController.cs b/src/Admin/Controllers/LogsController.cs new file mode 100644 index 0000000000..9245addb47 --- /dev/null +++ b/src/Admin/Controllers/LogsController.cs @@ -0,0 +1,70 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Bit.Admin.Models; +using Bit.Core; +using Bit.Core.Utilities; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.Documents.Client; +using Microsoft.Azure.Documents.Linq; + +namespace Bit.Admin.Controllers +{ + [Authorize] + [SelfHosted(NotSelfHostedOnly = true)] + public class LogsController : Controller + { + private const string Database = "Diagnostics"; + private const string Collection = "Logs"; + + private readonly GlobalSettings _globalSettings; + + public LogsController(GlobalSettings globalSettings) + { + _globalSettings = globalSettings; + } + + public async Task Index(string cursor = null, int count = 25) + { + var collectionLink = UriFactory.CreateDocumentCollectionUri(Database, Collection); + using(var client = new DocumentClient(new Uri(_globalSettings.DocumentDb.Uri), + _globalSettings.DocumentDb.Key)) + { + var options = new FeedOptions + { + MaxItemCount = count, + RequestContinuation = cursor + }; + + var query = client.CreateDocumentQuery(collectionLink, options) + .OrderByDescending(l => l.Timestamp).AsDocumentQuery(); + var response = await query.ExecuteNextAsync(); + + return View(new CursorPagedModel + { + Items = response.ToList(), + Count = count, + Cursor = cursor, + NextCursor = response.ResponseContinuation + }); + } + } + + public async Task View(string id) + { + using(var client = new DocumentClient(new Uri(_globalSettings.DocumentDb.Uri), + _globalSettings.DocumentDb.Key)) + { + var uri = UriFactory.CreateDocumentUri(Database, Collection, id); + var response = await client.ReadDocumentAsync(uri); + if(response?.Document == null) + { + return RedirectToAction("Index"); + } + + return View(response.Document); + } + } + } +} diff --git a/src/Admin/Models/CursorPagedModel.cs b/src/Admin/Models/CursorPagedModel.cs new file mode 100644 index 0000000000..b9e3fdb385 --- /dev/null +++ b/src/Admin/Models/CursorPagedModel.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Bit.Admin.Models +{ + public class CursorPagedModel + { + public List Items { get; set; } + public int Count { get; set; } + public string Cursor { get; set; } + public string NextCursor { get; set; } + } +} diff --git a/src/Admin/Models/LogModel.cs b/src/Admin/Models/LogModel.cs new file mode 100644 index 0000000000..0b101eb617 --- /dev/null +++ b/src/Admin/Models/LogModel.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.Serialization; +using Microsoft.Azure.Documents; +using Newtonsoft.Json; +using Serilog.Events; + +namespace Bit.Admin.Models +{ + public class LogModel : Resource + { + public long EventIdHash { get; set; } + public LogEventLevel Level { get; set; } + public string Message { get; set; } + public string MessageTruncated => Message.Length > 200 ? $"{Message.Substring(0, 200)}..." : Message; + public string MessageTemplate { get; set; } + public Error Exception { get; set; } + + [JsonObject(MemberSerialization.OptIn)] + public class Error : Exception, ISerializable + { + [JsonProperty(PropertyName = "error")] + public string ErrorMessage { get; set; } + + [JsonConstructor] + public Error() { } + } + } +} diff --git a/src/Admin/Views/Logs/Index.cshtml b/src/Admin/Views/Logs/Index.cshtml new file mode 100644 index 0000000000..4210f1ecd6 --- /dev/null +++ b/src/Admin/Views/Logs/Index.cshtml @@ -0,0 +1,61 @@ +@model CursorPagedModel +@{ + ViewData["Title"] = "Logs"; +} + +

Logs

+ +
+ + + + + + + + + + + @if(!Model.Items.Any()) + { + + + + } + else + { + @foreach(var log in Model.Items) + { + + + + + + + } + } + +
 TimestampLevelMessage
No results to list.
+ + + + @log.Timestamp.ToString()@log.Level@log.MessageTruncated
+
+ + diff --git a/src/Admin/Views/Logs/View.cshtml b/src/Admin/Views/Logs/View.cshtml new file mode 100644 index 0000000000..9358e913b2 --- /dev/null +++ b/src/Admin/Views/Logs/View.cshtml @@ -0,0 +1,30 @@ +@model LogModel +@{ + ViewData["Title"] = "Log: " + Model.Id; +} + +

Log @Model.Id

+ +

Information

+
+
Id
+
@Model.Id
+ +
Event Id Hash
+
@Model.EventIdHash
+ +
Timestamp
+
@Model.Timestamp.ToString()
+ +
Level
+
@Model.Level
+
+ +

Message

+
@Model.Message
+ +@if(Model.Exception != null) +{ +

Exception

+
@Model.Exception.ToString()
+} diff --git a/src/Admin/Views/Shared/_Layout.cshtml b/src/Admin/Views/Shared/_Layout.cshtml index 4663d438bf..e87a13089f 100644 --- a/src/Admin/Views/Shared/_Layout.cshtml +++ b/src/Admin/Views/Shared/_Layout.cshtml @@ -1,4 +1,5 @@ @inject SignInManager SignInManager +@inject Bit.Core.GlobalSettings GlobalSettings @@ -36,6 +37,12 @@ + @if(!GlobalSettings.SelfHosted) + { + + } }