Merge branch temp8 into temp8-fixtree
This commit is contained in:
@@ -145,9 +145,11 @@
|
||||
|
||||
public const string PartialViewMacros = "partialViewMacros";
|
||||
|
||||
public const string LogViewer = "logViewer";
|
||||
|
||||
public static class Groups
|
||||
{
|
||||
public const string Settings = "settingsGroup";
|
||||
public const string Settings = "settingsGroup";
|
||||
|
||||
public const string Templating = "templatingGroup";
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
@@ -337,5 +338,10 @@ namespace Umbraco.Core
|
||||
yield return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Direction sortOrder)
|
||||
{
|
||||
return sortOrder == Direction.Ascending ? source.OrderBy(keySelector) : source.OrderByDescending(keySelector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
internal class CountingFilter : ILogFilter
|
||||
{
|
||||
public CountingFilter()
|
||||
{
|
||||
Counts = new LogLevelCounts();
|
||||
}
|
||||
|
||||
public LogLevelCounts Counts { get; }
|
||||
|
||||
public bool TakeLogEvent(LogEvent e)
|
||||
{
|
||||
|
||||
switch (e.Level)
|
||||
{
|
||||
case LogEventLevel.Debug:
|
||||
Counts.Debug++;
|
||||
break;
|
||||
|
||||
case LogEventLevel.Information:
|
||||
Counts.Information++;
|
||||
break;
|
||||
|
||||
case LogEventLevel.Warning:
|
||||
Counts.Warning++;
|
||||
break;
|
||||
|
||||
case LogEventLevel.Error:
|
||||
Counts.Error++;
|
||||
break;
|
||||
|
||||
case LogEventLevel.Fatal:
|
||||
Counts.Fatal++;
|
||||
break;
|
||||
case LogEventLevel.Verbose:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
//Don't add it to the list
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
internal class ErrorCounterFilter : ILogFilter
|
||||
{
|
||||
public int Count { get; private set; }
|
||||
|
||||
public bool TakeLogEvent(LogEvent e)
|
||||
{
|
||||
if (e.Level == LogEventLevel.Fatal || e.Level == LogEventLevel.Error || e.Exception != null)
|
||||
Count++;
|
||||
|
||||
//Don't add it to the list
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Serilog.Events;
|
||||
using Serilog.Filters.Expressions;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
//Log Expression Filters (pass in filter exp string)
|
||||
internal class ExpressionFilter : ILogFilter
|
||||
{
|
||||
private readonly Func<LogEvent, bool> _filter;
|
||||
private const string ExpressionOperators = "()+=*<>%-";
|
||||
|
||||
public ExpressionFilter(string filterExpression)
|
||||
{
|
||||
Func<LogEvent, bool> filter;
|
||||
|
||||
if (string.IsNullOrEmpty(filterExpression))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If the expression is one word and doesn't contain a serilog operator then we can perform a like search
|
||||
if (!filterExpression.Contains(" ") && !filterExpression.ContainsAny(ExpressionOperators.Select(c => c)))
|
||||
{
|
||||
filter = PerformMessageLikeFilter(filterExpression);
|
||||
}
|
||||
else // check if it's a valid expression
|
||||
{
|
||||
// If the expression evaluates then make it into a filter
|
||||
if (FilterLanguage.TryCreateFilter(filterExpression, out var eval, out _))
|
||||
{
|
||||
filter = evt => true.Equals(eval(evt));
|
||||
}
|
||||
else
|
||||
{
|
||||
//Assume the expression was a search string and make a Like filter from that
|
||||
filter = PerformMessageLikeFilter(filterExpression);
|
||||
}
|
||||
}
|
||||
|
||||
_filter = filter;
|
||||
}
|
||||
|
||||
public bool TakeLogEvent(LogEvent e)
|
||||
{
|
||||
return _filter == null || _filter(e);
|
||||
}
|
||||
|
||||
private Func<LogEvent, bool> PerformMessageLikeFilter(string filterExpression)
|
||||
{
|
||||
var filterSearch = $"@Message like '%{FilterLanguage.EscapeLikeExpressionContent(filterExpression)}%'";
|
||||
if (FilterLanguage.TryCreateFilter(filterSearch, out var eval, out _))
|
||||
{
|
||||
return evt => true.Equals(eval(evt));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public interface ILogFilter
|
||||
{
|
||||
bool TakeLogEvent(LogEvent e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public interface ILogViewer
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all saved searches from your chosen data source
|
||||
/// </summary>
|
||||
IReadOnlyList<SavedLogSearch> GetSavedSearches();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new saved search to chosen data source and returns the updated searches
|
||||
/// </summary>
|
||||
IReadOnlyList<SavedLogSearch> AddSavedSearch(string name, string query);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a saved search to chosen data source and returns the remaining searches
|
||||
/// </summary>
|
||||
IReadOnlyList<SavedLogSearch> DeleteSavedSearch(string name, string query);
|
||||
|
||||
/// <summary>
|
||||
/// A count of number of errors
|
||||
/// By counting Warnings with Exceptions, Errors & Fatal messages
|
||||
/// </summary>
|
||||
int GetNumberOfErrors(DateTimeOffset startDate, DateTimeOffset endDate);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a number of the different log level entries
|
||||
/// </summary>
|
||||
LogLevelCounts GetLogLevelCounts(DateTimeOffset startDate, DateTimeOffset endDate);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all unique message templates and their counts
|
||||
/// </summary>
|
||||
IEnumerable<LogTemplate> GetMessageTemplates(DateTimeOffset startDate, DateTimeOffset endDate);
|
||||
|
||||
bool CanHandleLargeLogs { get; }
|
||||
|
||||
bool CheckCanOpenLogs(DateTimeOffset startDate, DateTimeOffset endDate);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the collection of logs
|
||||
/// </summary>
|
||||
PagedResult<LogMessage> GetLogs(DateTimeOffset startDate, DateTimeOffset endDate,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 100,
|
||||
Direction orderDirection = Direction.Descending,
|
||||
string filterExpression = null,
|
||||
string[] logLevels = null);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting.Compact.Reader;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
internal class JsonLogViewer : LogViewerSourceBase
|
||||
{
|
||||
private readonly string _logsPath;
|
||||
|
||||
public JsonLogViewer(string logsPath = "", string searchPath = "") : base(searchPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(logsPath))
|
||||
logsPath = $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\";
|
||||
|
||||
_logsPath = logsPath;
|
||||
}
|
||||
|
||||
private const int FileSizeCap = 100;
|
||||
|
||||
public override bool CanHandleLargeLogs => false;
|
||||
|
||||
public override bool CheckCanOpenLogs(DateTimeOffset startDate, DateTimeOffset endDate)
|
||||
{
|
||||
//Log Directory
|
||||
var logDirectory = _logsPath;
|
||||
|
||||
//Number of entries
|
||||
long fileSizeCount = 0;
|
||||
|
||||
//foreach full day in the range - see if we can find one or more filenames that end with
|
||||
//yyyyMMdd.json - Ends with due to MachineName in filenames - could be 1 or more due to load balancing
|
||||
for (var day = startDate.Date; day.Date <= endDate.Date; day = day.AddDays(1))
|
||||
{
|
||||
//Filename ending to search for (As could be multiple)
|
||||
var filesToFind = $"*{day:yyyyMMdd}.json";
|
||||
|
||||
var filesForCurrentDay = Directory.GetFiles(logDirectory, filesToFind);
|
||||
|
||||
fileSizeCount += filesForCurrentDay.Sum(x => new FileInfo(x).Length);
|
||||
}
|
||||
|
||||
//The GetLogSize call on JsonLogViewer returns the total file size in bytes
|
||||
//Check if the log size is not greater than 100Mb (FileSizeCap)
|
||||
var logSizeAsMegabytes = fileSizeCount / 1024 / 1024;
|
||||
return logSizeAsMegabytes <= FileSizeCap;
|
||||
}
|
||||
|
||||
protected override IReadOnlyList<LogEvent> GetLogs(DateTimeOffset startDate, DateTimeOffset endDate, ILogFilter filter, int skip, int take)
|
||||
{
|
||||
var logs = new List<LogEvent>();
|
||||
|
||||
//Log Directory
|
||||
var logDirectory = $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\";
|
||||
|
||||
var count = 0;
|
||||
|
||||
//foreach full day in the range - see if we can find one or more filenames that end with
|
||||
//yyyyMMdd.json - Ends with due to MachineName in filenames - could be 1 or more due to load balancing
|
||||
for (var day = startDate.Date; day.Date <= endDate.Date; day = day.AddDays(1))
|
||||
{
|
||||
//Filename ending to search for (As could be multiple)
|
||||
var filesToFind = $"*{day:yyyyMMdd}.json";
|
||||
|
||||
var filesForCurrentDay = Directory.GetFiles(logDirectory, filesToFind);
|
||||
|
||||
//Foreach file we find - open it
|
||||
foreach (var filePath in filesForCurrentDay)
|
||||
{
|
||||
//Open log file & add contents to the log collection
|
||||
//Which we then use LINQ to page over
|
||||
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
using (var stream = new StreamReader(fs))
|
||||
{
|
||||
var reader = new LogEventReader(stream);
|
||||
while (reader.TryRead(out var evt))
|
||||
{
|
||||
if (count > skip + take)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (count < skip)
|
||||
{
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (filter.TakeLogEvent(evt))
|
||||
{
|
||||
logs.Add(evt);
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return logs;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public class LogLevelCounts
|
||||
{
|
||||
public int Information { get; set; }
|
||||
|
||||
public int Debug { get; set; }
|
||||
|
||||
public int Warning { get; set; }
|
||||
|
||||
public int Error { get; set; }
|
||||
|
||||
public int Fatal { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public class LogMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// The time at which the log event occurred.
|
||||
/// </summary>
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The level of the event.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public LogEventLevel Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The message template describing the log event.
|
||||
/// </summary>
|
||||
public string MessageTemplateText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The message template filled with the log event properties.
|
||||
/// </summary>
|
||||
public string RenderedMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Properties associated with the log event, including those presented in Serilog.Events.LogEvent.MessageTemplate.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, LogEventPropertyValue> Properties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An exception associated with the log event, or null.
|
||||
/// </summary>
|
||||
public string Exception { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public class LogTemplate
|
||||
{
|
||||
public string MessageTemplate { get; set; }
|
||||
|
||||
public int Count { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Umbraco.Core.Components;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public class LogViewerComposer : ICoreComposer
|
||||
{
|
||||
public void Compose(Composition composition)
|
||||
{
|
||||
composition.RegisterUnique<ILogViewer>(_ => new JsonLogViewer());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public abstract class LogViewerSourceBase : ILogViewer
|
||||
{
|
||||
protected LogViewerSourceBase(string pathToSearches = "")
|
||||
{
|
||||
if (string.IsNullOrEmpty(pathToSearches))
|
||||
// ReSharper disable once StringLiteralTypo
|
||||
pathToSearches = IOHelper.MapPath("~/Config/logviewer.searches.config.js");
|
||||
|
||||
_searchesConfigPath = pathToSearches;
|
||||
}
|
||||
|
||||
private readonly string _searchesConfigPath;
|
||||
|
||||
public abstract bool CanHandleLargeLogs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get all logs from your chosen data source back as Serilog LogEvents
|
||||
/// </summary>
|
||||
protected abstract IReadOnlyList<LogEvent> GetLogs(DateTimeOffset startDate, DateTimeOffset endDate, ILogFilter filter, int skip, int take);
|
||||
|
||||
public abstract bool CheckCanOpenLogs(DateTimeOffset startDate, DateTimeOffset endDate);
|
||||
|
||||
public virtual IReadOnlyList<SavedLogSearch> GetSavedSearches()
|
||||
{
|
||||
//Our default implementation
|
||||
|
||||
//If file does not exist - lets create it with an empty array
|
||||
EnsureFileExists(_searchesConfigPath, "[]");
|
||||
|
||||
var rawJson = System.IO.File.ReadAllText(_searchesConfigPath);
|
||||
return JsonConvert.DeserializeObject<SavedLogSearch[]>(rawJson);
|
||||
}
|
||||
|
||||
public virtual IReadOnlyList<SavedLogSearch> AddSavedSearch(string name, string query)
|
||||
{
|
||||
//Get the existing items
|
||||
var searches = GetSavedSearches().ToList();
|
||||
|
||||
//Add the new item to the bottom of the list
|
||||
searches.Add(new SavedLogSearch { Name = name, Query = query });
|
||||
|
||||
//Serialize to JSON string
|
||||
var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented);
|
||||
|
||||
//If file does not exist - lets create it with an empty array
|
||||
EnsureFileExists(_searchesConfigPath, "[]");
|
||||
|
||||
//Write it back down to file
|
||||
System.IO.File.WriteAllText(_searchesConfigPath, rawJson);
|
||||
|
||||
//Return the updated object - so we can instantly reset the entire array from the API response
|
||||
//As opposed to push a new item into the array
|
||||
return searches;
|
||||
}
|
||||
|
||||
public virtual IReadOnlyList<SavedLogSearch> DeleteSavedSearch(string name, string query)
|
||||
{
|
||||
//Get the existing items
|
||||
var searches = GetSavedSearches().ToList();
|
||||
|
||||
//Removes the search
|
||||
searches.RemoveAll(s => s.Name.Equals(name) && s.Query.Equals(query));
|
||||
|
||||
//Serialize to JSON string
|
||||
var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented);
|
||||
|
||||
//Write it back down to file
|
||||
System.IO.File.WriteAllText(_searchesConfigPath, rawJson);
|
||||
|
||||
//Return the updated object - so we can instantly reset the entire array from the API response
|
||||
return searches;
|
||||
}
|
||||
|
||||
public int GetNumberOfErrors(DateTimeOffset startDate, DateTimeOffset endDate)
|
||||
{
|
||||
var errorCounter = new ErrorCounterFilter();
|
||||
GetLogs(startDate, endDate, errorCounter, 0, int.MaxValue);
|
||||
return errorCounter.Count;
|
||||
}
|
||||
|
||||
public LogLevelCounts GetLogLevelCounts(DateTimeOffset startDate, DateTimeOffset endDate)
|
||||
{
|
||||
var counter = new CountingFilter();
|
||||
GetLogs(startDate, endDate, counter, 0, int.MaxValue);
|
||||
return counter.Counts;
|
||||
}
|
||||
|
||||
public IEnumerable<LogTemplate> GetMessageTemplates(DateTimeOffset startDate, DateTimeOffset endDate)
|
||||
{
|
||||
var messageTemplates = new MessageTemplateFilter();
|
||||
GetLogs(startDate, endDate, messageTemplates, 0, int.MaxValue);
|
||||
|
||||
var templates = messageTemplates.Counts.
|
||||
Select(x => new LogTemplate { MessageTemplate = x.Key, Count = x.Value })
|
||||
.OrderByDescending(x=> x.Count);
|
||||
|
||||
return templates;
|
||||
}
|
||||
|
||||
public PagedResult<LogMessage> GetLogs(DateTimeOffset startDate, DateTimeOffset endDate,
|
||||
int pageNumber = 1, int pageSize = 100,
|
||||
Direction orderDirection = Direction.Descending,
|
||||
string filterExpression = null,
|
||||
string[] logLevels = null)
|
||||
{
|
||||
var expression = new ExpressionFilter(filterExpression);
|
||||
var filteredLogs = GetLogs(startDate, endDate, expression, 0, int.MaxValue);
|
||||
|
||||
//This is user used the checkbox UI to toggle which log levels they wish to see
|
||||
//If an empty array or null - its implied all levels to be viewed
|
||||
if (logLevels?.Length > 0)
|
||||
{
|
||||
var logsAfterLevelFilters = new List<LogEvent>();
|
||||
var validLogType = true;
|
||||
foreach (var level in logLevels)
|
||||
{
|
||||
//Check if level string is part of the LogEventLevel enum
|
||||
if(Enum.IsDefined(typeof(LogEventLevel), level))
|
||||
{
|
||||
validLogType = true;
|
||||
logsAfterLevelFilters.AddRange(filteredLogs.Where(x => string.Equals(x.Level.ToString(), level, StringComparison.InvariantCultureIgnoreCase)));
|
||||
}
|
||||
else
|
||||
{
|
||||
validLogType = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (validLogType)
|
||||
{
|
||||
filteredLogs = logsAfterLevelFilters;
|
||||
}
|
||||
}
|
||||
|
||||
long totalRecords = filteredLogs.Count;
|
||||
|
||||
//Order By, Skip, Take & Select
|
||||
var logMessages = filteredLogs
|
||||
.OrderBy(l => l.Timestamp, orderDirection)
|
||||
.Skip(pageSize * (pageNumber - 1))
|
||||
.Take(pageSize)
|
||||
.Select(x => new LogMessage
|
||||
{
|
||||
Timestamp = x.Timestamp,
|
||||
Level = x.Level,
|
||||
MessageTemplateText = x.MessageTemplate.Text,
|
||||
Exception = x.Exception?.ToString(),
|
||||
Properties = x.Properties,
|
||||
RenderedMessage = x.RenderMessage()
|
||||
});
|
||||
|
||||
return new PagedResult<LogMessage>(totalRecords, pageNumber, pageSize)
|
||||
{
|
||||
Items = logMessages
|
||||
};
|
||||
}
|
||||
|
||||
private static void EnsureFileExists(string path, string contents)
|
||||
{
|
||||
var absolutePath = IOHelper.MapPath(path);
|
||||
if (System.IO.File.Exists(absolutePath)) return;
|
||||
|
||||
using (var writer = System.IO.File.CreateText(absolutePath))
|
||||
{
|
||||
writer.Write(contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
internal class MessageTemplateFilter : ILogFilter
|
||||
{
|
||||
public readonly Dictionary<string, int> Counts = new Dictionary<string, int>();
|
||||
|
||||
public bool TakeLogEvent(LogEvent e)
|
||||
{
|
||||
var templateText = e.MessageTemplate.Text;
|
||||
if (Counts.TryGetValue(templateText, out var count))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = 1;
|
||||
}
|
||||
|
||||
Counts[templateText] = count;
|
||||
|
||||
//Don't add it to the list
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public class SavedLogSearch
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("query")]
|
||||
public string Query { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -428,7 +428,7 @@ namespace Umbraco.Core.Migrations.Install
|
||||
var tableExist = TableExists(tableName);
|
||||
if (overwrite && tableExist)
|
||||
{
|
||||
_logger.Info<DatabaseSchemaCreator>("Table '{TableName}' already exists, but will be recreated", tableName);
|
||||
_logger.Info<DatabaseSchemaCreator>("Table {TableName} already exists, but will be recreated", tableName);
|
||||
|
||||
DropTable(tableName);
|
||||
tableExist = false;
|
||||
@@ -437,13 +437,13 @@ namespace Umbraco.Core.Migrations.Install
|
||||
if (tableExist)
|
||||
{
|
||||
// The table exists and was not recreated/overwritten.
|
||||
_logger.Info<Database>("Table '{TableName}' already exists - no changes were made", tableName);
|
||||
_logger.Info<Database>("Table {TableName} already exists - no changes were made", tableName);
|
||||
return;
|
||||
}
|
||||
|
||||
//Execute the Create Table sql
|
||||
var created = _database.Execute(new Sql(createSql));
|
||||
_logger.Info<DatabaseSchemaCreator>("Create Table '{TableName}' ({Created}): \n {Sql}", tableName, created, createSql);
|
||||
_logger.Info<DatabaseSchemaCreator>("Create Table {TableName} ({Created}): \n {Sql}", tableName, created, createSql);
|
||||
|
||||
//If any statements exists for the primary key execute them here
|
||||
if (string.IsNullOrEmpty(createPrimaryKeySql) == false)
|
||||
@@ -479,11 +479,12 @@ namespace Umbraco.Core.Migrations.Install
|
||||
|
||||
if (overwrite)
|
||||
{
|
||||
_logger.Info<Database>("Table '{TableName}' was recreated", tableName);
|
||||
_logger.Info<Database>("Table {TableName} was recreated", tableName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info<Database>("New table '{TableName}' was created", tableName);
|
||||
_logger.Info<Database>("New table {TableName} was created", tableName);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
}
|
||||
|
||||
if (!publishedDataTypes.TryGetValue(id, out var dataType))
|
||||
throw new ArgumentException("Not a valid datatype identifier.", nameof(id));
|
||||
throw new ArgumentException($"Could not find a datatype with identifier {id}.", nameof(id));
|
||||
|
||||
return dataType;
|
||||
}
|
||||
|
||||
@@ -87,6 +87,9 @@
|
||||
<PackageReference Include="Serilog.Formatting.Compact">
|
||||
<Version>1.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog.Formatting.Compact.Reader">
|
||||
<Version>1.0.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog.Settings.AppSettings">
|
||||
<Version>2.2.2</Version>
|
||||
</PackageReference>
|
||||
@@ -343,6 +346,17 @@
|
||||
<Compile Include="IO\MediaPathSchemes\TwoGuidsMediaPathScheme.cs" />
|
||||
<Compile Include="IO\SupportingFileSystems.cs" />
|
||||
<Compile Include="KeyValuePairExtensions.cs" />
|
||||
<Compile Include="Logging\Viewer\CountingFilter.cs" />
|
||||
<Compile Include="Logging\Viewer\ErrorCounterFilter.cs" />
|
||||
<Compile Include="Logging\Viewer\ExpressionFilter.cs" />
|
||||
<Compile Include="Logging\Viewer\ILogFilter.cs" />
|
||||
<Compile Include="Logging\Viewer\LogTemplate.cs" />
|
||||
<Compile Include="Logging\Viewer\ILogViewer.cs" />
|
||||
<Compile Include="Logging\Viewer\LogLevelCounts.cs" />
|
||||
<Compile Include="Logging\Viewer\JsonLogViewer.cs" />
|
||||
<Compile Include="Logging\Viewer\LogMessage.cs" />
|
||||
<Compile Include="Logging\Viewer\LogViewerComposer.cs" />
|
||||
<Compile Include="Logging\Viewer\LogViewerSourceBase.cs" />
|
||||
<Compile Include="Logging\IProfilingLogger.cs" />
|
||||
<Compile Include="Logging\LogHttpRequest.cs" />
|
||||
<Compile Include="Logging\LogLevel.cs" />
|
||||
@@ -352,6 +366,8 @@
|
||||
<Compile Include="Logging\Serilog\Enrichers\HttpSessionIdEnricher.cs" />
|
||||
<Compile Include="Logging\Serilog\LoggerConfigExtensions.cs" />
|
||||
<Compile Include="Logging\Serilog\Enrichers\Log4NetLevelMapperEnricher.cs" />
|
||||
<Compile Include="Logging\Viewer\MessageTemplateFilter.cs" />
|
||||
<Compile Include="Logging\Viewer\SavedLogSearch.cs" />
|
||||
<Compile Include="Manifest\DashboardAccessRuleConverter.cs" />
|
||||
<Compile Include="Manifest\ManifestBackOfficeSection.cs" />
|
||||
<Compile Include="Manifest\ManifestContentAppDefinition.cs" />
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace Umbraco.Tests.Composing
|
||||
Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree]
|
||||
|
||||
typesFound = TypeFinder.FindClassesWithAttribute<TreeAttribute>(new[] { typeof (UmbracoContext).Assembly });
|
||||
Assert.AreEqual(21, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree]
|
||||
Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree]
|
||||
}
|
||||
|
||||
private static IProfilingLogger GetTestProfilingLogger()
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging.Viewer;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
|
||||
namespace Umbraco.Tests.Logging
|
||||
{
|
||||
[TestFixture]
|
||||
public class LogviewerTests
|
||||
{
|
||||
private ILogViewer _logViewer;
|
||||
|
||||
const string _logfileName = "UmbracoTraceLog.UNITTEST.20181112.json";
|
||||
const string _searchfileName = "logviewer.searches.config.js";
|
||||
|
||||
private string _newLogfilePath;
|
||||
private string _newLogfileDirPath;
|
||||
|
||||
private string _newSearchfilePath;
|
||||
private string _newSearchfileDirPath;
|
||||
|
||||
private DateTimeOffset _startDate = new DateTime(year: 2018, month: 11, day: 12, hour:0, minute:0, second:0);
|
||||
private DateTimeOffset _endDate = new DateTime(year: 2018, month: 11, day: 13, hour: 0, minute: 0, second: 0);
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
//Create an example JSON log file to check results
|
||||
//As a one time setup for all tets in this class/fixture
|
||||
|
||||
var exampleLogfilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, @"Logging\", _logfileName);
|
||||
_newLogfileDirPath = Path.Combine(TestContext.CurrentContext.TestDirectory, @"App_Data\Logs\");
|
||||
_newLogfilePath = Path.Combine(_newLogfileDirPath, _logfileName);
|
||||
|
||||
var exampleSearchfilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, @"Logging\", _searchfileName);
|
||||
_newSearchfileDirPath = Path.Combine(TestContext.CurrentContext.TestDirectory, @"Config\");
|
||||
_newSearchfilePath = Path.Combine(_newSearchfileDirPath, _searchfileName);
|
||||
|
||||
//Create/ensure Directory exists
|
||||
IOHelper.EnsurePathExists(_newLogfileDirPath);
|
||||
IOHelper.EnsurePathExists(_newSearchfileDirPath);
|
||||
|
||||
//Copy the sample files
|
||||
File.Copy(exampleLogfilePath, _newLogfilePath, true);
|
||||
File.Copy(exampleSearchfilePath, _newSearchfilePath, true);
|
||||
|
||||
_logViewer = new JsonLogViewer(logsPath: _newLogfileDirPath, searchPath: _newSearchfilePath);
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
//Cleanup & delete the example log & search files off disk
|
||||
//Once all tests in this class/fixture have run
|
||||
if (File.Exists(_newLogfilePath))
|
||||
File.Delete(_newLogfilePath);
|
||||
|
||||
if (File.Exists(_newSearchfilePath))
|
||||
File.Delete(_newSearchfilePath);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Logs_Contain_Correct_Error_Count()
|
||||
{
|
||||
var numberOfErrors = _logViewer.GetNumberOfErrors(startDate: _startDate, endDate: _endDate);
|
||||
|
||||
//Our dummy log should contain 2 errors
|
||||
Assert.AreEqual(2, numberOfErrors);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Logs_Contain_Correct_Log_Level_Counts()
|
||||
{
|
||||
var logCounts = _logViewer.GetLogLevelCounts(startDate: _startDate, endDate: _endDate);
|
||||
|
||||
Assert.AreEqual(1954, logCounts.Debug);
|
||||
Assert.AreEqual(2, logCounts.Error);
|
||||
Assert.AreEqual(0, logCounts.Fatal);
|
||||
Assert.AreEqual(62, logCounts.Information);
|
||||
Assert.AreEqual(7, logCounts.Warning);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Logs_Contains_Correct_Message_Templates()
|
||||
{
|
||||
var templates = _logViewer.GetMessageTemplates(startDate: _startDate, endDate: _endDate);
|
||||
|
||||
//Count no of templates
|
||||
Assert.AreEqual(43, templates.Count());
|
||||
|
||||
//Verify all templates & counts are unique
|
||||
CollectionAssert.AllItemsAreUnique(templates);
|
||||
|
||||
//Ensure the collection contains LogTemplate objects
|
||||
CollectionAssert.AllItemsAreInstancesOfType(templates, typeof(LogTemplate));
|
||||
|
||||
//Get first item & verify its template & count are what we expect
|
||||
var popularTemplate = templates.FirstOrDefault();
|
||||
|
||||
Assert.IsNotNull(popularTemplate);
|
||||
Assert.AreEqual("{LogPrefix} Task added {TaskType}", popularTemplate.MessageTemplate);
|
||||
Assert.AreEqual(689, popularTemplate.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Logs_Can_Open_As_Small_File()
|
||||
{
|
||||
//We are just testing a return value (as we know the example file is less than 200MB)
|
||||
//But this test method does not test/check that
|
||||
var canOpenLogs = _logViewer.CheckCanOpenLogs(startDate: _startDate, endDate: _endDate);
|
||||
Assert.IsTrue(canOpenLogs);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Logs_Can_Be_Queried()
|
||||
{
|
||||
//Should get me the most 100 recent log entries & using default overloads for remaining params
|
||||
var allLogs = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1);
|
||||
|
||||
//Check we get 100 results back for a page & total items all good :)
|
||||
Assert.AreEqual(100, allLogs.Items.Count());
|
||||
Assert.AreEqual(2410, allLogs.TotalItems);
|
||||
Assert.AreEqual(25, allLogs.TotalPages);
|
||||
|
||||
//Check collection all contain same object type
|
||||
CollectionAssert.AllItemsAreInstancesOfType(allLogs.Items, typeof(LogMessage));
|
||||
|
||||
//Check first item is newest
|
||||
var newestItem = allLogs.Items.First();
|
||||
DateTimeOffset newDate;
|
||||
DateTimeOffset.TryParse("2018-11-12T09:24:27.4057583Z", out newDate);
|
||||
Assert.AreEqual(newDate, newestItem.Timestamp);
|
||||
|
||||
|
||||
//Check we call method again with a smaller set of results & in ascending
|
||||
var smallQuery = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1, pageSize: 10, orderDirection: Direction.Ascending);
|
||||
Assert.AreEqual(10, smallQuery.Items.Count());
|
||||
Assert.AreEqual(241, smallQuery.TotalPages);
|
||||
|
||||
//Check first item is oldest
|
||||
var oldestItem = smallQuery.Items.First();
|
||||
DateTimeOffset oldDate;
|
||||
DateTimeOffset.TryParse("2018-11-12T08:34:45.8371142Z", out oldDate);
|
||||
Assert.AreEqual(oldDate, oldestItem.Timestamp);
|
||||
|
||||
|
||||
//Check invalid log levels
|
||||
//Rather than expect 0 items - get all items back & ignore the invalid levels
|
||||
string[] invalidLogLevels = { "Invalid", "NotALevel" };
|
||||
var queryWithInvalidLevels = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1, logLevels: invalidLogLevels);
|
||||
Assert.AreEqual(2410, queryWithInvalidLevels.TotalItems);
|
||||
|
||||
//Check we can call method with an array of logLevel (error & warning)
|
||||
string [] logLevels = { "Warning", "Error" };
|
||||
var queryWithLevels = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1, logLevels: logLevels);
|
||||
Assert.AreEqual(9, queryWithLevels.TotalItems);
|
||||
|
||||
//Query @Level='Warning' BUT we pass in array of LogLevels for Debug & Info (Expect to get 0 results)
|
||||
string[] logLevelMismatch = { "Debug", "Information" };
|
||||
var filterLevelQuery = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1, filterExpression: "@Level='Warning'", logLevels: logLevelMismatch); ;
|
||||
Assert.AreEqual(0, filterLevelQuery.TotalItems);
|
||||
}
|
||||
|
||||
[TestCase("", 2410)]
|
||||
[TestCase("Has(@Exception)", 2)]
|
||||
[TestCase("Has(Duration) and Duration > 1000", 13)]
|
||||
[TestCase("Not(@Level = 'Verbose') and Not(@Level= 'Debug')", 71)]
|
||||
[TestCase("StartsWith(SourceContext, 'Umbraco.Core')", 1183)]
|
||||
[TestCase("@MessageTemplate = '{EndMessage} ({Duration}ms) [Timing {TimingId}]'", 622)]
|
||||
[TestCase("SortedComponentTypes[?] = 'Umbraco.Web.Search.ExamineComponent'", 1)]
|
||||
[TestCase("Contains(SortedComponentTypes[?], 'DatabaseServer')", 1)]
|
||||
[Test]
|
||||
public void Logs_Can_Query_With_Expressions(string queryToVerify, int expectedCount)
|
||||
{
|
||||
var testQuery = _logViewer.GetLogs(startDate: _startDate, endDate: _endDate, pageNumber: 1, filterExpression: queryToVerify);
|
||||
Assert.AreEqual(expectedCount, testQuery.TotalItems);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Log_Search_Can_Persist()
|
||||
{
|
||||
//Add a new search
|
||||
_logViewer.AddSavedSearch("Unit Test Example", "Has(UnitTest)");
|
||||
|
||||
var searches = _logViewer.GetSavedSearches();
|
||||
|
||||
var savedSearch = new SavedLogSearch
|
||||
{
|
||||
Name = "Unit Test Example",
|
||||
Query = "Has(UnitTest)"
|
||||
};
|
||||
|
||||
//Check if we can find the newly added item from the results we get back
|
||||
var findItem = searches.Where(x => x.Name == "Unit Test Example" && x.Query == "Has(UnitTest)");
|
||||
|
||||
Assert.IsNotNull(findItem, "We should have found the saved search, but get no results");
|
||||
Assert.AreEqual(1, findItem.Count(), "Our list of searches should only contain one result");
|
||||
|
||||
//TODO: Need someone to help me find out why these don't work
|
||||
//CollectionAssert.Contains(searches, savedSearch, "Can not find the new search that was saved");
|
||||
//Assert.That(searches, Contains.Item(savedSearch));
|
||||
|
||||
//Remove the search from above & ensure it no longer exists
|
||||
_logViewer.DeleteSavedSearch("Unit Test Example", "Has(UnitTest)");
|
||||
|
||||
searches = _logViewer.GetSavedSearches();
|
||||
findItem = searches.Where(x => x.Name == "Unit Test Example" && x.Query == "Has(UnitTest)");
|
||||
Assert.IsEmpty(findItem, "The search item should no longer exist");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,42 @@
|
||||
[
|
||||
{
|
||||
"name": "Find all logs where the Level is NOT Verbose and NOT Debug",
|
||||
"query": "Not(@Level='Verbose') and Not(@Level='Debug')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)",
|
||||
"query": "Has(@Exception)"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that have the property 'Duration'",
|
||||
"query": "Has(Duration)"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms",
|
||||
"query": "Has(Duration) and Duration > 1000"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that are from the namespace 'Umbraco.Core'",
|
||||
"query": "StartsWith(SourceContext, 'Umbraco.Core')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that use a specific log message template",
|
||||
"query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'"
|
||||
},
|
||||
{
|
||||
"name": "Find logs where one of the items in the SortedComponentTypes property array is equal to",
|
||||
"query": "SortedComponentTypes[?] = 'Umbraco.Web.Search.ExamineComponent'"
|
||||
},
|
||||
{
|
||||
"name": "Find logs where one of the items in the SortedComponentTypes property array contains",
|
||||
"query": "Contains(SortedComponentTypes[?], 'DatabaseServer')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that the message has localhost in it with SQL like",
|
||||
"query": "@Message like '%localhost%'"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that the message that starts with 'end' in it with SQL like",
|
||||
"query": "@Message like 'end%'"
|
||||
}
|
||||
]
|
||||
@@ -125,6 +125,7 @@
|
||||
<Compile Include="CoreThings\GuidUtilsTests.cs" />
|
||||
<Compile Include="CoreThings\HexEncoderTests.cs" />
|
||||
<Compile Include="CoreXml\RenamedRootNavigatorTests.cs" />
|
||||
<Compile Include="Logging\LogviewerTests.cs" />
|
||||
<Compile Include="Manifest\ManifestContentAppTests.cs" />
|
||||
<Compile Include="Migrations\MigrationPlanTests.cs" />
|
||||
<Compile Include="Migrations\MigrationTests.cs" />
|
||||
@@ -514,6 +515,9 @@
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="Logging\UmbracoTraceLog.UNITTEST.20181112.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="Packaging\Packages\Document_Type_Picker_1.1.umb" />
|
||||
<None Include="UmbracoExamine\TestFiles\umbraco-sort.config">
|
||||
<SubType>Designer</SubType>
|
||||
@@ -559,6 +563,10 @@
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<Content Include="Logging\logviewer.searches.config.js">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Migrations\SqlScripts\MySqlTotal-480.sql" />
|
||||
<Content Include="Migrations\SqlScripts\SqlCe-SchemaAndData-4110.sql" />
|
||||
<Content Include="Migrations\SqlScripts\SqlCeTotal-480.sql" />
|
||||
<Content Include="Migrations\SqlScripts\SqlServerTotal-480.sql" />
|
||||
|
||||
Executable → Regular
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name umbraco.resources.logViewerResource
|
||||
* @description Retrives Umbraco log items (by default from JSON files on disk)
|
||||
*
|
||||
*
|
||||
**/
|
||||
function logViewerResource($q, $http, umbRequestHelper) {
|
||||
|
||||
//the factory object returned
|
||||
return {
|
||||
|
||||
getNumberOfErrors: function () {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logViewerApiBaseUrl",
|
||||
"GetNumberOfErrors")),
|
||||
'Failed to retrieve number of errors in logs');
|
||||
},
|
||||
|
||||
getLogLevelCounts: function () {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logViewerApiBaseUrl",
|
||||
"GetLogLevelCounts")),
|
||||
'Failed to retrieve log level counts');
|
||||
},
|
||||
|
||||
getMessageTemplates: function () {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logViewerApiBaseUrl",
|
||||
"GetMessageTemplates")),
|
||||
'Failed to retrieve log templates');
|
||||
},
|
||||
|
||||
getSavedSearches: function () {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logViewerApiBaseUrl",
|
||||
"GetSavedSearches")),
|
||||
'Failed to retrieve saved searches');
|
||||
},
|
||||
|
||||
postSavedSearch: function (name, query) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logViewerApiBaseUrl",
|
||||
"PostSavedSearch"), { 'name': name, 'query': query }),
|
||||
'Failed to add new saved search');
|
||||
},
|
||||
|
||||
deleteSavedSearch: function (name, query) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logViewerApiBaseUrl",
|
||||
"DeleteSavedSearch"), { 'name': name, 'query': query }),
|
||||
'Failed to delete saved search');
|
||||
},
|
||||
|
||||
getLogs: function (options) {
|
||||
|
||||
var defaults = {
|
||||
pageSize: 100,
|
||||
pageNumber: 1,
|
||||
orderDirection: "Descending",
|
||||
filterExpression: ''
|
||||
};
|
||||
|
||||
if (options === undefined) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
//overwrite the defaults if there are any specified
|
||||
angular.extend(defaults, options);
|
||||
|
||||
//now copy back to the options we will use
|
||||
options = defaults;
|
||||
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logViewerApiBaseUrl",
|
||||
"GetLogs",
|
||||
options)),
|
||||
'Failed to retrieve common log messages');
|
||||
},
|
||||
|
||||
canViewLogs: function () {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logViewerApiBaseUrl",
|
||||
"GetCanViewLogs")),
|
||||
'Failed to retrieve state if logs can be viewed');
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('umbraco.resources').factory('logViewerResource', logViewerResource);
|
||||
@@ -27,9 +27,10 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve
|
||||
});
|
||||
|
||||
//A list of query strings defined that when changed will not cause a reload of the route
|
||||
var nonRoutingQueryStrings = ["mculture", "cculture"];
|
||||
var retainedQueryStrings = ['mculture'];
|
||||
var nonRoutingQueryStrings = ["mculture", "cculture", "lq"];
|
||||
var retainedQueryStrings = ["mculture"];
|
||||
|
||||
|
||||
function setMode(mode) {
|
||||
switch (mode) {
|
||||
case 'tree':
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<div>
|
||||
<p>
|
||||
<strong>Query:</strong><br/>
|
||||
{{ model.query }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Name:</strong><br/>
|
||||
<input ng-model="model.name" type="text" name="queryName" ng-change="model.disableSubmitButton = model.name.length === 0"/>
|
||||
</p>
|
||||
</div>
|
||||
@@ -1,18 +1,5 @@
|
||||
<div class="umb-editors">
|
||||
|
||||
<!-- FIXME -->
|
||||
<!--
|
||||
For some reason I can't get the animation to work when we use the umb-editor directive.
|
||||
The the template isn't rendered quick enough and we can't access the css classes inside the directive
|
||||
I have also tried with the animation service but I can't get the animations to run when the ng-repeat is on the umb-editor directive
|
||||
|
||||
<umb-editor
|
||||
ng-repeat="editor in editors"
|
||||
model="editor">
|
||||
</umb-editor>
|
||||
-->
|
||||
|
||||
<!-- Temp solution -->
|
||||
<div class="umb-editor" ng-repeat="model in editors" ng-style="model.style" ng-class="{'umb-editor--small': model.size === 'small', 'umb-editor--animating': model.animating}">
|
||||
<div ng-if="!model.view && !model.animating" ng-transclude></div>
|
||||
<div ng-if="model.view && !model.animating" ng-include="model.view"></div>
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function LogViewerOverviewController($q, $location, logViewerResource) {
|
||||
|
||||
var vm = this;
|
||||
vm.loading = false;
|
||||
vm.canLoadLogs = false;
|
||||
vm.searches = [];
|
||||
vm.numberOfErrors = 0;
|
||||
vm.commonLogMessages = [];
|
||||
vm.commonLogMessagesCount = 10;
|
||||
|
||||
// ChartJS Options - for count/overview of log distribution
|
||||
vm.logTypeLabels = ["Info", "Debug", "Warning", "Error", "Critical"];
|
||||
vm.logTypeData = [0, 0, 0, 0, 0];
|
||||
vm.logTypeColors = [ '#dcdcdc', '#97bbcd', '#46bfbd', '#fdb45c', '#f7464a'];
|
||||
vm.chartOptions = {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'left'
|
||||
}
|
||||
};
|
||||
|
||||
//functions
|
||||
vm.searchLogQuery = searchLogQuery;
|
||||
vm.findMessageTemplate = findMessageTemplate;
|
||||
|
||||
function preFlightCheck(){
|
||||
vm.loading = true;
|
||||
|
||||
//Do our pre-flight check (to see if we can view logs)
|
||||
//IE the log file is NOT too big such as 1GB & crash the site
|
||||
logViewerResource.canViewLogs().then(function(result){
|
||||
vm.loading = false;
|
||||
vm.canLoadLogs = result;
|
||||
|
||||
if(result){
|
||||
//Can view logs - so initalise
|
||||
init();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function init() {
|
||||
|
||||
vm.loading = true;
|
||||
|
||||
var savedSearches = logViewerResource.getSavedSearches().then(function (data) {
|
||||
vm.searches = data;
|
||||
},
|
||||
// fallback to some defaults if error from API response
|
||||
function () {
|
||||
vm.searches = [
|
||||
{
|
||||
"name": "Find all logs where the Level is NOT Verbose and NOT Debug",
|
||||
"query": "Not(@Level='Verbose') and Not(@Level='Debug')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)",
|
||||
"query": "Has(@Exception)"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that have the property 'Duration'",
|
||||
"query": "Has(Duration)"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms",
|
||||
"query": "Has(Duration) and Duration > 1000"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that are from the namespace 'Umbraco.Core'",
|
||||
"query": "StartsWith(SourceContext, 'Umbraco.Core')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that use a specific log message template",
|
||||
"query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
var numOfErrors = logViewerResource.getNumberOfErrors().then(function (data) {
|
||||
vm.numberOfErrors = data;
|
||||
});
|
||||
|
||||
var logCounts = logViewerResource.getLogLevelCounts().then(function (data) {
|
||||
vm.logTypeData = [];
|
||||
vm.logTypeData.push(data.Information);
|
||||
vm.logTypeData.push(data.Debug);
|
||||
vm.logTypeData.push(data.Warning);
|
||||
vm.logTypeData.push(data.Error);
|
||||
vm.logTypeData.push(data.Fatal);
|
||||
});
|
||||
|
||||
var commonMsgs = logViewerResource.getMessageTemplates().then(function(data){
|
||||
vm.commonLogMessages = data;
|
||||
});
|
||||
|
||||
//Set loading indicatior to false when these 3 queries complete
|
||||
$q.all([savedSearches, numOfErrors, logCounts, commonMsgs]).then(function(data) {
|
||||
vm.loading = false;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function searchLogQuery(logQuery){
|
||||
$location.path("/settings/logViewer/search").search({lq: logQuery});
|
||||
}
|
||||
|
||||
function findMessageTemplate(template){
|
||||
var logQuery = "@MessageTemplate='" + template.MessageTemplate + "'";
|
||||
searchLogQuery(logQuery);
|
||||
}
|
||||
|
||||
preFlightCheck();
|
||||
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.Editors.LogViewer.OverviewController", LogViewerOverviewController);
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,96 @@
|
||||
<div data-element="editor-logs" ng-controller="Umbraco.Editors.LogViewer.OverviewController as vm" class="clearfix">
|
||||
|
||||
<umb-editor-view footer="false">
|
||||
|
||||
<umb-editor-header
|
||||
name="'Log Overview for Today'"
|
||||
name-locked="true"
|
||||
hide-icon="true"
|
||||
hide-description="true"
|
||||
hide-alias="true">
|
||||
</umb-editor-header>
|
||||
|
||||
<umb-editor-container>
|
||||
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
|
||||
|
||||
<!-- Warning message (if unable to view log files) -->
|
||||
<div ng-show="!vm.loading && !vm.canLoadLogs">
|
||||
<umb-box>
|
||||
<umb-box-header title="Unable to view logs"/>
|
||||
<umb-box-content>
|
||||
<p>Today's log file is too large to be viewed and would cause performance problems.</p>
|
||||
<p>If you need to view the log files, try opening them manually</p>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details" ng-show="!vm.loading && vm.canLoadLogs">
|
||||
<div class="umb-package-details__main-content">
|
||||
<!-- Saved Searches -->
|
||||
<umb-box>
|
||||
<umb-box-header>Saved Searches</umb-box-header>
|
||||
<umb-box-content>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<a ng-click="vm.searchLogQuery()" title="View all Logs" class="btn btn-link">All Logs <i class="icon-search"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Fetch saved searches -->
|
||||
<tr ng-repeat="search in vm.searches">
|
||||
<td>
|
||||
<a ng-click="vm.searchLogQuery(search.query)" title="{{search.name}}" class="btn btn-link">{{search.name}} <i class="icon-search"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
<!-- List of top X common log messages -->
|
||||
<umb-box>
|
||||
<umb-box-header>Common Log Messages</umb-box-header>
|
||||
<umb-box-content class="block-form">
|
||||
<em>Total Unique Message types</em>: {{ vm.commonLogMessages.length }}
|
||||
<table class="table table-hover">
|
||||
<tbody>
|
||||
<tr ng-repeat="template in vm.commonLogMessages | limitTo:vm.commonLogMessagesCount" ng-click="vm.findMessageTemplate(template)" style="cursor: pointer;">
|
||||
<td>
|
||||
{{ template.MessageTemplate }}
|
||||
</td>
|
||||
<td>
|
||||
{{ template.Count }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<a class="btn btn-primary" ng-if="vm.commonLogMessagesCount < vm.commonLogMessages.length" ng-click="vm.commonLogMessagesCount = vm.commonLogMessagesCount +10">Show More</a>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__sidebar">
|
||||
<!-- No of Errors -->
|
||||
<umb-box ng-click="vm.searchLogQuery('Has(@Exception)')" style="cursor:pointer;">
|
||||
<umb-box-header>Number of Errors</umb-box-header>
|
||||
<umb-box-content class="block-form" style="font-size: 40px; font-weight:900; text-align:center; color:#fe6561;">
|
||||
{{ vm.numberOfErrors }}
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
<!-- Chart of diff log types -->
|
||||
<umb-box>
|
||||
<umb-box-header>Log Types</umb-box-header>
|
||||
<umb-box-content class="block-form">
|
||||
|
||||
<canvas chart-doughnut
|
||||
chart-data="vm.logTypeData"
|
||||
chart-labels="vm.logTypeLabels"
|
||||
chart-colors="vm.logTypeColors"
|
||||
chart-options="vm.chartOptions"></canvas>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
</div>
|
||||
</div>
|
||||
</umb-editor-container>
|
||||
</umb-editor-view>
|
||||
</div>
|
||||
@@ -0,0 +1,324 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function LogViewerSearchController($location, logViewerResource, overlayService) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.loading = false;
|
||||
vm.logsLoading = false;
|
||||
|
||||
vm.showBackButton = true;
|
||||
vm.page = {};
|
||||
|
||||
vm.logLevels = [
|
||||
{
|
||||
name: 'Verbose',
|
||||
logTypeColor: 'gray'
|
||||
},
|
||||
{
|
||||
name: 'Debug',
|
||||
logTypeColor: 'secondary'
|
||||
},
|
||||
{
|
||||
name: 'Information',
|
||||
logTypeColor: 'primary'
|
||||
},
|
||||
{
|
||||
name: 'Warning',
|
||||
logTypeColor: 'warning'
|
||||
},
|
||||
{
|
||||
name: 'Error',
|
||||
logTypeColor: 'danger'
|
||||
},
|
||||
{
|
||||
name: 'Fatal',
|
||||
logTypeColor: 'danger'
|
||||
}
|
||||
];
|
||||
|
||||
vm.searches = [];
|
||||
|
||||
vm.logItems = {};
|
||||
vm.logOptions = {};
|
||||
vm.logOptions.orderDirection = 'Descending';
|
||||
|
||||
vm.fromDatePickerConfig = {
|
||||
pickDate: true,
|
||||
pickTime: true,
|
||||
useSeconds: false,
|
||||
useCurrent: false,
|
||||
format: "YYYY-MM-DD HH:mm",
|
||||
icons: {
|
||||
time: "icon-time",
|
||||
date: "icon-calendar",
|
||||
up: "icon-chevron-up",
|
||||
down: "icon-chevron-down"
|
||||
}
|
||||
};
|
||||
|
||||
vm.toDatePickerConfig = {
|
||||
pickDate: true,
|
||||
pickTime: true,
|
||||
useSeconds: false,
|
||||
format: "YYYY-MM-DD HH:mm",
|
||||
icons: {
|
||||
time: "icon-time",
|
||||
date: "icon-calendar",
|
||||
up: "icon-chevron-up",
|
||||
down: "icon-chevron-down"
|
||||
}
|
||||
};
|
||||
|
||||
//Functions
|
||||
vm.getLogs = getLogs;
|
||||
vm.changePageNumber = changePageNumber;
|
||||
vm.search = search;
|
||||
vm.getFilterName = getFilterName;
|
||||
vm.setLogLevelFilter = setLogLevelFilter;
|
||||
vm.toggleOrderBy = toggleOrderBy;
|
||||
vm.selectSearch = selectSearch;
|
||||
vm.resetSearch = resetSearch;
|
||||
vm.findItem = findItem;
|
||||
vm.checkForSavedSearch = checkForSavedSearch;
|
||||
vm.addToSavedSearches = addToSavedSearches;
|
||||
vm.deleteSavedSearch = deleteSavedSearch;
|
||||
vm.back = back;
|
||||
|
||||
|
||||
function init() {
|
||||
|
||||
//If we have a Querystring set for lq (log query)
|
||||
//Then update vm.logOptions.filterExpression
|
||||
var querystring = $location.search();
|
||||
if(querystring.lq){
|
||||
vm.logOptions.filterExpression = querystring.lq;
|
||||
}
|
||||
|
||||
vm.loading = true;
|
||||
|
||||
logViewerResource.getSavedSearches().then(function (data) {
|
||||
vm.searches = data;
|
||||
vm.loading = false;
|
||||
},
|
||||
// fallback to some defaults if error from API response
|
||||
function () {
|
||||
vm.searches = [
|
||||
{
|
||||
"name": "Find all logs where the Level is NOT Verbose and NOT Debug",
|
||||
"query": "Not(@Level='Verbose') and Not(@Level='Debug')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)",
|
||||
"query": "Has(@Exception)"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that have the property 'Duration'",
|
||||
"query": "Has(Duration)"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms",
|
||||
"query": "Has(Duration) and Duration > 1000"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that are from the namespace 'Umbraco.Core'",
|
||||
"query": "StartsWith(SourceContext, 'Umbraco.Core')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that use a specific log message template",
|
||||
"query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
//Get all logs on init load
|
||||
getLogs();
|
||||
}
|
||||
|
||||
|
||||
function search(){
|
||||
//Update the querystring lq (log query)
|
||||
$location.search('lq', vm.logOptions.filterExpression);
|
||||
|
||||
//Reset pagenumber back to 1
|
||||
vm.logOptions.pageNumber = 1;
|
||||
getLogs();
|
||||
}
|
||||
|
||||
function changePageNumber(pageNumber) {
|
||||
vm.logOptions.pageNumber = pageNumber;
|
||||
getLogs();
|
||||
}
|
||||
|
||||
function getLogs(){
|
||||
vm.logsLoading = true;
|
||||
|
||||
logViewerResource.getLogs(vm.logOptions).then(function (data) {
|
||||
vm.logItems = data;
|
||||
vm.logsLoading = false;
|
||||
|
||||
setLogTypeColor(vm.logItems.items);
|
||||
}, function(err){
|
||||
vm.logsLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
function setLogTypeColor(logItems) {
|
||||
angular.forEach(logItems, function (log) {
|
||||
switch (log.Level) {
|
||||
case "Information":
|
||||
log.logTypeColor = "primary";
|
||||
break;
|
||||
case "Debug":
|
||||
log.logTypeColor = "secondary";
|
||||
break;
|
||||
case "Warning":
|
||||
log.logTypeColor = "warning";
|
||||
break;
|
||||
case "Fatal":
|
||||
case "Error":
|
||||
log.logTypeColor = "danger";
|
||||
break;
|
||||
default:
|
||||
log.logTypeColor = "gray";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getFilterName(array) {
|
||||
var name = "All";
|
||||
var found = false;
|
||||
angular.forEach(array, function (item) {
|
||||
if (item.selected) {
|
||||
if (!found) {
|
||||
name = item.name
|
||||
found = true;
|
||||
} else {
|
||||
name = name + ", " + item.name;
|
||||
}
|
||||
}
|
||||
});
|
||||
return name;
|
||||
}
|
||||
|
||||
function setLogLevelFilter(logLevel) {
|
||||
|
||||
if (!vm.logOptions.logLevels) {
|
||||
vm.logOptions.logLevels = [];
|
||||
}
|
||||
|
||||
if (logLevel.selected) {
|
||||
vm.logOptions.logLevels.push(logLevel.name);
|
||||
} else {
|
||||
var index = vm.logOptions.logLevels.indexOf(logLevel.name);
|
||||
vm.logOptions.logLevels.splice(index, 1);
|
||||
}
|
||||
|
||||
getLogs();
|
||||
}
|
||||
|
||||
function toggleOrderBy(){
|
||||
vm.logOptions.orderDirection = vm.logOptions.orderDirection === 'Descending' ? 'Ascending' : 'Descending';
|
||||
|
||||
getLogs();
|
||||
}
|
||||
|
||||
function selectSearch(searchItem){
|
||||
//Update search box input
|
||||
vm.logOptions.filterExpression = searchItem.query;
|
||||
vm.dropdownOpen = false;
|
||||
|
||||
search();
|
||||
}
|
||||
|
||||
function resetSearch(){
|
||||
vm.logOptions.filterExpression = '';
|
||||
search();
|
||||
}
|
||||
|
||||
function findItem(key, value){
|
||||
if(isNaN(value)){
|
||||
vm.logOptions.filterExpression = key + "='" + value + "'";
|
||||
}
|
||||
else {
|
||||
vm.logOptions.filterExpression = key + "=" + value;
|
||||
}
|
||||
|
||||
search();
|
||||
}
|
||||
|
||||
//Return a bool to toggle display of the star/fav
|
||||
function checkForSavedSearch(){
|
||||
//Check if we have a value in
|
||||
if(!vm.logOptions.filterExpression){
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
//Check what we have searched for is not an existing saved search
|
||||
var findQuery = _.findWhere(vm.searches, {query: vm.logOptions.filterExpression});
|
||||
return !findQuery ? true: false;
|
||||
}
|
||||
}
|
||||
|
||||
function addToSavedSearches(){
|
||||
|
||||
var overlay = {
|
||||
title: "Save Search",
|
||||
subtitle: "Enter a friendly name for your search query",
|
||||
closeButtonLabel: "Cancel",
|
||||
submitButtonLabel: "Save Search",
|
||||
disableSubmitButton: true,
|
||||
view: "logviewersearch",
|
||||
query: vm.logOptions.filterExpression,
|
||||
submit: function (model) {
|
||||
//Resource call with two params (name & query)
|
||||
//API that opens the JSON and adds it to the bottom
|
||||
logViewerResource.postSavedSearch(model.name, model.query).then(function(data){
|
||||
vm.searches = data;
|
||||
overlayService.close();
|
||||
});
|
||||
},
|
||||
close: function() {
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
|
||||
overlayService.open(overlay);
|
||||
}
|
||||
|
||||
function deleteSavedSearch(searchItem) {
|
||||
|
||||
var overlay = {
|
||||
title: "Delete Search",
|
||||
subtitle: "Are you sure you wish to delete",
|
||||
closeButtonLabel: "Cancel",
|
||||
submitButtonLabel: "Delete Search",
|
||||
view: "default",
|
||||
submit: function (model) {
|
||||
//Resource call with two params (name & query)
|
||||
//API that opens the JSON and adds it to the bottom
|
||||
logViewerResource.deleteSavedSearch(searchItem.name, searchItem.query).then(function(data){
|
||||
vm.searches = data;
|
||||
overlayService.close();
|
||||
});
|
||||
},
|
||||
close: function() {
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
|
||||
overlayService.open(overlay);
|
||||
}
|
||||
|
||||
function back() {
|
||||
$location.path("settings/logViewer/overview").search('lq', null);
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.Editors.LogViewer.SearchController", LogViewerSearchController);
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,200 @@
|
||||
<div data-element="editor-logs" ng-controller="Umbraco.Editors.LogViewer.SearchController as vm" class="clearfix">
|
||||
|
||||
<umb-editor-view footer="false">
|
||||
|
||||
<umb-editor-header
|
||||
name="'Log Search'"
|
||||
name-locked="true"
|
||||
on-back="vm.back()"
|
||||
show-back-button="vm.showBackButton"
|
||||
hide-icon="true"
|
||||
hide-description="true"
|
||||
hide-alias="true">
|
||||
</umb-editor-header>
|
||||
|
||||
<umb-editor-container>
|
||||
|
||||
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
|
||||
|
||||
<form ng-submit="vm.search()">
|
||||
<umb-editor-sub-header>
|
||||
<umb-editor-sub-header-content-left style="width:100%; line-height:30px;">
|
||||
<!-- Log Level filter -->
|
||||
<div style="position: relative;">
|
||||
<a class="btn btn-link dropdown-toggle flex" href="" ng-click="vm.page.showLevelFilter = !vm.page.showLevelFilter">
|
||||
<span>Log Levels:</span>
|
||||
<span class="bold truncate dib" style="margin-left: 5px; margin-right: 3px; max-width: 150px;">{{ vm.getFilterName(vm.logLevels) }}</span>
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<umb-dropdown class="pull-right" ng-if="vm.page.showLevelFilter" on-close="vm.page.showLevelFilter = false;">
|
||||
<umb-dropdown-item ng-repeat="level in vm.logLevels" style="padding: 8px 20px 8px 16px;">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="loglevel-{{$index}}"
|
||||
type="checkbox"
|
||||
ng-model="level.selected"
|
||||
ng-change="vm.setLogLevelFilter(level)"
|
||||
style="margin-right: 10px; margin-top: -3px;" />
|
||||
<label for="loglevel-{{$index}}">
|
||||
<umb-badge size="s" color="{{ level.logTypeColor }}">{{ level.name }}</umb-badge>
|
||||
</label>
|
||||
</div>
|
||||
</umb-dropdown-item>
|
||||
</umb-dropdown>
|
||||
</div>
|
||||
|
||||
</umb-editor-sub-header-content-left>
|
||||
</umb-editor-sub-header>
|
||||
|
||||
<umb-editor-sub-header>
|
||||
<umb-editor-sub-header-content-left style="width:100%">
|
||||
|
||||
<div style="position:relative; width:100%;">
|
||||
<!-- Search/expression filter -->
|
||||
<input class="form-control search-input" type="text" ng-model="vm.logOptions.filterExpression" style="width:100%;" placeholder="Search logs…" />
|
||||
|
||||
<!-- Save Search & Clear Search icon buttons -->
|
||||
<ins class="icon-rate" ng-if="vm.checkForSavedSearch()" ng-click="vm.addToSavedSearches()" style="float: left; position: absolute; top: 0; line-height: 32px; right: 160px; color: #fdb45c; cursor: pointer;"> </ins>
|
||||
<ins class="icon-wrong" ng-if="vm.logOptions.filterExpression" ng-click="vm.resetSearch()" style="float: left; position: absolute; top: 0; line-height: 32px; right: 140px; color: #bbbabf; cursor: pointer;"> </ins>
|
||||
|
||||
<a class="umb-variant-switcher__toggle ng-scope" href="" ng-click="vm.dropdownOpen = !vm.dropdownOpen" style="top:0;">
|
||||
<span class="ng-binding">Example Searches</span>
|
||||
<ins class="umb-variant-switcher__expand icon-navigation-down" ng-class="{'icon-navigation-down': !vm.dropdownOpen, 'icon-navigation-up': vm.dropdownOpen}"> </ins>
|
||||
</a>
|
||||
|
||||
<!-- Common Searches Dropdown -->
|
||||
<umb-dropdown ng-if="vm.dropdownOpen" style="width: 100%; max-height: 250px; overflow-y: scroll; margin-top: -10px;" on-close="vm.dropdownOpen = false" umb-keyboard-list>
|
||||
<umb-dropdown-item class="umb-variant-switcher__item" ng-class="{'umb-variant-switcher_item--current': variant.active}" ng-repeat="search in vm.searches">
|
||||
<a href="" class="umb-variant-switcher__name-wrapper" ng-click="vm.selectSearch(search)" prevent-default>
|
||||
<span class="umb-variant-switcher__name">{{search.name}}</span>
|
||||
<span>{{ search.query }}</span>
|
||||
</a>
|
||||
<a href=""><span><i class="icon icon-trash text-error" title="Delete this search" ng-click="vm.deleteSavedSearch(search)"></i></span></a>
|
||||
</umb-dropdown-item>
|
||||
</umb-dropdown>
|
||||
</div>
|
||||
|
||||
<!-- Search Button -->
|
||||
<umb-button
|
||||
button-style="button"
|
||||
type="submit"
|
||||
action="vm.search()"
|
||||
label-key="general_search">
|
||||
</umb-button>
|
||||
|
||||
</umb-editor-sub-header-content-left>
|
||||
</umb-editor-sub-header>
|
||||
</form>
|
||||
|
||||
<div class=" ng-if="!vm.loading">
|
||||
|
||||
<div class="">
|
||||
|
||||
<!-- Loader for the main logs content when paging -->
|
||||
<umb-load-indicator ng-if="vm.logsLoading"></umb-load-indicator>
|
||||
|
||||
<!-- Empty states -->
|
||||
<umb-empty-state ng-if="vm.logItems.totalItems === 0" position="center">
|
||||
<localize key="general_searchNoResult"></localize>
|
||||
</umb-empty-state>
|
||||
|
||||
<!-- Main Log Table -->
|
||||
<umb-box data-element="node-info-history" ng-if="!vm.logsLoading && vm.logItems.totalItems > 0">
|
||||
<umb-box-content class="block-form">
|
||||
|
||||
<div ng-if="vm.logItems.totalItems > 0">
|
||||
Total Items: {{ vm.logItems.totalItems }}
|
||||
</div>
|
||||
|
||||
<table class="table table-hover" ng-if="vm.logItems.totalItems > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="min-width:100px;" ng-click="vm.toggleOrderBy()">
|
||||
Timestamp
|
||||
<ins class="icon-navigation-down" ng-class="{'icon-navigation-down': vm.logOptions.orderDirection === 'Descending', 'icon-navigation-up': vm.logOptions.orderDirection !== 'Descending'}"> </ins>
|
||||
</th>
|
||||
<th>Level</th>
|
||||
<th>Machine</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat-start="log in vm.logItems.items" ng-click="log.open = !log.open">
|
||||
<td>{{ log.Timestamp | date:'medium' }}</td>
|
||||
<td><umb-badge size="s" color="{{ log.logTypeColor }}">{{ log.Level }}</umb-badge></td>
|
||||
<td><small>{{ log.Properties.MachineName.Value }}</small></td>
|
||||
<td>{{ log.RenderedMessage }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Log Details (Exception & Properties) -->
|
||||
<tr ng-repeat-end ng-if="log.open">
|
||||
<td colspan="4">
|
||||
<div ng-if="log.Exception" style="border-left:4px solid #fe6561; padding:0 10px 10px 10px; box-shadow:rgba(0,0,0,0.07) 2px 2px 10px">
|
||||
<h3 style="color:#fe6561;">Exception</h3>
|
||||
<p style="white-space: pre-wrap;">{{ log.Exception }}</p>
|
||||
</div>
|
||||
|
||||
<h3>Properties</h3>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Timestamp</th>
|
||||
<td>{{log.Timestamp}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>@MessageTemplate</th>
|
||||
<td>{{log.MessageTemplateText}}</td>
|
||||
</tr>
|
||||
<tr ng-repeat="(key, val) in log.Properties">
|
||||
<th>{{key}}</th>
|
||||
<td ng-switch on="key">
|
||||
<a ng-switch-when="HttpRequestNumber" ng-click="vm.findItem(key, val.Value)" title="Find Logs with Request ID">{{val.Value}} <i class="icon-search"></i></a>
|
||||
<a ng-switch-when="SourceContext" ng-click="vm.findItem(key, val.Value)" title="Find Logs with Namespace">{{val.Value}} <i class="icon-search"></i></a>
|
||||
<a ng-switch-when="MachineName" ng-click="vm.findItem(key, val.Value)" title="Find Logs with Machine Name">{{val.Value}} <i class="icon-search"></i></a>
|
||||
<a ng-switch-when="RequestUrl" href="{{val.Value}}" target="_blank" rel="noopener" title="Open">{{val.Value}} <i class="icon-link"></i></a>
|
||||
<span ng-switch-default>{{val.Value}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-info dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="icon-search"></i> Search
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a ng-href="https://www.google.com/search?q={{ log.RenderedMessage }}" target="_blank" title="Search this message with Google"><img src="https://www.google.com/favicon.ico" width="16" height="16" /> Search With Google</a></li>
|
||||
<li><a ng-href="https://www.bing.com/search?q={{ log.RenderedMessage }}" target="_blank" title="Search this message with Bing"><img src="https://www.bing.com/favicon.ico" width="16" height="16" /> Search With Bing</a></li>
|
||||
<li><a ng-href="https://our.umbraco.com/search?q={{ log.RenderedMessage }}&content=wiki,forum,documentation" target="_blank" title="Search this message on Our Umbraco forums and docs"><img src="https://our.umbraco.com/assets/images/app-icons/favicon.png" width="16" height="16" /> Search Our Umbraco</a></li>
|
||||
<li><a ng-href="https://www.google.co.uk/?q=site:our.umbraco.org {{ log.RenderedMessage }}&safe=off#q=site:our.umbraco.org {{ log.RenderedMessage }} {{ log.Properties['SourceContext'].Value }}&safe=off" target="_blank" title="Search Our Umbraco forums using Google"><img src="https://www.google.com/favicon.ico" width="16" height="16" /> Search Our with Google</a></li>
|
||||
<li><a ng-href="https://github.com/umbraco/Umbraco-CMS/search?q={{ log.Properties['SourceContext'].Value }}" target="_blank" title="Search within Umbraco source code on Github"><img src="https://www.github.com/favicon.ico" width="16" height="16" /> Search Umbraco Source</a></li>
|
||||
<li><a ng-href="https://github.com/umbraco/Umbraco-CMS/issues?q={{ log.Properties['SourceContext'].Value }}" target="_blank" title="Search Umbraco Issues on Github"><img src="https://www.github.com/favicon.ico" width="16" height="16" /> Search Umbraco Issues</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div ng-if="!vm.loading" class="flex justify-center">
|
||||
<umb-pagination
|
||||
ng-if="vm.logItems.totalPages"
|
||||
page-number="vm.logItems.pageNumber"
|
||||
total-pages="vm.logItems.totalPages"
|
||||
on-change="vm.changePageNumber(pageNumber)">
|
||||
</umb-pagination>
|
||||
</div>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</umb-editor-container>
|
||||
|
||||
</umb-editor-view>
|
||||
|
||||
</div>
|
||||
@@ -236,6 +236,7 @@
|
||||
<Content Include="Config\serilog.config">
|
||||
<SubType>Designer</SubType>
|
||||
</Content>
|
||||
<Content Include="Config\logviewer.searches.config.js" />
|
||||
<None Include="Config\umbracoSettings.Release.config">
|
||||
<DependentUpon>umbracoSettings.config</DependentUpon>
|
||||
<SubType>Designer</SubType>
|
||||
|
||||
@@ -1680,6 +1680,7 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="scripts">Scripts</key>
|
||||
<key alias="stylesheets">Stylesheets</key>
|
||||
<key alias="templates">Templates</key>
|
||||
<key alias="logViewer">Log Viewer</key>
|
||||
<key alias="users">Users</key>
|
||||
<key alias="settingsGroup">Settings</key>
|
||||
<key alias="templatingGroup">Templating</key>
|
||||
|
||||
@@ -115,8 +115,6 @@ namespace Umbraco.Web.UI.Umbraco.Developer.Macros
|
||||
|
||||
protected IEnumerable<IDataEditor> GetMacroParameterEditors()
|
||||
{
|
||||
// we need to show the depracated ones for backwards compatibility
|
||||
// FIXME not managing deprecated here?!
|
||||
return Current.ParameterEditors;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
[
|
||||
{
|
||||
"name": "Find all logs where the Level is NOT Verbose and NOT Debug",
|
||||
"query": "Not(@Level='Verbose') and Not(@Level='Debug')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)",
|
||||
"query": "Has(@Exception)"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that have the property 'Duration'",
|
||||
"query": "Has(Duration)"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms",
|
||||
"query": "Has(Duration) and Duration > 1000"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that are from the namespace 'Umbraco.Core'",
|
||||
"query": "StartsWith(SourceContext, 'Umbraco.Core')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that use a specific log message template",
|
||||
"query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'"
|
||||
},
|
||||
{
|
||||
"name": "Find logs where one of the items in the SortedComponentTypes property array is equal to",
|
||||
"query": "SortedComponentTypes[?] = 'Umbraco.Web.Search.ExamineComponent'"
|
||||
},
|
||||
{
|
||||
"name": "Find logs where one of the items in the SortedComponentTypes property array contains",
|
||||
"query": "Contains(SortedComponentTypes[?], 'DatabaseServer')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that the message has localhost in it with SQL like",
|
||||
"query": "@Message like '%localhost%'"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that the message that starts with 'end' in it with SQL like",
|
||||
"query": "@Message like 'end%'"
|
||||
}
|
||||
]
|
||||
@@ -310,8 +310,12 @@ namespace Umbraco.Web.Editors
|
||||
controller => controller.GetAllLanguages())
|
||||
},
|
||||
{
|
||||
"relationTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<RelationTypeController>(
|
||||
"relationTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<RelationTypeController>(
|
||||
controller => controller.GetById(1))
|
||||
},
|
||||
{
|
||||
"logViewerApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<LogViewerController>(
|
||||
controller => controller.GetNumberOfErrors())
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Web.Http;
|
||||
using Umbraco.Core.Logging.Viewer;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Backoffice controller supporting the dashboard for viewing logs with some simple graphs & filtering
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
public class LogViewerController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private ILogViewer _logViewer;
|
||||
|
||||
public LogViewerController(ILogViewer logViewer)
|
||||
{
|
||||
_logViewer = logViewer;
|
||||
}
|
||||
|
||||
private bool CanViewLogs()
|
||||
{
|
||||
//Can the interface deal with Large Files
|
||||
if (_logViewer.CanHandleLargeLogs)
|
||||
return true;
|
||||
|
||||
//Interface CheckCanOpenLogs
|
||||
return _logViewer.CheckCanOpenLogs(startDate: DateTime.Now.AddDays(-1), endDate: DateTime.Now);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public bool GetCanViewLogs()
|
||||
{
|
||||
return CanViewLogs();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public int GetNumberOfErrors()
|
||||
{
|
||||
//We will need to stop the request if trying to do this on a 1GB file
|
||||
if (CanViewLogs() == false)
|
||||
{
|
||||
throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Unable to view logs, due to size"));
|
||||
}
|
||||
|
||||
return _logViewer.GetNumberOfErrors(startDate: DateTime.Now.AddDays(-1), endDate: DateTime.Now);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public LogLevelCounts GetLogLevelCounts()
|
||||
{
|
||||
//We will need to stop the request if trying to do this on a 1GB file
|
||||
if (CanViewLogs() == false)
|
||||
{
|
||||
throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Unable to view logs, due to size"));
|
||||
}
|
||||
|
||||
return _logViewer.GetLogLevelCounts(startDate: DateTime.Now.AddDays(-1), endDate: DateTime.Now);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IEnumerable<LogTemplate> GetMessageTemplates()
|
||||
{
|
||||
//We will need to stop the request if trying to do this on a 1GB file
|
||||
if (CanViewLogs() == false)
|
||||
{
|
||||
throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Unable to view logs, due to size"));
|
||||
}
|
||||
|
||||
return _logViewer.GetMessageTemplates(startDate: DateTime.Now.AddDays(-1), endDate: DateTime.Now);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public PagedResult<LogMessage> GetLogs(string orderDirection = "Descending", int pageNumber = 1, string filterExpression = null, [FromUri]string[] logLevels = null)
|
||||
{
|
||||
//We will need to stop the request if trying to do this on a 1GB file
|
||||
if (CanViewLogs() == false)
|
||||
{
|
||||
throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Unable to view logs, due to size"));
|
||||
}
|
||||
|
||||
var direction = orderDirection == "Descending" ? Direction.Descending : Direction.Ascending;
|
||||
return _logViewer.GetLogs(startDate: DateTime.Now.AddDays(-1), endDate: DateTime.Now, filterExpression: filterExpression, pageNumber: pageNumber, orderDirection: direction, logLevels: logLevels);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IEnumerable<SavedLogSearch> GetSavedSearches()
|
||||
{
|
||||
return _logViewer.GetSavedSearches();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public IEnumerable<SavedLogSearch> PostSavedSearch(SavedLogSearch item)
|
||||
{
|
||||
return _logViewer.AddSavedSearch(item.Name, item.Query);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public IEnumerable<SavedLogSearch> DeleteSavedSearch(SavedLogSearch item)
|
||||
{
|
||||
return _logViewer.DeleteSavedSearch(item.Name, item.Query);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,8 +273,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
/// <inheritdoc />
|
||||
public override PublishedItemType ItemType => _contentNode.ContentType.ItemType;
|
||||
|
||||
// fixme
|
||||
// was => _contentData.Published == false;
|
||||
/// <inheritdoc />
|
||||
public override bool IsDraft(string culture = null)
|
||||
{
|
||||
|
||||
@@ -201,7 +201,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
private void InitializeRepositoryEvents()
|
||||
{
|
||||
//fixme: The reason these events are in the repository is for legacy, the events should exist at the service
|
||||
//todo: The reason these events are in the repository is for legacy, the events should exist at the service
|
||||
// level now since we can fire these events within the transaction... so move the events to service level
|
||||
|
||||
// plug repository event handlers
|
||||
@@ -584,7 +584,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
continue;
|
||||
}
|
||||
|
||||
// fixme - should we do some RV check here? (later)
|
||||
// todo- should we do some RV check here? (later)
|
||||
|
||||
var capture = payload;
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
@@ -674,7 +674,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
continue;
|
||||
}
|
||||
|
||||
// fixme - should we do some RV checks here? (later)
|
||||
// todo- should we do some RV checks here? (later)
|
||||
|
||||
var capture = payload;
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
@@ -773,7 +773,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
using (_contentStore.GetWriter(_scopeProvider))
|
||||
using (_mediaStore.GetWriter(_scopeProvider))
|
||||
{
|
||||
// fixme - need to add a datatype lock
|
||||
// todo - need to add a datatype lock
|
||||
// this is triggering datatypes reload in the factory, and right after we create some
|
||||
// content types by loading them ... there's a race condition here, which would require
|
||||
// some locking on datatypes
|
||||
|
||||
@@ -30,7 +30,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
private Task _collectTask;
|
||||
private volatile int _wlocked;
|
||||
|
||||
// fixme - collection trigger (ok for now)
|
||||
// minGenDelta to be adjusted
|
||||
// we may want to throttle collects even if delta is reached
|
||||
// we may want to force collect if delta is not reached but very old
|
||||
@@ -107,7 +106,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
}
|
||||
|
||||
// gets a scope contextual representing a locked writer to the dictionary
|
||||
// fixme GetScopedWriter? should the dict have a ref onto the scope provider?
|
||||
// GetScopedWriter? should the dict have a ref onto the scope provider?
|
||||
public IDisposable GetWriter(IScopeProvider scopeProvider)
|
||||
{
|
||||
return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new SnapDictionaryWriter(this, scoped));
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Web.PublishedCache
|
||||
//
|
||||
// at the moment we do NOT support models for sets - that would require
|
||||
// an entirely new models factory + not even sure it makes sense at all since
|
||||
// sets are created manually fixme yes it does!
|
||||
// sets are created manually todo yes it does! - what does this all mean?
|
||||
//
|
||||
internal class PublishedElement : IPublishedElement
|
||||
{
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace Umbraco.Web.PublishedCache
|
||||
_membershipUser = member;
|
||||
_publishedMemberType = publishedMemberType ?? throw new ArgumentNullException(nameof(publishedMemberType));
|
||||
|
||||
// fixme
|
||||
// RawValueProperty is used for two things here
|
||||
// - for the 'map properties' thing that we should really get rid of
|
||||
// - for populating properties that every member should always have, and that we force-create
|
||||
@@ -49,8 +48,6 @@ namespace Umbraco.Web.PublishedCache
|
||||
|
||||
#region Membership provider member properties
|
||||
|
||||
// fixme why this?
|
||||
|
||||
public string Email => _membershipUser.Email;
|
||||
|
||||
public string UserName => _membershipUser.Username;
|
||||
|
||||
@@ -5,7 +5,7 @@ using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
{
|
||||
// fixme should be a ScopeContextualBase
|
||||
// todo should be a ScopeContextualBase
|
||||
internal class SafeXmlReaderWriter : IDisposable
|
||||
{
|
||||
private readonly bool _scoped;
|
||||
|
||||
@@ -369,7 +369,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
// fixme - enable!
|
||||
// todo - enable!
|
||||
//content.Instance.RefreshContentFromDatabase();
|
||||
throw new InvalidOperationException($"{e.Message}. This usually indicates that the content cache is corrupt; the content cache has been rebuilt in an attempt to self-fix the issue.");
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
|
||||
// internal for unit tests
|
||||
// no file nor db, no config check
|
||||
// fixme - er, we DO have a DB?
|
||||
// todo - er, we DO have a DB?
|
||||
internal XmlStore(IContentTypeService contentTypeService, IContentService contentService, IScopeProvider scopeProvider, RoutesCache routesCache, PublishedContentTypeCache contentTypeCache,
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor, MainDom mainDom,
|
||||
bool testing, bool enableRepositoryEvents, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IGlobalSettings globalSettings, IEntityXmlSerializer entitySerializer)
|
||||
@@ -602,7 +602,7 @@ AND (umbracoNode.id=@id)";
|
||||
// should we have async versions that would do: ?
|
||||
// var releaser = await _xmlLock.LockAsync();
|
||||
//
|
||||
// fixme - not sure about the "resync current published snapshot" thing here, see 7.6...
|
||||
// todo - not sure about the "resync current published snapshot" thing here, see 7.6...
|
||||
|
||||
// gets a locked safe read access to the main xml
|
||||
private SafeXmlReaderWriter GetSafeXmlReader()
|
||||
@@ -641,7 +641,7 @@ AND (umbracoNode.id=@id)";
|
||||
|
||||
public void EnsureFilePermission()
|
||||
{
|
||||
// FIXME - but do we really have a store, initialized, at that point?
|
||||
// todo - but do we really have a store, initialized, at that point?
|
||||
var filename = _xmlFileName + ".temp";
|
||||
File.WriteAllText(filename, "TEMP");
|
||||
File.Delete(filename);
|
||||
@@ -1559,8 +1559,8 @@ ORDER BY umbracoNode.level, umbracoNode.sortOrder";
|
||||
// need to update the published xml if we're saving the published version,
|
||||
// or having an impact on that version - we update the published xml even when masked
|
||||
|
||||
// fixme - in the repo... either its 'unpublished' and 'publishing', or 'published' and 'published', this has changed!
|
||||
// fixme - what are we serializing really? which properties?
|
||||
// todo - in the repo... either its 'unpublished' and 'publishing', or 'published' and 'published', this has changed!
|
||||
// todo - what are we serializing really? which properties?
|
||||
|
||||
// if not publishing, no change to published xml
|
||||
if (((Content) entity).PublishedState != PublishedState.Publishing)
|
||||
@@ -1669,7 +1669,7 @@ ORDER BY umbracoNode.level, umbracoNode.sortOrder";
|
||||
|
||||
// RepositoryCacheMode.Scoped because we do NOT want to use the L2 cache that may be out-of-sync
|
||||
// hopefully this does not cause issues and we're not nested in another scope w/different mode
|
||||
// fixme - well, guess what?
|
||||
// todo - well, guess what?
|
||||
// original code made sure the repository used no cache
|
||||
// now we're using the Scoped scope cache mode
|
||||
// and then?
|
||||
@@ -2027,7 +2027,7 @@ AND cmsPreviewXml.nodeId IS NULL OR cmsPreviewXml.xml NOT LIKE '% key=""'
|
||||
{
|
||||
// every non-trashed media item should have a corresponding row in cmsContentXml
|
||||
// and that row should have the key="..." attribute
|
||||
// fixme - where's the trashed test here?
|
||||
// todo - where's the trashed test here?
|
||||
|
||||
var mediaObjectType = Constants.ObjectTypes.Media;
|
||||
var db = scope.Database;
|
||||
|
||||
@@ -251,7 +251,7 @@ namespace Umbraco.Web
|
||||
|
||||
public static IEnumerable<PublishedSearchResult> SearchDescendants(this IPublishedContent content, string term, string indexName = null)
|
||||
{
|
||||
//fixme: pass in the IExamineManager
|
||||
//todo inject examine manager
|
||||
|
||||
indexName = string.IsNullOrEmpty(indexName) ? Constants.UmbracoIndexes.ExternalIndexName : indexName;
|
||||
if (!ExamineManager.Instance.TryGetIndex(indexName, out var index))
|
||||
@@ -272,7 +272,7 @@ namespace Umbraco.Web
|
||||
|
||||
public static IEnumerable<PublishedSearchResult> SearchChildren(this IPublishedContent content, string term, string indexName = null)
|
||||
{
|
||||
//fixme: pass in the IExamineManager
|
||||
//todo inject examine manager
|
||||
|
||||
indexName = string.IsNullOrEmpty(indexName) ? Constants.UmbracoIndexes.ExternalIndexName : indexName;
|
||||
if (!ExamineManager.Instance.TryGetIndex(indexName, out var index))
|
||||
@@ -954,7 +954,7 @@ namespace Umbraco.Web
|
||||
/// </remarks>
|
||||
public static IEnumerable<IPublishedContent> Children(this IPublishedContent content, string culture = null)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException(nameof(content)); // fixme wtf is this?
|
||||
if (content == null) throw new ArgumentNullException(nameof(content)); // fixme/task wtf is this?
|
||||
|
||||
return content.Children.Where(x =>
|
||||
{
|
||||
@@ -1017,7 +1017,7 @@ namespace Umbraco.Web
|
||||
/// <summary>
|
||||
/// Gets the first child of the content, of a given content type.
|
||||
/// </summary>
|
||||
public static IPublishedContent FirstChild(this IPublishedContent content, string alias, string culture = null) // fixme oops
|
||||
public static IPublishedContent FirstChild(this IPublishedContent content, string alias, string culture = null) // fixme/task oops
|
||||
{
|
||||
return content.Children(culture,alias).FirstOrDefault();
|
||||
}
|
||||
|
||||
@@ -69,25 +69,6 @@ namespace Umbraco.Web
|
||||
return prop != null && prop.HasValue(culture, segment);
|
||||
}
|
||||
|
||||
// fixme - .Value() refactoring - in progress
|
||||
// missing variations...
|
||||
|
||||
/// <summary>
|
||||
/// Returns one of two strings depending on whether the content has a value for a property identified by its alias.
|
||||
/// </summary>
|
||||
/// <param name="content">The content.</param>
|
||||
/// <param name="alias">The property alias.</param>
|
||||
/// <param name="valueIfTrue">The value to return if the content has a value for the property.</param>
|
||||
/// <param name="valueIfFalse">The value to return if the content has no value for the property.</param>
|
||||
/// <returns>Either <paramref name="valueIfTrue"/> or <paramref name="valueIfFalse"/> depending on whether the content
|
||||
/// has a value for the property identified by the alias.</returns>
|
||||
public static IHtmlString IfValue(this IPublishedElement content, string alias, string valueIfTrue, string valueIfFalse = null)
|
||||
{
|
||||
return content.HasValue(alias)
|
||||
? new HtmlString(valueIfTrue)
|
||||
: new HtmlString(valueIfFalse ?? string.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Value
|
||||
@@ -165,57 +146,6 @@ namespace Umbraco.Web
|
||||
|
||||
#endregion
|
||||
|
||||
#region Value or Umbraco.Field - WORK IN PROGRESS
|
||||
|
||||
// fixme - .Value() refactoring - in progress
|
||||
// trying to reproduce Umbraco.Field so we can get rid of it
|
||||
//
|
||||
// what we want:
|
||||
// - alt aliases
|
||||
// - recursion
|
||||
// - default value
|
||||
// - before & after (if value)
|
||||
//
|
||||
// convertLineBreaks: should be an extension string.ConvertLineBreaks()
|
||||
// stripParagraphs: should be an extension string.StripParagraphs()
|
||||
// format: should use the standard .ToString(format)
|
||||
//
|
||||
// see UmbracoComponentRenderer.Field - which is ugly ;-(
|
||||
|
||||
// recurse first, on each alias (that's how it's done in Field)
|
||||
//
|
||||
// there is no strongly typed recurse, etc => needs to be in ModelsBuilder?
|
||||
|
||||
// that one can only happen in ModelsBuilder as that's where the attributes are defined
|
||||
// the attribute that carries the alias is in ModelsBuilder!
|
||||
//public static TValue Value<TModel, TValue>(this TModel content, Expression<Func<TModel, TValue>> propertySelector, ...)
|
||||
// where TModel : IPublishedElement
|
||||
//{
|
||||
// PropertyInfo pi = GetPropertyFromExpression(propertySelector);
|
||||
// var attr = pi.GetCustomAttribute<ImplementPropertyAttribute>();
|
||||
// var alias = attr.Alias;
|
||||
// return content.Value<TValue>(alias, ...)
|
||||
//}
|
||||
|
||||
// recurse should be implemented via fallback
|
||||
|
||||
// todo - that one should be refactored, missing culture and so many things
|
||||
public static IHtmlString Value<T>(this IPublishedElement content, string aliases, Func<T, string> format, string alt = "")
|
||||
{
|
||||
if (format == null) format = x => x.ToString();
|
||||
|
||||
var property = aliases.Split(',')
|
||||
.Where(x => string.IsNullOrWhiteSpace(x) == false)
|
||||
.Select(x => content.GetProperty(x.Trim()))
|
||||
.FirstOrDefault(x => x != null);
|
||||
|
||||
return property != null
|
||||
? new HtmlString(format(property.Value<T>()))
|
||||
: new HtmlString(alt);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ToIndexedArray
|
||||
|
||||
public static IndexedArrayItem<TContent>[] ToIndexedArray<TContent>(this IEnumerable<TContent> source)
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace Umbraco.Web.Routing
|
||||
return v.InvariantContains(a1) || v.InvariantContains(a2);
|
||||
}
|
||||
|
||||
// fixme - even with Linq, what happens below has to be horribly slow
|
||||
// todo - even with Linq, what happens below has to be horribly slow
|
||||
// but the only solution is to entirely refactor url providers to stop being dynamic
|
||||
|
||||
if (rootNodeId > 0)
|
||||
|
||||
@@ -107,7 +107,7 @@ namespace Umbraco.Web.Routing
|
||||
/// <param name="filter">An optional function to filter the list of domains, if more than one applies.</param>
|
||||
/// <returns>The domain and its normalized uri, that best matches the specified uri and cultures.</returns>
|
||||
/// <remarks>
|
||||
/// fixme - must document and explain this all
|
||||
/// todo - must document and explain this all
|
||||
/// <para>If <paramref name="uri"/> is null, pick the first domain that matches <paramref name="culture"/>,
|
||||
/// else the first that matches <paramref name="defaultCulture"/>, else the first one (ordered by id), else null.</para>
|
||||
/// <para>If <paramref name="uri"/> is not null, look for domains that would be a base uri of the current uri,</para>
|
||||
@@ -244,7 +244,7 @@ namespace Umbraco.Web.Routing
|
||||
/// <returns>The domains and their normalized uris, that match the specified uri.</returns>
|
||||
internal static IEnumerable<DomainAndUri> SelectDomains(IEnumerable<Domain> domains, Uri uri)
|
||||
{
|
||||
// fixme where are we matching ?!!?
|
||||
// todo where are we matching ?!!?
|
||||
return domains
|
||||
.Where(d => d.IsWildcard == false)
|
||||
.Select(d => new DomainAndUri(d, uri))
|
||||
|
||||
@@ -19,8 +19,7 @@ using RenderingEngine = Umbraco.Core.RenderingEngine;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
// fixme - make this public
|
||||
// fixme - making sense to have an interface?
|
||||
// todo - making sense to have an interface?
|
||||
public class PublishedRouter
|
||||
{
|
||||
private readonly IWebRoutingSection _webRoutingSection;
|
||||
@@ -53,7 +52,7 @@ namespace Umbraco.Web.Routing
|
||||
GetRolesForLogin = s => Roles.Provider.GetRolesForUser(s);
|
||||
}
|
||||
|
||||
// fixme
|
||||
// todo
|
||||
// in 7.7 this is cached in the PublishedContentRequest, which ... makes little sense
|
||||
// killing it entirely, if we need cache, just implement it properly !!
|
||||
// this is all soooo weird
|
||||
@@ -381,7 +380,7 @@ namespace Umbraco.Web.Routing
|
||||
|
||||
// NOTE: we could start with what's the current default?
|
||||
|
||||
// fixme - bad - we probably should be using the appropriate filesystems!
|
||||
// todo - bad - we probably should be using the appropriate filesystems!
|
||||
|
||||
if (FindTemplateRenderingEngineInDirectory(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.MvcViews)),
|
||||
alias, new[] { ".cshtml", ".vbhtml" }))
|
||||
|
||||
@@ -181,7 +181,7 @@ namespace Umbraco.Web.Routing
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<DomainAndUri> MapDomains(IReadOnlyCollection<DomainAndUri> domainAndUris, Uri current, bool excludeDefault, string culture, string defaultCulture)
|
||||
{
|
||||
// fixme ignoring cultures entirely?
|
||||
// todo ignoring cultures entirely?
|
||||
|
||||
var currentAuthority = current.GetLeftPart(UriPartial.Authority);
|
||||
KeyValuePair<string, string[]>[] candidateSites = null;
|
||||
@@ -279,7 +279,7 @@ namespace Umbraco.Web.Routing
|
||||
if (domainAndUris == null) throw new ArgumentNullException(nameof(domainAndUris));
|
||||
if (domainAndUris.Count == 0) throw new ArgumentException("Cannot be empty.", nameof(domainAndUris));
|
||||
|
||||
// fixme how shall we deal with cultures?
|
||||
// todo how shall we deal with cultures?
|
||||
|
||||
// we do our best, but can't do the impossible
|
||||
// get the "default" domain ie the first one for the culture, else the first one (exists, length > 0)
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace Umbraco.Web.Runtime
|
||||
.SetDefaultRenderMvcController<RenderMvcController>(); // default controller for template views
|
||||
|
||||
composition.WithCollectionBuilder<SearchableTreeCollectionBuilder>()
|
||||
.Add(() => composition.TypeLoader.GetTypes<ISearchableTree>()); // fixme which searchable trees?!
|
||||
.Add(() => composition.TypeLoader.GetTypes<ISearchableTree>());
|
||||
|
||||
composition.Register<UmbracoTreeSearcher>(Lifetime.Request);
|
||||
|
||||
|
||||
@@ -15,6 +15,6 @@ namespace Umbraco.Web.Scheduling
|
||||
void Add(T task);
|
||||
bool TryAdd(T task);
|
||||
|
||||
// fixme - complete the interface?
|
||||
// todo - complete the interface?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Umbraco.Web.Scheduling
|
||||
// ensure we run with an UmbracoContext, because this may run in a background task,
|
||||
// yet developers may be using the 'current' UmbracoContext in the event handlers
|
||||
//
|
||||
// fixme
|
||||
// todo
|
||||
// - or maybe not, CacheRefresherComponent already ensures a context when handling events
|
||||
// - UmbracoContext 'current' needs to be refactored and cleaned up
|
||||
// - batched messenger should not depend on a current HttpContext
|
||||
|
||||
@@ -154,7 +154,6 @@ namespace Umbraco.Web.Search
|
||||
|
||||
_rebuildOnStartupRunner = new BackgroundTaskRunner<IBackgroundTask>(
|
||||
"RebuildIndexesOnStartup",
|
||||
//new BackgroundTaskRunnerOptions{ LongRunning= true }, //fixme, this flag doesn't have any affect anymore
|
||||
logger);
|
||||
|
||||
_rebuildOnStartupRunner.TryAdd(task);
|
||||
@@ -214,7 +213,7 @@ namespace Umbraco.Web.Search
|
||||
// just ignore that payload
|
||||
// so what?!
|
||||
|
||||
//fixme: Rebuild the index at this point?
|
||||
//todo: Rebuild the index at this point?
|
||||
}
|
||||
else // RefreshNode or RefreshBranch (maybe trashed)
|
||||
{
|
||||
|
||||
@@ -326,7 +326,7 @@ namespace Umbraco.Web.Security
|
||||
switch (umbracoType)
|
||||
{
|
||||
case UmbracoObjectTypes.Member:
|
||||
// fixme - need to implement Get(guid)!
|
||||
// todo - need to implement Get(guid)!
|
||||
var memberAttempt = entityService.GetId(guidUdi.Guid, umbracoType);
|
||||
if (memberAttempt.Success)
|
||||
return GetById(memberAttempt.Result);
|
||||
|
||||
@@ -37,8 +37,8 @@ namespace Umbraco.Web.Templates
|
||||
_umbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext));
|
||||
}
|
||||
|
||||
private IFileService FileService => Current.Services.FileService; // fixme inject
|
||||
private PublishedRouter PublishedRouter => Core.Composing.Current.Factory.GetInstance<PublishedRouter>(); // fixme inject
|
||||
private IFileService FileService => Current.Services.FileService; // todo inject
|
||||
private PublishedRouter PublishedRouter => Core.Composing.Current.Factory.GetInstance<PublishedRouter>(); // todo inject
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -235,7 +235,6 @@ namespace Umbraco.Web.Trees
|
||||
AddActionNode<ActionSort>(item, menu, true);
|
||||
AddActionNode<ActionAssignDomain>(item, menu, opensDialog: true);
|
||||
AddActionNode<ActionRights>(item, menu, opensDialog: true);
|
||||
//fixme - conver this editor to angular
|
||||
AddActionNode<ActionProtect>(item, menu, true, convert: true, opensDialog: true);
|
||||
if (EmailSender.CanSendRequiredEmail)
|
||||
{
|
||||
@@ -308,7 +307,7 @@ namespace Umbraco.Web.Trees
|
||||
entity.Name = "[[" + entity.Id + "]]";
|
||||
}
|
||||
|
||||
//fixme: Remove the need for converting to legacy
|
||||
//todo: Remove the need for converting to legacy
|
||||
private void AddActionNode<TAction>(IUmbracoEntity item, MenuItemCollection menu, bool hasSeparator = false, bool convert = false, bool opensDialog = false)
|
||||
where TAction : IAction
|
||||
{
|
||||
|
||||
@@ -401,7 +401,7 @@ namespace Umbraco.Web.Trees
|
||||
internal IEnumerable<MenuItem> GetAllowedUserMenuItemsForNode(IUmbracoEntity dd)
|
||||
{
|
||||
var permission = Services.UserService.GetPermissions(Security.CurrentUser, dd.Path);
|
||||
//fixme: inject
|
||||
//todo: inject
|
||||
var actions = Current.Actions.FromEntityPermission(permission)
|
||||
.ToList();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
//fixme - we don't really use this, it is nice to have the treecontroller, attribute and ApplicationTree streamlined to implement this but it's not used
|
||||
//todo- we don't really use this, it is nice to have the treecontroller, attribute and ApplicationTree streamlined to implement this but it's not used
|
||||
//leave as internal for now, maybe we'll use in the future, means we could pass around ITree
|
||||
internal interface ITree
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Web.Trees
|
||||
/// </summary>
|
||||
internal class LegacyTreeDataConverter
|
||||
{
|
||||
//fixme: remove this whole class when everything is angularized
|
||||
//todo: remove this whole class when everything is angularized
|
||||
|
||||
/// <summary>
|
||||
/// This will look at the legacy IAction's JsFunctionName and convert it to a confirmation dialog view if possible
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Net.Http.Formatting;
|
||||
using Umbraco.Web.Models.Trees;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.LogViewer)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.LogViewer, null, sortOrder: 9)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
public class LogViewerTreeController : TreeController
|
||||
{
|
||||
protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
|
||||
{
|
||||
//We don't have any child nodes & only use the root node to load a custom UI
|
||||
return new TreeNodeCollection();
|
||||
}
|
||||
|
||||
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
|
||||
{
|
||||
//We don't have any menu item options (such as create/delete/reload) & only use the root node to load a custom UI
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to create a root model for a tree
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
|
||||
{
|
||||
var root = base.CreateRootNode(queryStrings);
|
||||
|
||||
//this will load in a custom UI instead of the dashboard for the root node
|
||||
root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Settings, Constants.Trees.LogViewer, "overview");
|
||||
root.Icon = "icon-box-alt";
|
||||
root.HasChildren = false;
|
||||
root.MenuUrl = null;
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace Umbraco.Web.Trees
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.Scripts, TreeTitle = "Scripts", IconOpen = "icon-folder", IconClosed = "icon-folder", SortOrder = 10, TreeGroup = Constants.Trees.Groups.Templating)]
|
||||
public class ScriptsTreeController : FileSystemTreeController
|
||||
{
|
||||
protected override IFileSystem FileSystem => Current.FileSystems.ScriptsFileSystem; // fixme inject
|
||||
protected override IFileSystem FileSystem => Current.FileSystems.ScriptsFileSystem; // todo inject
|
||||
|
||||
private static readonly string[] ExtensionsStatic = { "js" };
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Umbraco.Web.Trees
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.Stylesheets, TreeTitle = "Stylesheets", IconOpen = "icon-folder", IconClosed = "icon-folder", SortOrder = 9, TreeGroup = Constants.Trees.Groups.Templating)]
|
||||
public class StylesheetsTreeController : FileSystemTreeController
|
||||
{
|
||||
protected override IFileSystem FileSystem => Current.FileSystems.StylesheetsFileSystem; // fixme inject
|
||||
protected override IFileSystem FileSystem => Current.FileSystems.StylesheetsFileSystem; // todo inject
|
||||
|
||||
private static readonly string[] ExtensionsStatic = { "css" };
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ namespace Umbraco.Web.UI.Controls
|
||||
|
||||
protected override void Render(System.Web.UI.HtmlTextWriter writer)
|
||||
{
|
||||
// fixme - image is gone!
|
||||
base.ImageUrl = "/images/progressBar.gif";
|
||||
base.AlternateText = Title;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Umbraco.Web.UI.Controls
|
||||
UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext));
|
||||
Umbraco = new UmbracoHelper(umbracoContext, services);
|
||||
|
||||
// fixme inject somehow
|
||||
// todo inject somehow
|
||||
Logger = Current.Logger;
|
||||
ProfilingLogger = Current.ProfilingLogger;
|
||||
Services = Current.Services;
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Umbraco.Web.UI.Controls
|
||||
Umbraco = new UmbracoHelper(umbracoContext, services);
|
||||
Members = Current.Factory.GetInstance<MembershipHelper>();
|
||||
|
||||
// fixme inject somehow
|
||||
// todo inject somehow
|
||||
Logger = Current.Logger;
|
||||
ProfilingLogger = Current.ProfilingLogger;
|
||||
Services = Current.Services;
|
||||
|
||||
@@ -219,10 +219,12 @@
|
||||
<Compile Include="Editors\BackOfficeAssetsController.cs" />
|
||||
<Compile Include="Editors\BackOfficeModel.cs" />
|
||||
<Compile Include="Editors\BackOfficeServerVariables.cs" />
|
||||
<Compile Include="Editors\LogViewerController.cs" />
|
||||
<Compile Include="ImageProcessorLogger.cs" />
|
||||
<Compile Include="Macros\MacroTagParser.cs" />
|
||||
<Compile Include="Models\ContentEditing\ContentDomainsAndCulture.cs" />
|
||||
<Compile Include="Models\ContentEditing\ContentSavedState.cs" />
|
||||
<Compile Include="Trees\LogViewerTreeController.cs" />
|
||||
<Compile Include="Models\Mapping\ContentSavedStateResolver.cs" />
|
||||
<Compile Include="Mvc\ContainerControllerFactory.cs" />
|
||||
<Compile Include="OwinExtensions.cs" />
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Umbraco.Web
|
||||
/// <param name="replace">A value indicating whether to replace the existing context.</param>
|
||||
/// <returns>The "current" UmbracoContext.</returns>
|
||||
/// <remarks>
|
||||
/// fixme - this needs to be clarified
|
||||
/// todo - this needs to be clarified
|
||||
///
|
||||
/// If <paramref name="replace"/> is true then the "current" UmbracoContext is replaced
|
||||
/// with a new one even if there is one already. See <see cref="WebRuntimeComponent"/>. Has to do with
|
||||
|
||||
@@ -325,23 +325,14 @@ namespace Umbraco.Web
|
||||
|
||||
var umbracoType = Constants.UdiEntityType.ToUmbracoObjectType(udi.EntityType);
|
||||
|
||||
var entityService = Current.Services.EntityService;
|
||||
switch (umbracoType)
|
||||
{
|
||||
case UmbracoObjectTypes.Document:
|
||||
return Content(guidUdi.Guid);
|
||||
case UmbracoObjectTypes.Media:
|
||||
// fixme - need to implement Media(guid)!
|
||||
var mediaAttempt = entityService.GetId(guidUdi.Guid, umbracoType);
|
||||
if (mediaAttempt.Success)
|
||||
return Media(mediaAttempt.Result);
|
||||
break;
|
||||
return Media(guidUdi.Guid);
|
||||
case UmbracoObjectTypes.Member:
|
||||
// fixme - need to implement Member(guid)!
|
||||
var memberAttempt = entityService.GetId(guidUdi.Guid, umbracoType);
|
||||
if (memberAttempt.Success)
|
||||
return Member(memberAttempt.Result);
|
||||
break;
|
||||
return Member(guidUdi.Guid);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -650,7 +641,7 @@ namespace Umbraco.Web
|
||||
//TODO: This is horrible but until the media cache properly supports GUIDs we have no choice here and
|
||||
// currently there won't be any way to add this method correctly to `ITypedPublishedContentQuery` without breaking an interface and adding GUID support for media
|
||||
|
||||
var entityService = Current.Services.EntityService; // fixme inject
|
||||
var entityService = Current.Services.EntityService; // todo inject
|
||||
var mediaAttempt = entityService.GetId(id, UmbracoObjectTypes.Media);
|
||||
return mediaAttempt.Success ? ContentQuery.Media(mediaAttempt.Result) : null;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Umbraco.Web
|
||||
UmbracoContext = umbracoContext;
|
||||
Umbraco = new UmbracoHelper(umbracoContext, services);
|
||||
|
||||
// fixme inject somehow
|
||||
// todo inject somehow
|
||||
Logger = Current.Logger;
|
||||
ProfilingLogger = Current.ProfilingLogger;
|
||||
Services = Current.Services;
|
||||
|
||||
@@ -11,6 +11,6 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
|
||||
public sealed class EnableOverrideAuthorizationAttribute : Attribute
|
||||
{
|
||||
//fixme we should remove this and use the System.Web.Http.OverrideAuthorizationAttribute which uses IOverrideFilter instead
|
||||
//todo we should remove this and use the System.Web.Http.OverrideAuthorizationAttribute which uses IOverrideFilter instead
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,13 +83,13 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
}
|
||||
else if (Udi.TryParse(argument, true, out Udi udi))
|
||||
{
|
||||
//fixme: inject? we can't because this is an attribute but we could provide ctors and empty ctors that pass in the required services
|
||||
//todo: inject? we can't because this is an attribute but we could provide ctors and empty ctors that pass in the required services
|
||||
nodeId = Current.Services.EntityService.GetId(udi).Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
Guid.TryParse(argument, out Guid key);
|
||||
//fixme: inject? we can't because this is an attribute but we could provide ctors and empty ctors that pass in the required services
|
||||
//todo: inject? we can't because this is an attribute but we could provide ctors and empty ctors that pass in the required services
|
||||
nodeId = Current.Services.EntityService.GetId(key, UmbracoObjectTypes.Document).Result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
_paramName = paramName;
|
||||
}
|
||||
|
||||
// fixme v8 guess this is not used anymore, source is ignored?!
|
||||
// todo v8 guess this is not used anymore, source is ignored?!
|
||||
public EnsureUserPermissionForMediaAttribute(string paramName, DictionarySource source)
|
||||
{
|
||||
if (string.IsNullOrEmpty(paramName)) throw new ArgumentNullOrEmptyException(nameof(paramName));
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Web.Http.Controllers;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
//fixme remove this since we don't need it, see notes in EnableOverrideAuthorizationAttribute
|
||||
//todo remove this since we don't need it, see notes in EnableOverrideAuthorizationAttribute
|
||||
|
||||
/// <summary>
|
||||
/// Abstract auth filter class that can be used to enable overriding class auth filters at the action level
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Umbraco.Web.WebApi
|
||||
/// </summary>
|
||||
internal static bool Enable = true;
|
||||
|
||||
// fixme - inject!
|
||||
// todo - inject!
|
||||
private readonly UmbracoContext _umbracoContext;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user