├── .dockerignore ├── .editorconfig ├── .filenesting.json ├── .gitattributes ├── .github └── workflows │ ├── docker.yml │ └── main.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Opserver.sln ├── Opserver.sln.DotSettings ├── appveyor.yml ├── docs ├── Configuration.md ├── Docs.csproj ├── Gemfile ├── HowTo │ ├── BuildAndRun.md │ └── UpgradeFromV1.md ├── Releases.md ├── _config.yml ├── _includes │ └── navigation.html ├── _layouts │ └── default.html ├── css │ └── main.css └── index.md ├── nuget.config ├── readme.md ├── src ├── Directory.Build.props ├── Opserver.Core │ ├── Data │ │ ├── Cache.cs │ │ ├── CloudFlare │ │ │ ├── CloudFlareAPI.Actions.cs │ │ │ ├── CloudFlareAPI.Zones.cs │ │ │ ├── CloudFlareAPI.cs │ │ │ ├── CloudFlareModule.cs │ │ │ └── CloudFlareResponse.cs │ │ ├── Cloudflare │ │ │ └── CloudflareRoles.cs │ │ ├── Dashboard │ │ │ ├── Application.cs │ │ │ ├── DashboardCategory.cs │ │ │ ├── DashboardModule.cs │ │ │ ├── DashboardRoles.cs │ │ │ ├── GraphPoint.cs │ │ │ ├── HardwareSummary.cs │ │ │ ├── HardwareType.cs │ │ │ ├── IServiceControlProvider.cs │ │ │ ├── Interface.Data.cs │ │ │ ├── Interface.cs │ │ │ ├── Node.Data.cs │ │ │ ├── Node.Polling.cs │ │ │ ├── Node.cs │ │ │ ├── NodeService.cs │ │ │ ├── NodeStatus.cs │ │ │ ├── Providers │ │ │ │ ├── BosunDataProvider.Metrics.cs │ │ │ │ ├── BosunDataProvider.Nodes.cs │ │ │ │ ├── BosunDataProvider.cs │ │ │ │ ├── DashboardDataProvider.cs │ │ │ │ ├── EmptyDataProvider.cs │ │ │ │ ├── OrionDataProvider.cs │ │ │ │ ├── SignalFxDataProvider.HostProperties.cs │ │ │ │ ├── SignalFxDataProvider.SignalFlow.cs │ │ │ │ ├── SignalFxDataProvider.cs │ │ │ │ ├── WmiDataProvider.Data.cs │ │ │ │ ├── WmiDataProvider.Polling.cs │ │ │ │ └── WmiDataProvider.cs │ │ │ ├── Volume.Data.cs │ │ │ └── Volume.cs │ │ ├── DataApi.cs │ │ ├── Elastic │ │ │ ├── ElasticCluster.Actions.cs │ │ │ ├── ElasticCluster.Aliases.cs │ │ │ ├── ElasticCluster.HealthStatus.cs │ │ │ ├── ElasticCluster.IndexStats.cs │ │ │ ├── ElasticCluster.Issues.cs │ │ │ ├── ElasticCluster.KnownNodes.cs │ │ │ ├── ElasticCluster.Nodes.cs │ │ │ ├── ElasticCluster.State.cs │ │ │ ├── ElasticCluster.cs │ │ │ ├── ElasticModule.cs │ │ │ ├── ElasticRoles.cs │ │ │ └── ShardStates.cs │ │ ├── Exceptions │ │ │ ├── Application.cs │ │ │ ├── ExceptionSorts.cs │ │ │ ├── ExceptionStore.cs │ │ │ ├── ExceptionsModule.cs │ │ │ └── ExceptionsRoles.cs │ │ ├── GlobalPollingStatus.cs │ │ ├── HAProxy │ │ │ ├── Backend.cs │ │ │ ├── Frontend.cs │ │ │ ├── HAProxyAdmin.cs │ │ │ ├── HAProxyGroup.Issues.cs │ │ │ ├── HAProxyGroup.cs │ │ │ ├── HAProxyInstance.Roles.cs │ │ │ ├── HAProxyInstance.cs │ │ │ ├── HAProxyModule.cs │ │ │ ├── HAProxyRoles.cs │ │ │ ├── Item.cs │ │ │ ├── Proxy.cs │ │ │ ├── Server.cs │ │ │ ├── ServerStatus.cs │ │ │ ├── Socket.cs │ │ │ ├── StatAttribute.cs │ │ │ ├── StatProperty.cs │ │ │ └── StatusType.cs │ │ ├── IPNet.cs │ │ ├── Issue.cs │ │ ├── Jira │ │ │ └── JiraClient.cs │ │ ├── MonitorStatus.cs │ │ ├── NodeRole.cs │ │ ├── PagerDuty │ │ │ ├── IncidentStatus.cs │ │ │ ├── PagerDutyAPI.Actions.cs │ │ │ ├── PagerDutyAPI.Incidents.cs │ │ │ ├── PagerDutyAPI.Logs.cs │ │ │ ├── PagerDutyAPI.OnCall.cs │ │ │ ├── PagerDutyAPI.Schedules.cs │ │ │ ├── PagerDutyAPI.cs │ │ │ ├── PagerDutyModule.cs │ │ │ └── PagerDutyRoles.cs │ │ ├── PollNode.Equality.cs │ │ ├── PollNode.Events.cs │ │ ├── PollNode.cs │ │ ├── PollingService.Issues.cs │ │ ├── PollingService.NodeRoles.cs │ │ ├── PollingService.Polling.cs │ │ ├── PollingService.Stats.cs │ │ ├── PollingService.cs │ │ ├── Redis │ │ │ ├── RedisAnalyzer.Memory.cs │ │ │ ├── RedisConnectionInfo.cs │ │ │ ├── RedisHost.cs │ │ │ ├── RedisInfo.Parsing.cs │ │ │ ├── RedisInfo.PropertyAttribute.cs │ │ │ ├── RedisInfo.cs │ │ │ ├── RedisInstance.Actions.cs │ │ │ ├── RedisInstance.Clients.cs │ │ │ ├── RedisInstance.Config.cs │ │ │ ├── RedisInstance.Info.cs │ │ │ ├── RedisInstance.Issues.cs │ │ │ ├── RedisInstance.SlowLog.cs │ │ │ ├── RedisInstance.cs │ │ │ ├── RedisInstanceOperation.cs │ │ │ ├── RedisModule.cs │ │ │ ├── RedisReplicationGroup.cs │ │ │ └── RedisRoles.cs │ │ ├── SQL │ │ │ ├── Enums.cs │ │ │ ├── ISQLVersioned.cs │ │ │ ├── QueryPlans │ │ │ │ ├── ShowPlanXML.cs │ │ │ │ └── ShowPlanXML.generated.cs │ │ │ ├── SQLAzureServer.cs │ │ │ ├── SQLCluster.Issues.cs │ │ │ ├── SQLCluster.cs │ │ │ ├── SQLInstance.ActiveOperations.cs │ │ │ ├── SQLInstance.CPUHistory.cs │ │ │ ├── SQLInstance.Configuration.cs │ │ │ ├── SQLInstance.Connections.cs │ │ │ ├── SQLInstance.Databases.cs │ │ │ ├── SQLInstance.Errors.cs │ │ │ ├── SQLInstance.Features.cs │ │ │ ├── SQLInstance.Issues.cs │ │ │ ├── SQLInstance.Jobs.cs │ │ │ ├── SQLInstance.Memory.cs │ │ │ ├── SQLInstance.PerfCounters.cs │ │ │ ├── SQLInstance.Properties.cs │ │ │ ├── SQLInstance.Services.cs │ │ │ ├── SQLInstance.TopOperations.cs │ │ │ ├── SQLInstance.TraceFlags.cs │ │ │ ├── SQLInstance.Utils.cs │ │ │ ├── SQLInstance.Volumes.cs │ │ │ ├── SQLInstance.WaitStats.cs │ │ │ ├── SQLInstance.cs │ │ │ ├── SQLModule.cs │ │ │ ├── SQLNode.AvailabilityGroups.AGDatabaseReplica.cs │ │ │ ├── SQLNode.AvailabilityGroups.AGInfo.cs │ │ │ ├── SQLNode.AvailabilityGroups.AGListener.cs │ │ │ ├── SQLNode.AvailabilityGroups.AGListenerIPAddress.cs │ │ │ ├── SQLNode.AvailabilityGroups.AGReplica.cs │ │ │ ├── SQLNode.AvailabilityGroups.cs │ │ │ ├── SQLNode.ClusterInfo.cs │ │ │ ├── SQLNode.TCPListeners.cs │ │ │ ├── SQLNode.cs │ │ │ ├── SQLRoles.cs │ │ │ └── SQLServerEngine.cs │ │ └── SearchResult.cs │ ├── ExtensionMethods.Exceptions.cs │ ├── ExtensionMethods.GetSet.cs │ ├── ExtensionMethods.Sequences.cs │ ├── ExtensionMethods.Sql.cs │ ├── ExtensionMethods.Time.cs │ ├── ExtensionMethods.cs │ ├── GlobalSuppressions.cs │ ├── Helpers │ │ ├── AddressCache.cs │ │ ├── Connection.cs │ │ ├── CryptoRandom.cs │ │ ├── FileSizeFormatProvider.cs │ │ ├── OpserverConfigException.cs │ │ ├── PerfCounters.cs │ │ ├── Singleton.cs │ │ ├── SortDir.cs │ │ ├── ThreadStats.cs │ │ ├── WindowsKernelVersions.cs │ │ └── Wmi.cs │ ├── IMinVersioned.cs │ ├── IOverallStatusCount.cs │ ├── ISeachableNode.cs │ ├── Models │ │ └── Roles.cs │ ├── Opserver.Core.csproj │ ├── ServiceExtensions.cs │ ├── Settings │ │ ├── CloudFlareSettings.cs │ │ ├── DashboardSettings.Bosun.cs │ │ ├── DashboardSettings.Orion.cs │ │ ├── DashboardSettings.Providers.cs │ │ ├── DashboardSettings.SignalFx.cs │ │ ├── DashboardSettings.WMI.cs │ │ ├── DashboardSettings.cs │ │ ├── ElasticSettings.cs │ │ ├── ExceptionSettings.cs │ │ ├── GlobalSettings.cs │ │ ├── HAProxySettings.cs │ │ ├── Interfaces.cs │ │ ├── JiraSettings.cs │ │ ├── ModuleSettings.cs │ │ ├── OpserverSettings.cs │ │ ├── PagerDutySettings.cs │ │ ├── RedisSettings.cs │ │ └── SQLSettings.cs │ ├── StatusModule.cs │ ├── StringBuilderCache.cs │ ├── StringSplits.cs │ └── app.config ├── Opserver.Web │ ├── ContentSource │ │ ├── bootstrap │ │ │ ├── js │ │ │ │ └── bootstrap.min.js │ │ │ └── less │ │ │ │ ├── .csscomb.json │ │ │ │ ├── .csslintrc │ │ │ │ ├── alerts.less │ │ │ │ ├── badges.less │ │ │ │ ├── bootstrap.less │ │ │ │ ├── breadcrumbs.less │ │ │ │ ├── button-groups.less │ │ │ │ ├── buttons.less │ │ │ │ ├── carousel.less │ │ │ │ ├── close.less │ │ │ │ ├── code.less │ │ │ │ ├── component-animations.less │ │ │ │ ├── dropdowns.less │ │ │ │ ├── forms.less │ │ │ │ ├── grid.less │ │ │ │ ├── input-groups.less │ │ │ │ ├── jumbotron.less │ │ │ │ ├── labels.less │ │ │ │ ├── list-group.less │ │ │ │ ├── media.less │ │ │ │ ├── mixins.less │ │ │ │ ├── mixins │ │ │ │ ├── alerts.less │ │ │ │ ├── background-variant.less │ │ │ │ ├── border-radius.less │ │ │ │ ├── buttons.less │ │ │ │ ├── center-block.less │ │ │ │ ├── clearfix.less │ │ │ │ ├── forms.less │ │ │ │ ├── gradients.less │ │ │ │ ├── grid-framework.less │ │ │ │ ├── grid.less │ │ │ │ ├── hide-text.less │ │ │ │ ├── image.less │ │ │ │ ├── labels.less │ │ │ │ ├── list-group.less │ │ │ │ ├── nav-divider.less │ │ │ │ ├── nav-vertical-align.less │ │ │ │ ├── opacity.less │ │ │ │ ├── pagination.less │ │ │ │ ├── panels.less │ │ │ │ ├── progress-bar.less │ │ │ │ ├── reset-filter.less │ │ │ │ ├── reset-text.less │ │ │ │ ├── resize.less │ │ │ │ ├── responsive-visibility.less │ │ │ │ ├── size.less │ │ │ │ ├── tab-focus.less │ │ │ │ ├── table-row.less │ │ │ │ ├── text-emphasis.less │ │ │ │ ├── text-overflow.less │ │ │ │ └── vendor-prefixes.less │ │ │ │ ├── modals.less │ │ │ │ ├── navbar.less │ │ │ │ ├── navs.less │ │ │ │ ├── normalize.less │ │ │ │ ├── pager.less │ │ │ │ ├── pagination.less │ │ │ │ ├── panels.less │ │ │ │ ├── popovers.less │ │ │ │ ├── print.less │ │ │ │ ├── progress-bars.less │ │ │ │ ├── responsive-embed.less │ │ │ │ ├── responsive-utilities.less │ │ │ │ ├── scaffolding.less │ │ │ │ ├── tables.less │ │ │ │ ├── theme.less │ │ │ │ ├── thumbnails.less │ │ │ │ ├── tooltip.less │ │ │ │ ├── type.less │ │ │ │ ├── utilities.less │ │ │ │ ├── variables.less │ │ │ │ └── wells.less │ │ ├── font-awesome │ │ │ └── less │ │ │ │ ├── animated.less │ │ │ │ ├── bordered-pulled.less │ │ │ │ ├── core.less │ │ │ │ ├── fixed-width.less │ │ │ │ ├── font-awesome.less │ │ │ │ ├── icons.less │ │ │ │ ├── larger.less │ │ │ │ ├── list.less │ │ │ │ ├── mixins.less │ │ │ │ ├── path.less │ │ │ │ ├── rotated-flipped.less │ │ │ │ ├── screen-reader.less │ │ │ │ ├── stacked.less │ │ │ │ └── variables.less │ │ ├── js │ │ │ ├── Scripts.js │ │ │ └── plugins │ │ │ │ ├── bootbox.js │ │ │ │ ├── colorbrewer.js │ │ │ │ ├── d3.v3.js │ │ │ │ ├── highlight.pack.js │ │ │ │ ├── jquery-ui.min.js │ │ │ │ ├── qp.js │ │ │ │ ├── tablesorter.js │ │ │ │ ├── toastr.js │ │ │ │ └── visibility.js │ │ └── themes │ │ │ ├── _shared.less │ │ │ ├── _shared │ │ │ ├── _bootswatch.less │ │ │ ├── _variables.less │ │ │ ├── animations.less │ │ │ ├── charts.less │ │ │ ├── exceptions.less │ │ │ ├── highlightjs.less │ │ │ ├── spinkit.less │ │ │ ├── sql.less │ │ │ └── toastr.less │ │ │ ├── dark.less │ │ │ └── light.less │ ├── Controllers │ │ ├── AdminController.cs │ │ ├── ApiController.cs │ │ ├── AuthController.OIDC.cs │ │ ├── AuthController.cs │ │ ├── CloudflareController.cs │ │ ├── DashboardController.Admin.cs │ │ ├── DashboardController.Polling.cs │ │ ├── DashboardController.cs │ │ ├── DataController.cs │ │ ├── DefaultRoute.cs │ │ ├── ElasticController.cs │ │ ├── ExceptionsController.cs │ │ ├── GraphController.Json.cs │ │ ├── GraphController.Spark.cs │ │ ├── GraphController.cs │ │ ├── HAProxyController.Admin.cs │ │ ├── HAProxyController.cs │ │ ├── HomeController.cs │ │ ├── HubController.cs │ │ ├── MiscController.cs │ │ ├── PagerDutyController.Admin.cs │ │ ├── PagerDutyController.cs │ │ ├── PollController.cs │ │ ├── RedisController.Admin.cs │ │ ├── RedisController.cs │ │ ├── SQLController.Admin.cs │ │ ├── SQLController.cs │ │ └── StatusController.cs │ ├── Current.cs │ ├── ExtensionMethods.cs │ ├── Help │ │ └── SQL │ │ │ └── WaitStats.md │ ├── Helpers │ │ ├── DataKeys.cs │ │ ├── ExtensionMethods.cs │ │ ├── Health.cs │ │ ├── Icon.cs │ │ ├── MiniProfilerCacheStorage.cs │ │ ├── NavTab.cs │ │ ├── OnlyAllowAttribute.cs │ │ ├── Poll.cs │ │ ├── PrefixedJsonConfiguration.cs │ │ ├── Status.cs │ │ ├── Tag │ │ │ └── PollTagHelper.cs │ │ ├── TestItem.cs │ │ └── Theme.cs │ ├── Models │ │ └── User.cs │ ├── Opserver.Web.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Security │ │ ├── ActiveDirectoryProvider.cs │ │ ├── ActiveDirectorySecuritySettings.cs │ │ ├── EveryonesAnAdminProvider.cs │ │ ├── EveryonesReadOnlyProvider.cs │ │ ├── ISecurityProviderToken.cs │ │ ├── OIDCProvider.cs │ │ ├── OIDCSecuritySettings.cs │ │ ├── OIDCToken.cs │ │ ├── SecurityManager.cs │ │ ├── SecurityProvider.cs │ │ ├── SecurityProviderFlowType.cs │ │ ├── SecuritySettings.cs │ │ ├── UnconfiguredProvider.cs │ │ └── UserNamePasswordToken.cs │ ├── Startup.cs │ ├── Views │ │ ├── Auth │ │ │ ├── Login.Model.cs │ │ │ └── Login.cshtml │ │ ├── CloudFlare │ │ │ ├── DNS.Model.cs │ │ │ ├── DNS.cshtml │ │ │ ├── Dashboard.Model.cs │ │ │ ├── Dashboard.cshtml │ │ │ ├── _ViewImports.cshtml │ │ │ └── _ViewStart.cshtml │ │ ├── Dashboard │ │ │ ├── CurrentStatusTypes.cs │ │ │ ├── Dashboard.Model.cs │ │ │ ├── Dashboard.Table.cshtml │ │ │ ├── Dashboard.cshtml │ │ │ ├── Node.Graph.Model.cs │ │ │ ├── Node.Graph.cshtml │ │ │ ├── Node.Hardware.cshtml │ │ │ ├── Node.Interfaces.cshtml │ │ │ ├── Node.Model.cs │ │ │ ├── Node.Network.cshtml │ │ │ ├── Node.Services.cshtml │ │ │ ├── Node.Stats.cshtml │ │ │ ├── Node.VMHost.cshtml │ │ │ ├── Node.Volumes.cshtml │ │ │ ├── Node.cshtml │ │ │ └── _ViewImports.cshtml │ │ ├── Elastic │ │ │ ├── AllClusters.cshtml │ │ │ ├── Cluster.Indexes.cshtml │ │ │ ├── Cluster.Panel.Indexes.cshtml │ │ │ ├── Cluster.Panel.Stats.cshtml │ │ │ ├── Cluster.Selector.cshtml │ │ │ ├── Cluster.Shards.cshtml │ │ │ ├── Cluster.cshtml │ │ │ ├── Dashboard.Model.cs │ │ │ ├── Dashboard.cshtml │ │ │ ├── Indexes.Table.cshtml │ │ │ ├── Indexes.cshtml │ │ │ ├── Node.Selector.cshtml │ │ │ ├── Node.Settings.cshtml │ │ │ ├── Node.cshtml │ │ │ ├── Shards.Table.cshtml │ │ │ ├── _ViewImports.cshtml │ │ │ └── _ViewStart.cshtml │ │ ├── Exceptions │ │ │ ├── Exceptions.Detail.cshtml │ │ │ ├── Exceptions.Filters.cshtml │ │ │ ├── Exceptions.Jira.cshtml │ │ │ ├── Exceptions.Model.cs │ │ │ ├── Exceptions.Navigation.cshtml │ │ │ ├── Exceptions.Preview.cshtml │ │ │ ├── Exceptions.Table.Rows.cshtml │ │ │ ├── Exceptions.Table.cshtml │ │ │ ├── Exceptions.cshtml │ │ │ └── _ViewImports.cshtml │ │ ├── HAProxy │ │ │ ├── HAProxy.Dashboard.cshtml │ │ │ ├── HAProxy.Model.cs │ │ │ ├── HAProxy.cshtml │ │ │ └── _ViewImports.cshtml │ │ ├── Home │ │ │ ├── About.Caches.cshtml │ │ │ ├── About.Model.cs │ │ │ ├── About.cshtml │ │ │ └── Test.cshtml │ │ ├── Hub │ │ │ ├── Dashboard.Node.cshtml │ │ │ ├── HubModel.cs │ │ │ ├── Index.Dashboard.cshtml │ │ │ ├── Index.Elastic.cshtml │ │ │ ├── Index.HAProxy.cshtml │ │ │ ├── Index.Issues.cshtml │ │ │ ├── Index.SQL.cshtml │ │ │ ├── Index.cshtml │ │ │ └── SQL.AvailabilityGroup.cshtml │ │ ├── PagerDuty │ │ │ ├── PagerDuty.EscFull.cshtml │ │ │ ├── PagerDuty.Incident.Model.cs │ │ │ ├── PagerDuty.Incident.cshtml │ │ │ ├── PagerDuty.Model.cs │ │ │ ├── PagerDuty.OnCallRow.cshtml │ │ │ ├── PagerDuty.cshtml │ │ │ └── _ViewImports.cshtml │ │ ├── Redis │ │ │ ├── Dashboard.Instances.cshtml │ │ │ ├── Dashboard.Model.cs │ │ │ ├── Dashboard.cshtml │ │ │ ├── Instance.Actions.cshtml │ │ │ ├── Instance.Analysis.Memory.cshtml │ │ │ ├── Instance.Clients.cshtml │ │ │ ├── Instance.Config.cshtml │ │ │ ├── Instance.Info.cshtml │ │ │ ├── Instance.Selector.cshtml │ │ │ ├── Instance.SlowLog.cshtml │ │ │ ├── Instance.cshtml │ │ │ ├── Server.Actions.Preview.cshtml │ │ │ ├── Server.Actions.cshtml │ │ │ ├── _ViewImports.cshtml │ │ │ └── _ViewStart.cshtml │ │ ├── SQL │ │ │ ├── AllJobs.cshtml │ │ │ ├── Connections.cshtml │ │ │ ├── Dashboard.Model.cs │ │ │ ├── Dashboard.cshtml │ │ │ ├── Databases.Modal.Backups.cshtml │ │ │ ├── Databases.Modal.Columns.cshtml │ │ │ ├── Databases.Modal.MissingIndexes.cshtml │ │ │ ├── Databases.Modal.Partitions.cshtml │ │ │ ├── Databases.Modal.Restores.cshtml │ │ │ ├── Databases.Modal.Storage.cshtml │ │ │ ├── Databases.Modal.StoredProcedures.cshtml │ │ │ ├── Databases.Modal.Tables.Indexes.cshtml │ │ │ ├── Databases.Modal.Tables.cshtml │ │ │ ├── Databases.Modal.UnusedIndexes.cshtml │ │ │ ├── Databases.Modal.Views.cshtml │ │ │ ├── Databases.Modal.cshtml │ │ │ ├── Databases.Model.cs │ │ │ ├── Databases.cshtml │ │ │ ├── Instance.Configuration.cshtml │ │ │ ├── Instance.Connections.cshtml │ │ │ ├── Instance.DBFiles.cshtml │ │ │ ├── Instance.Errors.cshtml │ │ │ ├── Instance.Jobs.cshtml │ │ │ ├── Instance.Memory.cshtml │ │ │ ├── Instance.Model.cs │ │ │ ├── Instance.Selector.cshtml │ │ │ ├── Instance.cshtml │ │ │ ├── Operations.Active.Filters.cshtml │ │ │ ├── Operations.Active.Model.cs │ │ │ ├── Operations.Active.cshtml │ │ │ ├── Operations.Top.Detail.cshtml │ │ │ ├── Operations.Top.Filters.cshtml │ │ │ ├── Operations.Top.Model.cs │ │ │ ├── Operations.Top.cshtml │ │ │ ├── Ops.Top.Detail.Model.cs │ │ │ ├── Servers.AvailabilityGroup.cshtml │ │ │ ├── Servers.ClusterDetail.cshtml │ │ │ ├── Servers.Model.cs │ │ │ ├── Servers.cshtml │ │ │ ├── _ViewImports.cshtml │ │ │ └── _ViewStart.cshtml │ │ ├── Shared │ │ │ ├── AccessDenied.cshtml │ │ │ ├── Error.cshtml │ │ │ ├── Gauges │ │ │ │ ├── Circle.Model.cs │ │ │ │ └── Circle.cshtml │ │ │ ├── Issues.cshtml │ │ │ ├── IssuesButton.cshtml │ │ │ ├── Master.cshtml │ │ │ ├── NoConfiguration.cshtml │ │ │ ├── PageNotFound.Model.cs │ │ │ ├── PageNotFound.cshtml │ │ │ ├── Partials.MemoryCell.Model.cs │ │ │ ├── Partials.MemoryCell.cshtml │ │ │ ├── PollInfo.Model.cs │ │ │ ├── PollInfo.cshtml │ │ │ ├── TopBoxOptions.cs │ │ │ ├── TopRefresh.Model.cs │ │ │ ├── TopRefresh.cshtml │ │ │ └── TopTabs.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml │ ├── bundleconfig.json │ ├── compilerconfig.json │ ├── compilerconfig.json.defaults │ ├── favicon.ico │ ├── opserverSettings.json │ └── wwwroot │ │ ├── Content │ │ ├── font-awesome │ │ │ └── fonts │ │ │ │ ├── FontAwesome.otf │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ └── fontawesome-webfont.woff2 │ │ ├── img │ │ │ ├── apple-touch-icon.png │ │ │ ├── logo │ │ │ │ ├── full-dark.svg │ │ │ │ ├── full.svg │ │ │ │ ├── icon-128.png │ │ │ │ ├── icon-16.png │ │ │ │ ├── icon-256.png │ │ │ │ ├── icon-32.png │ │ │ │ ├── icon.svg │ │ │ │ └── logo.eps │ │ │ └── query-plan │ │ │ │ ├── arithmetic_expression.gif │ │ │ │ ├── assert.gif │ │ │ │ ├── assign.gif │ │ │ │ ├── bitmap.gif │ │ │ │ ├── bookmark_lookup.gif │ │ │ │ ├── clustered_index_delete.gif │ │ │ │ ├── clustered_index_insert.gif │ │ │ │ ├── clustered_index_scan.gif │ │ │ │ ├── clustered_index_seek.gif │ │ │ │ ├── clustered_index_update.gif │ │ │ │ ├── collapse.gif │ │ │ │ ├── compute_scalar.gif │ │ │ │ ├── concatenation.gif │ │ │ │ ├── constant_scan.gif │ │ │ │ ├── convert.gif │ │ │ │ ├── declare.gif │ │ │ │ ├── deleted_scan.gif │ │ │ │ ├── distribute_streams.gif │ │ │ │ ├── dynamic.gif │ │ │ │ ├── fetch_query.gif │ │ │ │ ├── filter.gif │ │ │ │ ├── gather_streams.gif │ │ │ │ ├── hash_match.gif │ │ │ │ ├── icons.png │ │ │ │ ├── if.gif │ │ │ │ ├── inserted_scan.gif │ │ │ │ ├── intrinsic.gif │ │ │ │ ├── keyset.gif │ │ │ │ ├── log_row_scan.gif │ │ │ │ ├── merge_interval.gif │ │ │ │ ├── merge_join.gif │ │ │ │ ├── nested_loops.gif │ │ │ │ ├── nonclustered_index_delete.gif │ │ │ │ ├── nonclustered_index_insert.gif │ │ │ │ ├── nonclustered_index_scan.gif │ │ │ │ ├── nonclustered_index_seek.gif │ │ │ │ ├── nonclustered_index_spool.gif │ │ │ │ ├── nonclustered_index_update.gif │ │ │ │ ├── online_index_insert.gif │ │ │ │ ├── parameter_table_scan.gif │ │ │ │ ├── population_query.gif │ │ │ │ ├── rdi_lookup.gif │ │ │ │ ├── refresh_query.gif │ │ │ │ ├── remote_delete.gif │ │ │ │ ├── remote_insert.gif │ │ │ │ ├── remote_query.gif │ │ │ │ ├── remote_scan.gif │ │ │ │ ├── remote_update.gif │ │ │ │ ├── repartition_streams.gif │ │ │ │ ├── result.gif │ │ │ │ ├── row_count_spool.gif │ │ │ │ ├── segment.gif │ │ │ │ ├── sequence.gif │ │ │ │ ├── sequenceproject.gif │ │ │ │ ├── snapshot.gif │ │ │ │ ├── sort.gif │ │ │ │ ├── split.gif │ │ │ │ ├── spool.gif │ │ │ │ ├── stream_aggregate.gif │ │ │ │ ├── switch.gif │ │ │ │ ├── table_delete.gif │ │ │ │ ├── table_insert.gif │ │ │ │ ├── table_scan.gif │ │ │ │ ├── table_spool.gif │ │ │ │ ├── table_update.gif │ │ │ │ ├── table_valued_function.gif │ │ │ │ ├── top.gif │ │ │ │ ├── udx.gif │ │ │ │ └── while.gif │ │ ├── js │ │ │ └── jquery-2.2.4.min.js │ │ └── themes │ │ │ ├── dark.css │ │ │ ├── dark.min.css │ │ │ ├── light.css │ │ │ └── light.min.css │ │ └── favicon.ico └── Opserver.ruleset └── tests ├── Directory.Build.props ├── Opserver.Tests ├── HAProxyTests.cs ├── IPNetTests.cs ├── Opserver.Tests.csproj └── SecurityProviderTests.cs └── docker-compose.yml /.dockerignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | .vs/ 4 | TestResults/ 5 | artifacts/ 6 | _ReSharper.*/ 7 | _ReSharper.* 8 | *.user 9 | *.suo 10 | *.cache 11 | *.psess 12 | *.vsp 13 | *.pidb 14 | *.userprefs 15 | *DS_Store -------------------------------------------------------------------------------- /.filenesting.json: -------------------------------------------------------------------------------- 1 | { 2 | "help": "https://go.microsoft.com/fwlink/?linkid=866610", 3 | "root": true, 4 | 5 | "dependentFileProviders": { 6 | "add": { 7 | "pathSegment": { 8 | "add": { 9 | "ExtensionMethods.*": [ ".cs" ], 10 | "Models\\*.*": [ "*.cs" ] 11 | } 12 | }, 13 | "fileSuffixToExtension": { 14 | "add": { 15 | ".Model.cs": [ ".cshtml" ], 16 | ".json.defaults": [ ".json" ], 17 | ".min.css": [ ".css" ], 18 | ".min.js": [ ".js" ], 19 | ".min.js.map": [ ".min.js" ] 20 | } 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'Dockerfile' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Build Opserver Docker Image 14 | run: | 15 | docker build --tag opserver/opserver-ci:latest --target web . -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: windows-2019 8 | steps: 9 | - name: Setup .NET 10 | uses: actions/setup-dotnet@v1 11 | with: 12 | dotnet-version: 8.0.x 13 | - uses: actions/checkout@v3 14 | - name: Build with dotnet 15 | run: dotnet build 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build 2 | WORKDIR /app 3 | 4 | # Global 5 | COPY ./*.sln ./nuget.config ./ 6 | # Apps 7 | COPY src/*/*.csproj ./ 8 | RUN for file in $(ls *.csproj); do mkdir -p src/${file%.*}/ && mv $file src/${file%.*}/; done 9 | # Tests 10 | COPY tests/*/*.csproj ./ 11 | RUN for file in $(ls *.csproj); do mkdir -p tests/${file%.*}/ && mv $file tests/${file%.*}/; done 12 | 13 | # Copy all 14 | COPY . . 15 | 16 | # FROM build AS test-runner 17 | # WORKDIR /app/tests/Opserver.Tests 18 | # ENTRYPOINT dotnet test --results-directory /app/artifacts --logger:trx 19 | 20 | FROM build AS web-publish 21 | WORKDIR /app/src/Opserver.Web 22 | RUN dotnet publish -c Release -o publish 23 | 24 | # Build runtime image 25 | FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS web 26 | WORKDIR /app 27 | COPY --from=web-publish /app/src/Opserver.Web/publish ./ 28 | ENTRYPOINT ["dotnet", "Opserver.Web.dll"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Stack Exchange Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /Opserver.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | CPU 3 | OS 4 | VM 5 | True 6 | True 7 | True 8 | True -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2019 2 | 3 | install: 4 | - choco install dotnet-sdk --version 8.0.100 5 | 6 | skip_branch_with_pr: true 7 | skip_tags: true 8 | skip_commits: 9 | files: 10 | - '**/*.md' 11 | - docs/* 12 | 13 | environment: 14 | Appveyor: true 15 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 16 | 17 | build_script: 18 | - cmd: dotnet build 19 | 20 | test: off -------------------------------------------------------------------------------- /docs/Docs.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | 5 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'github-pages', group: :jekyll_plugins -------------------------------------------------------------------------------- /docs/HowTo/UpgradeFromV1.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "default" 3 | --- 4 | ### How-To Upgrade From Opserver V1 to V2 5 | 6 | Opserver v2 is a migration from .NET Full Framework (requiring IIS) to .NET Core. This means it can run in a variety of ways: 7 | 8 | - Under IIS (you'll need the ASP.NET Core hosting module - [here's how that works'](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/?view=aspnetcore-3.1)) 9 | - Directly as an executable (using the Kestrel web server) 10 | - Using a container (these aren't published yet, but we'll get there) 11 | 12 | It can run on Windows, macOS, or Linux. 13 | 14 | Legacy configuration under `src\Opserver.Web\Config` is still supported with the exception of `securitySettings.config`. 15 | Everything is now JSON (though it may support more options in the future), and there's no XML/JSON mix. 16 | 17 | See [the confgiuration documentation](../Configuration) for examples of all configs. -------------------------------------------------------------------------------- /docs/Releases.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Release Notes" 3 | layout: "default" 4 | --- 5 | ### Release Notes 6 | This page tracks major changes included in any update starting with version 2.0 7 | 8 | #### Version 2.0.0 (In preview) 9 | - **New**: 10 | - Runs on .NET Core (previous .NET Framework) 11 | - Now supports Windows, Linux, and MacOS 12 | - Windows: Doesn't require IIS - can spin up via `dotnet run`, or via a publish and copy 13 | - Supports running in a container (via Docker) 14 | - Adds SignalFX as a dashboard provider 15 | - Adds OpenId Connect as a login provider 16 | - Overhauled configuration system (though most old formats are supported for a smooth transition) 17 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | title: 'Opserver' 2 | subtitle: 'Documentation' 3 | codeurl: 'https://github.com/Opserver/Opserver' 4 | baseurl: '/Opserver' 5 | highlighter: rouge 6 | exclude: [README.md'] 7 | markdown: kramdown 8 | redcarpet: 9 | extensions: [ 10 | 'no_intra_emphasis', 11 | 'fenced_code_blocks', 12 | 'autolink', 13 | 'strikethrough', 14 | 'superscript', 15 | 'with_toc_data', 16 | 'tables', 17 | 'hardwrap' 18 | ] -------------------------------------------------------------------------------- /docs/_includes/navigation.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Opserver 2 | [![Build status](https://ci.appveyor.com/api/projects/status/7m0b1e4orimk5nvr/branch/main?svg=true)](https://ci.appveyor.com/project/StackExchange/opserver/branch/main) 3 | [![Open Source Helpers](https://www.codetriage.com/opserver/opserver/badges/users.svg)](https://www.codetriage.com/opserver/opserver) 4 | 5 | Opserver is a monitoring system originally from the team at [Stack Exchange](https://stackexchange.com), home of [Stack Overflow](https://stackoverflow.com). 6 | 7 | [Please see the documentation site here](https://opserver.github.io/Opserver/). 8 | 9 | ### License 10 | Opserver is licensed under the [MIT License](https://opensource.org/licenses/MIT). 11 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8.0 4 | true 5 | $(MSBuildThisFileDirectory)Opserver.ruleset 6 | 7 | $(AssemblyName) 8 | https://github.com/Opserver/Opserver/ 9 | https://raw.github.com/Opserver/Opserver/master/LICENSE 10 | 11 | git 12 | https://github.com/Opserver/Opserver/ 13 | 14 | true 15 | embedded 16 | en-US 17 | false 18 | true 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/CloudFlare/CloudFlareAPI.Actions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using System.Threading.Tasks; 3 | 4 | namespace Opserver.Data.Cloudflare 5 | { 6 | public partial class CloudflareAPI 7 | { 8 | public Task PurgeFileAsync(string url) 9 | { 10 | var zone = GetZoneFromUrl(url); 11 | if (zone == null) return Task.FromResult(false); 12 | 13 | var otherUrl = url.StartsWith("http:") 14 | ? Regex.Replace(url, "^http:", "https:") 15 | : Regex.Replace(url, "^https:", "http:"); 16 | 17 | return Module.PurgeFilesAsync(zone, new[] {url, otherUrl}); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Cloudflare/CloudflareRoles.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.Cloudflare 2 | { 3 | public static class CloudflareRoles 4 | { 5 | public const string Admin = nameof(CloudflareModule) + ":" + nameof(Admin); 6 | public const string Viewer = nameof(CloudflareModule) + ":" + nameof(Viewer); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Dashboard/Application.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Opserver.Data.Dashboard 4 | { 5 | public class Application 6 | { 7 | public Node Node { get; set; } 8 | 9 | public string Id { get; internal set; } 10 | public string NodeId { get; internal set; } 11 | public string NiceName { get; internal set; } 12 | public string AppName { get; internal set; } 13 | public string ComponentName { get; internal set; } 14 | public DateTime LastUpdated { get; internal set; } 15 | public bool IsUnwatched { get; internal set; } 16 | 17 | public int? ProcessID { get; internal set; } 18 | public string ProcessName { get; internal set; } 19 | public DateTime? LastTimeUp { get; internal set; } 20 | 21 | public double? CurrentPercentCPU { get; internal set; } 22 | public double? CurrentPercentMemory { get; internal set; } 23 | public long? CurrentMemoryUsed { get; internal set; } 24 | public long? CurrentVirtualMemoryUsed { get; internal set; } 25 | 26 | public decimal? PercentCPU { get; internal set; } 27 | public decimal? PercentMemory { get; internal set; } 28 | public long? MemoryUsed { get; internal set; } 29 | public long? VirtualMemoryUsed { get; internal set; } 30 | public string ErrorMessage { get; internal set; } 31 | 32 | public bool IsRunning { get; internal set; } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Dashboard/DashboardRoles.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.Dashboard 2 | { 3 | public static class DashboardRoles 4 | { 5 | public const string Admin = nameof(DashboardModule) + ":" + nameof(Admin); 6 | public const string Viewer = nameof(DashboardModule) + ":" + nameof(Viewer); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Dashboard/GraphPoint.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.Dashboard 2 | { 3 | public interface IGraphPoint 4 | { 5 | long DateEpoch { get; } 6 | } 7 | 8 | public class GraphPoint : IGraphPoint 9 | { 10 | /// 11 | /// Date represented as a unix epoch 12 | /// 13 | public virtual long DateEpoch { get; set; } 14 | /// 15 | /// Value of the top (or only) point 16 | /// 17 | public virtual double? Value { get; set; } 18 | } 19 | 20 | public class DoubleGraphPoint : GraphPoint 21 | { 22 | /// 23 | /// Value of the bottom (or second) point 24 | /// 25 | public virtual double? BottomValue { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Dashboard/HardwareType.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.Dashboard 2 | { 3 | public enum HardwareType 4 | { 5 | Unknown = 0, 6 | Physical = 1, 7 | VirtualMachine = 2, 8 | Network = 3 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Dashboard/IServiceControlProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Opserver.Data.Dashboard 4 | { 5 | public interface IServiceControlProvider 6 | { 7 | Task UpdateServiceAsync(Node node, string serviceName, NodeService.Action action); 8 | } 9 | 10 | public static class ServiceControlProviderExtensions 11 | { 12 | public static bool HasServiceControl(this Node node) => node?.DataProvider is IServiceControlProvider; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Dashboard/Node.Polling.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Opserver.Helpers; 4 | 5 | namespace Opserver.Data.Dashboard 6 | { 7 | public partial class Node 8 | { 9 | public async Task> GetCPUUtilization() 10 | { 11 | if (OperatingSystem.IsWindows()) 12 | { 13 | return await PerfCounters.Windows.GetCPUUtilization(Ip); 14 | } 15 | return new PerfCounters.QueryResult 16 | { 17 | Duration = TimeSpan.Zero 18 | }; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Elastic/ElasticCluster.Actions.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.Elastic 2 | { 3 | public partial class ElasticCluster 4 | { 5 | public bool AllocateShard() //string index, int shard, string node) 6 | { 7 | // TODO: Implementation 8 | // if node is null - take a stab at a random one! 9 | // for real though, algorithms (space probably?) 10 | //return PostAction("/_cluster/reroute", GetRerouteCommand(new { allocate = new { index, shard, node } })); 11 | return false; 12 | } 13 | 14 | public bool MoveShard() //string index, int shard, string fromNode, string toNode) 15 | { 16 | // TODO: Implementation 17 | return false; 18 | //return PostAction("/_clusterreroute", GetRerouteCommand(new { move = new {index, shard, from_node = fromNode, to_node = toNode} })); 19 | } 20 | 21 | public bool CancelInitialization() //string index, int shard, string node) 22 | { 23 | // TODO: Implementation 24 | return false; 25 | //return PostAction("/_cluster/reroute", GetRerouteCommand(new { cancel = new {index, shard, node} })); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Elastic/ElasticCluster.Issues.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Opserver.Data.Elastic 4 | { 5 | public partial class ElasticCluster : IIssuesProvider 6 | { 7 | string IIssuesProvider.Name => "Elastic"; 8 | 9 | public IEnumerable GetIssues() 10 | { 11 | if (MonitorStatus != MonitorStatus.Good && LastPoll.HasValue) 12 | { 13 | yield return new Issue(this, "Elastic", Name) { IsCluster = true }; 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Elastic/ElasticModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.Extensions.Configuration; 5 | 6 | namespace Opserver.Data.Elastic 7 | { 8 | public class ElasticModule : StatusModule 9 | { 10 | public override string Name => "Elastic"; 11 | public override bool Enabled => Clusters.Count > 0; 12 | 13 | public List Clusters { get; } 14 | 15 | public ElasticModule(IConfiguration config, PollingService poller) : base(config, poller) 16 | { 17 | Clusters = Settings.Clusters 18 | .Select(c => new ElasticCluster(this, c)) 19 | .Where(i => i.TryAddToGlobalPollers()) 20 | .ToList(); 21 | } 22 | 23 | public override MonitorStatus MonitorStatus => Clusters.GetWorstStatus(); 24 | public override bool IsMember(string node) 25 | { 26 | return Clusters.Any(c => c.KnownNodes.Any(sn => string.Equals(sn.Host, node, StringComparison.InvariantCultureIgnoreCase))); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Elastic/ElasticRoles.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.Elastic 2 | { 3 | public static class ElasticRoles 4 | { 5 | public const string Admin = nameof(ElasticModule) + ":" + nameof(Admin); 6 | public const string Viewer = nameof(ElasticModule) + ":" + nameof(Viewer); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Elastic/ShardStates.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.Elastic 2 | { 3 | public static class ShardStates 4 | { 5 | public const string Unassigned = "UNASSIGNED"; 6 | public const string Initializing = "INITIALIZING"; 7 | public const string Started = "STARTED"; 8 | public const string Relocating = "RELOCATING"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Exceptions/ExceptionSorts.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.Exceptions 2 | { 3 | public enum ExceptionSorts 4 | { 5 | TimeDesc = 0, 6 | TimeAsc = 1, 7 | AppAsc = 2, 8 | AppDesc = 3, 9 | TypeAsc = 4, 10 | TypeDesc = 5, 11 | MessageAsc = 6, 12 | MessageDesc = 7, 13 | UrlAsc = 8, 14 | UrlDesc = 9, 15 | IPAddressAsc = 10, 16 | IPAddressDesc = 11, 17 | HostAsc = 12, 18 | HostDesc = 13, 19 | MachineNameAsc = 14, 20 | MachineNameDesc = 15, 21 | CountAsc = 16, 22 | CountDesc = 17 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Exceptions/ExceptionsRoles.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.Exceptions 2 | { 3 | public static class ExceptionsRoles 4 | { 5 | public const string Admin = nameof(ExceptionsModule) + ":" + nameof(Admin); 6 | public const string Viewer = nameof(ExceptionsModule) + ":" + nameof(Viewer); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/GlobalPollingStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Opserver.Data 5 | { 6 | public class GlobalPollingStatus : IMonitorStatus 7 | { 8 | public MonitorStatus MonitorStatus { get; internal set; } 9 | public string MonitorStatusReason { get; internal set; } 10 | public DateTime StartTime { get; internal set; } 11 | public DateTime? LastPollAll { get; internal set; } 12 | public bool IsAlive { get; internal set; } 13 | public long TotalPollIntervals { get; internal set; } 14 | public long ActivePolls { get; internal set; } 15 | public int NodeCount { get; internal set; } 16 | public int TotalPollers { get; internal set; } 17 | public List> NodeBreakdown { get; internal set; } 18 | public List Nodes { get; internal set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/HAProxy/Backend.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Opserver.Data.HAProxy 4 | { 5 | /// 6 | /// Represents an HAProxy backend for a proxy 7 | /// 8 | public class Backend : Item 9 | { 10 | public List Servers { get; internal set; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Opserver.Core/Data/HAProxy/Frontend.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.HAProxy 2 | { 3 | /// 4 | /// Represents a HAProxy Frontend for a proxy 5 | /// 6 | public class Frontend : Item 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /src/Opserver.Core/Data/HAProxy/HAProxyGroup.Issues.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Opserver.Data.HAProxy 5 | { 6 | public partial class HAProxyGroup : IIssuesProvider 7 | { 8 | string IIssuesProvider.Name => "HAProxy"; 9 | 10 | public IEnumerable GetIssues() 11 | { 12 | if (MonitorStatus != MonitorStatus.Good && Instances.Any(i => i.LastPoll.HasValue)) 13 | { 14 | yield return new Issue(this, "HAProxy", Name); 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/HAProxy/HAProxyRoles.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.HAProxy 2 | { 3 | public static class HAProxyRoles 4 | { 5 | public const string Admin = nameof(HAProxyModule) + ":" + nameof(Admin); 6 | public const string Viewer = nameof(HAProxyModule) + ":" + nameof(Viewer); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/HAProxy/Server.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.HAProxy 2 | { 3 | public class Server : Item 4 | { 5 | public string Name => ServerName; 6 | } 7 | } -------------------------------------------------------------------------------- /src/Opserver.Core/Data/HAProxy/Socket.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.HAProxy 2 | { 3 | /// 4 | /// Represents a Socket interface in HAProxy (a Frontend/backend are a tied combination here) 5 | /// 6 | public class Socket : Item 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /src/Opserver.Core/Data/HAProxy/StatAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Opserver.Data.HAProxy 4 | { 5 | /// 6 | /// Represents a statistic from the proxy stat dump, since these are always added at the end in newer versions, they're parsed based on position. 7 | /// 8 | [AttributeUsage(AttributeTargets.Property)] 9 | public sealed class StatAttribute : Attribute 10 | { 11 | public int Position { get; set; } 12 | public string Name { get; set; } 13 | 14 | public StatAttribute(string name, int position) 15 | { 16 | Position = position; 17 | Name = name; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/HAProxy/StatusType.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.HAProxy 2 | { 3 | public enum StatusType 4 | { 5 | Frontend = 0, 6 | Backend = 1, 7 | Server = 2, 8 | Socket = 3 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Issue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Opserver.Data 5 | { 6 | public class Issue : Issue where T : IMonitorStatus 7 | { 8 | public T Item { get; set; } 9 | 10 | public Issue(T item, string type, string title, DateTime? date = null) 11 | { 12 | Type = type; 13 | Title = title; 14 | Date = date ?? DateTime.UtcNow; 15 | Item = item; 16 | MonitorStatus = item.MonitorStatus; 17 | Description = item.MonitorStatusReason; 18 | } 19 | } 20 | 21 | public class Issue : IMonitorStatus 22 | { 23 | public string Title { get; set; } 24 | public string Description { get; set; } 25 | public string Type { get; set; } 26 | /// 27 | /// Whether this issue is a service rather than a node - presumably an entire service being offline is worse 28 | /// 29 | public bool IsCluster { get; set; } 30 | public DateTime Date { get; set; } 31 | public MonitorStatus MonitorStatus { get; set; } 32 | public string MonitorStatusReason => Description; 33 | } 34 | 35 | public interface IIssuesProvider 36 | { 37 | string Name { get; } 38 | IEnumerable GetIssues(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/MonitorStatus.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Opserver.Data 4 | { 5 | public enum MonitorStatus 6 | { 7 | [Description("good")] 8 | Good = 0, 9 | [Description("unknown")] 10 | Unknown = 1, 11 | [Description("maintenance")] 12 | Maintenance = 2, 13 | [Description("warning")] 14 | Warning = 3, 15 | [Description("critical")] 16 | Critical = 4 17 | } 18 | 19 | public interface IMonitoredService : IMonitorStatus { } 20 | 21 | public interface IMonitorStatus 22 | { 23 | MonitorStatus MonitorStatus { get; } 24 | string MonitorStatusReason { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/NodeRole.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace Opserver.Data 5 | { 6 | public class NodeRole 7 | { 8 | public string Node { get; set; } 9 | public string Service { get; set; } 10 | public string Description { get; set; } 11 | public bool Active { get; set; } 12 | public int? SiblingsActive { get; set; } 13 | public int? SiblingsInactive { get; set; } 14 | } 15 | 16 | public interface INodeRoleProvider 17 | { 18 | IEnumerable GetRoles(string node); 19 | Task EnableAsync(string node); 20 | Task DisableAsync(string node); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/PagerDuty/IncidentStatus.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Opserver.Data.PagerDuty 4 | { 5 | public enum IncidentStatus 6 | { 7 | // ReSharper disable InconsistentNaming 8 | [Description("Triggered")] 9 | triggered = 0, 10 | [Description("Acknowledged")] 11 | acknowledged = 1, 12 | [Description("Resolved")] 13 | resolved = 2 14 | // ReSharper restore InconsistentNaming 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/PagerDuty/PagerDutyModule.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace Opserver.Data.PagerDuty 4 | { 5 | public class PagerDutyModule : StatusModule 6 | { 7 | public override string Name => "PagerDuty"; 8 | public override bool Enabled => Settings.Enabled; 9 | 10 | public PagerDutyAPI API { get; } 11 | 12 | public PagerDutyModule(IConfiguration config, PollingService poller) : base(config, poller) 13 | { 14 | if (Settings.Enabled) 15 | { 16 | API = new PagerDutyAPI(this); 17 | API.TryAddToGlobalPollers(); 18 | } 19 | } 20 | 21 | public override MonitorStatus MonitorStatus => API.MonitorStatus; 22 | public override bool IsMember(string node) => false; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/PagerDuty/PagerDutyRoles.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.PagerDuty 2 | { 3 | public static class PagerDutyRoles 4 | { 5 | public const string Admin = nameof(PagerDutyModule) + ":" + nameof(Admin); 6 | public const string Viewer = nameof(PagerDutyModule) + ":" + nameof(Viewer); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/PollNode.Equality.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data 2 | { 3 | public partial class PollNode 4 | { 5 | public bool Equals(PollNode other) 6 | { 7 | if (other is null) return false; 8 | if (ReferenceEquals(this, other)) return true; 9 | return other.GetType() == GetType() && string.Equals(UniqueKey, other.UniqueKey); 10 | } 11 | 12 | public override int GetHashCode() => UniqueKey?.GetHashCode() ?? 0; 13 | 14 | public static bool operator ==(PollNode left, PollNode right) => Equals(left, right); 15 | 16 | public static bool operator !=(PollNode left, PollNode right) => !Equals(left, right); 17 | 18 | public override bool Equals(object obj) 19 | { 20 | if (obj is null) return false; 21 | if (ReferenceEquals(this, obj)) return true; 22 | if (obj.GetType() != GetType()) return false; 23 | return Equals((PollNode)obj); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/PollNode.Events.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Opserver.Data 4 | { 5 | public partial class PollNode 6 | { 7 | public event EventHandler MonitorStatusChanged; 8 | public event EventHandler Polling; 9 | public event EventHandler Polled; 10 | 11 | public class PollStartArgs : EventArgs 12 | { 13 | /// 14 | /// Whether to abort the poll 15 | /// 16 | public bool AbortPoll { get; set; } 17 | } 18 | 19 | public class PollResultArgs : EventArgs { } 20 | 21 | public class MonitorStatusArgs : EventArgs 22 | { 23 | public MonitorStatus OldMonitorStatus { get; internal set; } 24 | public MonitorStatus NewMonitorStatus { get; internal set; } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/PollingService.NodeRoles.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace Opserver.Data 6 | { 7 | public partial class PollingService 8 | { 9 | internal ConcurrentBag NodeRoleProviders { get; } = new ConcurrentBag(); 10 | 11 | public IEnumerable GetNodeRoles(string node) 12 | { 13 | foreach (var p in NodeRoleProviders) 14 | { 15 | foreach (var r in p.GetRoles(node)) 16 | { 17 | yield return r; 18 | } 19 | } 20 | } 21 | 22 | public async Task EnableAllNodeRolesAsync(string node) 23 | { 24 | var tasks = new List>(); 25 | foreach (var p in NodeRoleProviders) 26 | { 27 | tasks.Add(p.EnableAsync(node)); 28 | } 29 | await Task.WhenAll(tasks); 30 | return tasks.TrueForAll(b => b.Result); 31 | } 32 | 33 | public async Task DisableAllNodeRolesAsync(string node) 34 | { 35 | var tasks = new List>(); 36 | foreach (var p in NodeRoleProviders) 37 | { 38 | tasks.Add(p.DisableAsync(node)); 39 | } 40 | await Task.WhenAll(tasks); 41 | return tasks.TrueForAll(b => b.Result); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/PollingService.Stats.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Opserver.Helpers; 4 | 5 | namespace Opserver.Data 6 | { 7 | public partial class PollingService 8 | { 9 | public GlobalPollingStatus GetPollingStatus() => new GlobalPollingStatus 10 | { 11 | MonitorStatus = _globalPollingThread.IsAlive ? (AllPollNodes.Count > 0 ? MonitorStatus.Good : MonitorStatus.Unknown) : MonitorStatus.Critical, 12 | MonitorStatusReason = _globalPollingThread.IsAlive ? (AllPollNodes.Count > 0 ? null : "No Poll Nodes") : "Global Polling Thread Dead", 13 | StartTime = _startTime, 14 | LastPollAll = _lastPollAll, 15 | IsAlive = _globalPollingThread.IsAlive, 16 | TotalPollIntervals = _totalPollIntervals, 17 | ActivePolls = _globalActivePolls, 18 | NodeCount = AllPollNodes.Count, 19 | TotalPollers = AllPollNodes.Sum(n => n.DataPollers.Count()), 20 | NodeBreakdown = AllPollNodes.GroupBy(n => n.GetType()).Select(g => Tuple.Create(g.Key, g.Count())).ToList(), 21 | Nodes = AllPollNodes.ToList() 22 | }; 23 | 24 | public ThreadStats GetThreadStats() => new ThreadStats(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Redis/RedisConnectionInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using Opserver.Helpers; 4 | using StackExchange.Redis; 5 | 6 | namespace Opserver.Data.Redis 7 | { 8 | public class RedisConnectionInfo 9 | { 10 | private AddressCache AddressCache { get; } 11 | public string Name => Settings.Name; 12 | public string Host => Server.HostName; 13 | public int Port => Settings.Port; 14 | public string Password => Settings.Password; 15 | public RedisFeatures Features { get; internal set; } 16 | public RedisHost Server { get; } 17 | internal RedisSettings.Instance Settings { get; set; } 18 | 19 | internal RedisConnectionInfo(RedisHost server, RedisSettings.Instance settings, AddressCache addressCache) 20 | { 21 | Server = server; 22 | Settings = settings; 23 | AddressCache = addressCache; 24 | } 25 | 26 | public List IPAddresses => AddressCache.GetHostAddresses(Host); 27 | 28 | public override string ToString() => $"{Name} ({Host}:{Port})"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Redis/RedisInfo.PropertyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Opserver.Data.Redis 4 | { 5 | [AttributeUsage(AttributeTargets.Property)] 6 | public sealed class RedisInfoPropertyAttribute : Attribute 7 | { 8 | public string PropertyName { get; } 9 | public RedisInfoPropertyAttribute(string propertyName) 10 | { 11 | PropertyName = propertyName; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Redis/RedisInstance.Clients.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using StackExchange.Profiling; 4 | using StackExchange.Redis; 5 | 6 | namespace Opserver.Data.Redis 7 | { 8 | public partial class RedisInstance 9 | { 10 | private Cache> _clients; 11 | public Cache> Clients => 12 | _clients ??= GetRedisCache(60.Seconds(), async () => 13 | { 14 | using (MiniProfiler.Current.CustomTiming("redis", "CLIENT LIST")) 15 | { 16 | var result = await Connection.GetSingleServer().ClientListAsync(); 17 | return result.ToList(); 18 | } 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Redis/RedisInstance.Issues.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Opserver.Data.Redis 4 | { 5 | public partial class RedisInstance : IIssuesProvider 6 | { 7 | string IIssuesProvider.Name => "Redis"; 8 | 9 | public IEnumerable GetIssues() 10 | { 11 | if (MonitorStatus != MonitorStatus.Good && LastPoll.HasValue) 12 | { 13 | yield return new Issue(this, "Redis", Name); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Redis/RedisReplicationGroup.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Opserver.Data.Redis 4 | { 5 | public class RedisReplicationGroup 6 | { 7 | public string Name { get; } 8 | public List Hosts { get; internal set; } 9 | 10 | public RedisReplicationGroup(string name, List hosts) 11 | { 12 | Name = name; 13 | Hosts = hosts; 14 | 15 | foreach (var h in hosts) 16 | { 17 | h.ReplicationGroup = this; 18 | } 19 | } 20 | 21 | public override string ToString() => Name; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/Redis/RedisRoles.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.Redis 2 | { 3 | public static class RedisRoles 4 | { 5 | public const string Admin = nameof(RedisModule) + ":" + nameof(Admin); 6 | public const string Viewer = nameof(RedisModule) + ":" + nameof(Viewer); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/SQL/ISQLVersioned.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.SQL 2 | { 3 | public interface ISQLVersioned : IMinVersioned 4 | { 5 | SQLServerEditions SupportedEditions { get; } 6 | 7 | string GetFetchSQL(in SQLServerEngine e); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/SQL/SQLCluster.Issues.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Opserver.Data.SQL 4 | { 5 | public partial class SQLCluster : IIssuesProvider 6 | { 7 | string IIssuesProvider.Name => "SQL"; 8 | 9 | public IEnumerable GetIssues() 10 | { 11 | foreach (var ag in AvailabilityGroups.WithIssues()) 12 | { 13 | yield return new Issue(ag, "SQL Availability Group", ag.Name) { IsCluster = true }; 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/SQL/SQLInstance.Errors.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Opserver.Data.SQL 6 | { 7 | public partial class SQLInstance 8 | { 9 | public LightweightCache> GetErrorLog(int minutesAgo) 10 | { 11 | return TimedCache("ErrorInfo-" + minutesAgo.ToString(), 12 | conn => 13 | { 14 | var sql = GetFetchSQL(); 15 | return conn.Query(sql, new { minutesAgo }).AsList(); 16 | }, RefreshInterval, 5.Minutes()); 17 | } 18 | 19 | public class SQLErrorLogInfo : ISQLVersioned 20 | { 21 | Version IMinVersioned.MinVersion => SQLServerVersions.SQL2005.RTM; 22 | SQLServerEditions ISQLVersioned.SupportedEditions => SQLServerEditions.All; 23 | 24 | public DateTime LogDate { get; internal set; } 25 | public string ProcessInfo { get; internal set; } 26 | public string Text { get; internal set; } 27 | 28 | public string GetFetchSQL(in SQLServerEngine e) => @" 29 | Declare @Time_Start varchar(30); 30 | Set @Time_Start = DATEADD(mi, -@minutesAgo, GETDATE()); 31 | Declare @ErrorLog Table (LogDate datetime, ProcessInfo varchar(255), Text varchar(max)); 32 | Insert Into @ErrorLog Exec master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @Time_Start, NULL; 33 | Select * From @ErrorLog Order By LogDate Desc;"; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/SQL/SQLInstance.Issues.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Opserver.Data.SQL 4 | { 5 | public partial class SQLInstance : IIssuesProvider 6 | { 7 | string IIssuesProvider.Name => "SQL"; 8 | 9 | public IEnumerable GetIssues() 10 | { 11 | if (MonitorStatus != MonitorStatus.Good) 12 | { 13 | yield return new Issue(this, "SQL Server", Name); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/SQL/SQLInstance.TraceFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Opserver.Data.SQL 5 | { 6 | public partial class SQLInstance 7 | { 8 | private Cache> _traceFlags; 9 | public Cache> TraceFlags => _traceFlags ??= SqlCacheList(5.Minutes()); 10 | 11 | public class TraceFlagInfo : ISQLVersioned 12 | { 13 | // This likely works fine on 6+, need to test 14 | Version IMinVersioned.MinVersion => SQLServerVersions.SQL2000.RTM; 15 | SQLServerEditions ISQLVersioned.SupportedEditions => SQLServerEditions.All; 16 | 17 | public int TraceFlag { get; internal set; } 18 | public bool Enabled { get; internal set; } 19 | public bool Global { get; internal set; } 20 | public int Session { get; internal set; } 21 | 22 | public string GetFetchSQL(in SQLServerEngine e) => @" 23 | Declare @Flags Table(TraceFlag INT, Enabled BIT, Global BIT, Session INT); 24 | Insert Into @Flags Exec('DBCC TRACESTATUS (-1) WITH NO_INFOMSGS'); 25 | Select * From @Flags;"; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/SQL/SQLInstance.Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Opserver.Data.SQL 5 | { 6 | public partial class SQLInstance 7 | { 8 | /// 9 | /// Removes a query plan from the cache 10 | /// 11 | /// The handle of the plan to fetch 12 | public async Task RemovePlanAsync(byte[] planHandle) 13 | { 14 | try 15 | { 16 | using var conn = await GetConnectionAsync(); 17 | return await conn.ExecuteAsync("DBCC FREEPROCCACHE (@planHandle);", new { planHandle }); 18 | } 19 | catch (Exception ex) 20 | { 21 | ex.Log(); 22 | return 0; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/SQL/SQLNode.AvailabilityGroups.AGListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Opserver.Data.SQL 5 | { 6 | public partial class SQLNode 7 | { 8 | /// 9 | /// http://msdn.microsoft.com/en-us/library/hh231328.aspx 10 | /// 11 | public class AGListener : ISQLVersioned 12 | { 13 | Version IMinVersioned.MinVersion => SQLServerVersions.SQL2012.RTM; 14 | SQLServerEditions ISQLVersioned.SupportedEditions => SQLServerEditions.All; 15 | 16 | public Guid GroupId { get; internal set; } 17 | public string ListenerId { get; internal set; } 18 | public string DnsName { get; internal set; } 19 | public int Port { get; internal set; } 20 | public bool IsConformant { get; internal set; } 21 | public string IPConfigurationString { get; internal set; } 22 | 23 | public List Addresses { get; internal set; } 24 | public List LocalAddresses { get; internal set; } 25 | 26 | public string GetFetchSQL(in SQLServerEngine e) => @" 27 | Select group_id GroupId, 28 | listener_id ListenerId, 29 | dns_name DNSName, 30 | port Port, 31 | is_conformant IsConformant, 32 | ip_configuration_string_from_cluster IPConfigurationString 33 | From sys.availability_group_listeners;"; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/SQL/SQLRoles.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Data.SQL 2 | { 3 | public static class SQLRoles 4 | { 5 | public const string Admin = nameof(SQLModule) + ":" + nameof(Admin); 6 | public const string Viewer = nameof(SQLModule) + ":" + nameof(Viewer); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Opserver.Core/Data/SearchResult.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace Opserver.Data 4 | { 5 | public class SearchResult : SearchResult 6 | { 7 | [IgnoreDataMember] 8 | public T Item { get; set; } 9 | } 10 | 11 | public class SearchResult : IMonitorStatus 12 | { 13 | public MonitorStatus MonitorStatus { get; set; } 14 | public string MonitorStatusReason { get; set; } 15 | public string Title { get; set; } 16 | public string Description { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Opserver.Core/ExtensionMethods.Exceptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using StackExchange.Exceptional; 3 | 4 | namespace Opserver 5 | { 6 | public static partial class ExtensionMethods 7 | { 8 | /// 9 | /// Manually write an exception to our standard exception log. 10 | /// 11 | /// The to log. 12 | public static void Log(this Exception exception) => exception.LogNoContext(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Opserver.Core/ExtensionMethods.Sequences.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Text; 4 | 5 | namespace Opserver 6 | { 7 | public static partial class ExtensionMethods 8 | { 9 | internal static string GetString(this ref SequenceReader reader, int length) 10 | { 11 | var value = Encoding.UTF8.GetString(reader.UnreadSpan.Slice(0, length).Trim((byte)0)); 12 | reader.Advance(length); 13 | return value; 14 | } 15 | 16 | internal static string GetBase64EncodedString(this ref SequenceReader reader, int length) 17 | { 18 | var value = Convert.ToBase64String(reader.UnreadSpan.Slice(0, length)); 19 | reader.Advance(length); 20 | return value; 21 | } 22 | 23 | internal static bool TryReadBigEndian(this ref SequenceReader reader, out double value) 24 | { 25 | if (!reader.TryReadBigEndian(out long longValue)) 26 | { 27 | value = default; 28 | return false; 29 | } 30 | 31 | value = BitConverter.Int64BitsToDouble(longValue); 32 | return true; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Opserver.Core/ExtensionMethods.Time.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Opserver 4 | { 5 | public static partial class ExtensionMethods 6 | { 7 | // TODO: Convert these to TicksPerSecond constructor, faster. 8 | public static TimeSpan Seconds(this int seconds) => TimeSpan.FromSeconds(seconds); 9 | public static TimeSpan Minutes(this int minutes) => TimeSpan.FromMinutes(minutes); 10 | public static TimeSpan Hours(this int hours) => TimeSpan.FromHours(hours); 11 | public static TimeSpan Days(this int days) => TimeSpan.FromDays(days); 12 | // https://stackoverflow.com/a/20046261/871146 13 | public static DateTime RoundDown(this DateTime dt, TimeSpan d) 14 | { 15 | var delta = dt.Ticks % d.Ticks; 16 | return new DateTime(dt.Ticks - delta, dt.Kind); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Opserver.Core/Helpers/OpserverConfigException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace Opserver.Helpers 5 | { 6 | public class OpserverConfigException : Exception 7 | { 8 | public OpserverConfigException() { } 9 | public OpserverConfigException(string message) : base(message) { } 10 | public OpserverConfigException(string message, Exception innerException) : base(message, innerException) { } 11 | protected OpserverConfigException(SerializationInfo info, StreamingContext context) { } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Opserver.Core/Helpers/Singleton.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Helpers 2 | { 3 | public static class Singleton where T : new() 4 | { 5 | public static readonly T Instance = new T(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Opserver.Core/Helpers/SortDir.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver 2 | { 3 | public enum SortDir 4 | { 5 | Asc, 6 | Desc 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Opserver.Core/Helpers/ThreadStats.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | 3 | namespace Opserver.Helpers 4 | { 5 | public class ThreadStats 6 | { 7 | private readonly int _minWorkerThreads; 8 | public int MinWorkerThreads => _minWorkerThreads; 9 | 10 | private readonly int _minIOThreads; 11 | public int MinIOThreads => _minIOThreads; 12 | 13 | private readonly int _availableWorkerThreads; 14 | public int AvailableWorkerThreads => _availableWorkerThreads; 15 | 16 | private readonly int _availableIOThreads; 17 | public int AvailableIOThreads => _availableIOThreads; 18 | 19 | private readonly int _maxIOThreads; 20 | public int MaxIOThreads => _maxIOThreads; 21 | 22 | private readonly int _maxWorkerThreads; 23 | public int MaxWorkerThreads => _maxWorkerThreads; 24 | 25 | public int BusyIOThreads => _maxIOThreads - _availableIOThreads; 26 | public int BusyWorkerThreads => _maxWorkerThreads - _availableWorkerThreads; 27 | 28 | public ThreadStats() 29 | { 30 | ThreadPool.GetMinThreads(out _minWorkerThreads, out _minIOThreads); 31 | ThreadPool.GetAvailableThreads(out _availableWorkerThreads, out _availableIOThreads); 32 | ThreadPool.GetMaxThreads(out _maxWorkerThreads, out _maxIOThreads); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Opserver.Core/Helpers/WindowsKernelVersions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Opserver.Helpers 4 | { 5 | public static class WindowsKernelVersions 6 | { 7 | public static readonly Version WindowsNT31 = new Version(3, 1); 8 | public static readonly Version WindowsNT35 = new Version(3, 5); 9 | public static readonly Version WindowsNT351 = new Version(3, 51); 10 | public static readonly Version WindowsNT4 = new Version(4, 0); 11 | public static readonly Version Windows2000 = new Version(5, 0); 12 | public static readonly Version WindowsXP = new Version(5, 1); 13 | public static readonly Version Windows2003AndXP64 = new Version(5, 2); 14 | public static readonly Version Windows2008AndVista = new Version(6, 0); 15 | public static readonly Version Windows2008R2And7 = new Version(6, 1); 16 | public static readonly Version Windows2012And8 = new Version(6, 2); 17 | public static readonly Version Windows2012R2And81 = new Version(6, 3); 18 | public static readonly Version Windows2016And10 = new Version(10, 0); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Opserver.Core/IMinVersioned.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace Opserver 5 | { 6 | public interface IMinVersioned 7 | { 8 | [IgnoreDataMember] 9 | Version MinVersion { get; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Opserver.Core/IOverallStatusCount.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver 2 | { 3 | public interface IOverallStatusCount 4 | { 5 | int Count { get; } 6 | string Tooltip { get; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Opserver.Core/ISeachableNode.cs: -------------------------------------------------------------------------------- 1 | using Opserver.Data; 2 | 3 | namespace Opserver 4 | { 5 | public interface ISearchableNode 6 | { 7 | string DisplayName { get; } 8 | string Name { get; } 9 | string CategoryName { get; } 10 | MonitorStatus MonitorStatus { get; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Opserver.Core/Models/Roles.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Models 2 | { 3 | public static class Roles 4 | { 5 | public const string Anonymous = nameof(Anonymous); 6 | public const string Authenticated = nameof(Authenticated); 7 | 8 | public const string LocalRequest = nameof(LocalRequest); 9 | public const string InternalRequest = nameof(InternalRequest); 10 | public const string ApiRequest = nameof(ApiRequest); 11 | 12 | public const string GlobalAdmin = nameof(GlobalAdmin); 13 | public const string GlobalViewer = nameof(GlobalViewer); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Opserver.Core/Settings/CloudFlareSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Opserver.Data.Cloudflare; 3 | 4 | namespace Opserver 5 | { 6 | public class CloudflareSettings : ModuleSettings 7 | { 8 | public override bool Enabled => Email.HasValue() && APIKey.HasValue(); 9 | public override string AdminRole => CloudflareRoles.Admin; 10 | public override string ViewRole => CloudflareRoles.Viewer; 11 | public List DataCenters { get; set; } = new List(); 12 | 13 | /// 14 | /// Email for the Cloudflare account 15 | /// 16 | public string Email { get; set; } 17 | 18 | /// 19 | /// APIKey for the Cloudflare account 20 | /// 21 | public string APIKey { get; set; } 22 | 23 | public class DataCenter : ISettingsCollectionItem 24 | { 25 | /// 26 | /// The name for this data center 27 | /// 28 | public string Name { get; set; } 29 | 30 | /// 31 | /// The IP ranges for this data center, in CIDR format 32 | /// 33 | public List Ranges { get; set; } = new List(); 34 | 35 | /// 36 | /// The masked IP ranges for this data center, in CIDR format 37 | /// 38 | public List MaskedRanges { get; set; } = new List(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Opserver.Core/Settings/DashboardSettings.Bosun.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver 2 | { 3 | public class BosunSettings : IProviderSettings 4 | { 5 | public bool Enabled => Host.HasValue(); 6 | public string Name => "Bosun"; 7 | 8 | /// 9 | /// Endpoint for Bosun APIs, e.g. "bosun.mydomain.com", also accepts http or https:// forms 10 | /// 11 | public string Host { get; set; } 12 | 13 | /// 14 | /// API Key for accessing Bosun APIs 15 | /// 16 | public string APIKey { get; set; } 17 | 18 | /// 19 | /// Whether to ignore the Bosun ping status. If true, unreachable nodes will not be marked as such. 20 | /// 21 | public bool IgnorePing { get; set; } 22 | 23 | public void Normalize() 24 | { 25 | Host = Host.NormalizeHostOrFQDN(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Opserver.Core/Settings/DashboardSettings.Orion.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver 2 | { 3 | public class OrionSettings : IProviderSettings 4 | { 5 | public bool Enabled => Host.HasValue(); 6 | public string Name => "Orion"; 7 | 8 | /// 9 | /// The host for Orion, used for generating links 10 | /// 11 | public string Host { get; set; } 12 | 13 | /// 14 | /// The connection string for this provider 15 | /// 16 | public string ConnectionString { get; set; } 17 | 18 | /// 19 | /// Default maximum timeout in milliseconds before giving up on fetching data from this provider 20 | /// 21 | public int QueryTimeoutMs { get; set; } = 10 * 1000; 22 | 23 | /// 24 | /// Whether to show child statuses if the node is overall healthy. Example: warning if any drives are in warning state. 25 | /// 26 | public bool ChildStatusForHealthy { get; set; } = false; 27 | 28 | public void Normalize() 29 | { 30 | Host = Host.NormalizeHostOrFQDN(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Opserver.Core/Settings/DashboardSettings.Providers.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Opserver 5 | { 6 | public class ProvidersSettings 7 | { 8 | public BosunSettings Bosun { get; set; } 9 | public OrionSettings Orion { get; set; } 10 | public WMISettings WMI { get; set; } 11 | public SignalFxSettings SignalFx { get; set; } 12 | 13 | public bool Any() => All.Any(p => p != null); 14 | 15 | public IEnumerable All 16 | { 17 | get 18 | { 19 | yield return Bosun; 20 | yield return Orion; 21 | yield return WMI; 22 | yield return SignalFx; 23 | } 24 | } 25 | } 26 | 27 | public interface IProviderSettings 28 | { 29 | bool Enabled { get; } 30 | string Name { get; } 31 | 32 | void Normalize(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Opserver.Core/Settings/DashboardSettings.SignalFx.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver 2 | { 3 | public class SignalFxSettings : IProviderSettings 4 | { 5 | public bool Enabled => AccessToken.HasValue(); 6 | public string Name => "SignalFx"; 7 | 8 | /// 9 | /// Realm for SignalFx, e.g. "us1", "eu0" 10 | /// 11 | public string Realm { get; set; } 12 | 13 | /// 14 | /// API Key for accessing SignalFx APIs 15 | /// 16 | public string AccessToken { get; set; } 17 | 18 | public void Normalize() 19 | { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Opserver.Core/Settings/DashboardSettings.WMI.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Opserver 4 | { 5 | public class WMISettings : IProviderSettings 6 | { 7 | public bool Enabled => Nodes.Count > 0; 8 | public string Name => "WMI"; 9 | 10 | /// 11 | /// List of hostnames to poll via WMI 12 | /// 13 | public List Nodes { get; set; } = new List(); 14 | 15 | /// 16 | /// Timeout in seconds for cache with more or less static data, like node name or volume size. 17 | /// 18 | public int StaticDataTimeoutSeconds { get; set; } = 5 * 60; 19 | 20 | /// 21 | /// Timeout in seconds for dynamic data like CPU load. 22 | /// 23 | public int DynamicDataTimeoutSeconds { get; set; } = 30; 24 | 25 | /// 26 | /// Amount of history to keep 27 | /// 28 | public int HistoryHours { get; set; } = 24; 29 | 30 | /// 31 | /// Username to use when polling (for non-domain testing) 32 | /// 33 | public string Username { get; set; } 34 | /// 35 | /// Password to use when polling (for non-domain testing) 36 | /// 37 | public string Password { get; set; } 38 | 39 | public void Normalize() {} 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Opserver.Core/Settings/Interfaces.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver 2 | { 3 | public interface ISecurableModule 4 | { 5 | bool Enabled { get; } 6 | // TODO: List 7 | string ViewGroups { get; } 8 | string AdminGroups { get; } 9 | 10 | string ViewRole { get; } 11 | string AdminRole { get; } 12 | } 13 | 14 | public interface ISettingsCollectionItem 15 | { 16 | string Name { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Opserver.Core/Settings/ModuleSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver 2 | { 3 | public abstract class ModuleSettings : ISecurableModule 4 | { 5 | /// 6 | /// Whether this section is enabled (has servers, has connection, etc.) 7 | /// 8 | public abstract bool Enabled { get; } 9 | 10 | /// 11 | /// Semilcolon delimited list of security groups that can see this section, but not perform actions. 12 | /// 13 | public string ViewGroups { get; set; } 14 | 15 | /// 16 | /// Semilcolon delimited list of security groups that can do anything in this section, including management actions. 17 | /// 18 | public string AdminGroups { get; set; } 19 | 20 | /// 21 | /// The role name to use for viewers of this module. 22 | /// 23 | public abstract string ViewRole { get; } 24 | 25 | /// 26 | /// The role name to use for admins of this module. 27 | /// 28 | public abstract string AdminRole { get; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Opserver.Core/Settings/OpserverSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver 2 | { 3 | public class OpserverSettings 4 | { 5 | public GlobalSettings Global { get; set; } = new GlobalSettings(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Opserver.Core/Settings/PagerDutySettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Opserver.Data.PagerDuty; 3 | 4 | namespace Opserver 5 | { 6 | public class PagerDutySettings : ModuleSettings 7 | { 8 | public override bool Enabled => APIKey.HasValue(); 9 | public override string AdminRole => PagerDutyRoles.Admin; 10 | public override string ViewRole => PagerDutyRoles.Viewer; 11 | 12 | public string APIKey { get; set; } 13 | public List UserNameMap { get; set; } = new List(); 14 | 15 | public int OnCallToShow { get; set; } = 2; 16 | public int DaysToCache { get; set; } = 60; 17 | public string HeaderTitle { get; set; } 18 | public string HeaderHtml { get; set; } 19 | 20 | public string PrimaryScheduleName { get; set; } 21 | } 22 | 23 | public class EmailMapping 24 | { 25 | public string OpServerName { get; set; } 26 | public string EmailUser { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Opserver.Core/StringSplits.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver 2 | { 3 | /// 4 | /// From Marc Gravell - every time a split is used you can either allocate an array 5 | /// ...or reference the single one created here and avoid that 6 | /// 7 | public static class StringSplits 8 | { 9 | public static readonly char[] 10 | Space = {' '}, 11 | Comma = {','}, 12 | Period = {'.'}, 13 | Minus = {'-'}, 14 | Plus = {'+'}, 15 | Asterisk = {'*'}, 16 | Percent = {'%'}, 17 | Ampersand = {'&'}, 18 | AtSign = {'@'}, 19 | Equal = {'='}, 20 | Underscore = {'_'}, 21 | NewLine = {'\n'}, 22 | SemiColon = {';'}, 23 | Colon = {':'}, 24 | VerticalBar = {'|'}, 25 | ForwardSlash = {'/'}, 26 | BackSlash = {'\\'}, 27 | DoubleQuote = {'"'}, 28 | Tilde = {'`'}, 29 | Period_Plus = {'.', '+'}, 30 | NewLine_CarriageReturn = {'\n', '\r'}, 31 | Comma_SemiColon = {',', ';'}, 32 | Comma_SemiColon_Space = {',', ';', ' '}, 33 | BackSlash_Slash_Period = {'\\', '/', '.'}, 34 | Numbers = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Opserver.Core/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/.csslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "adjoining-classes": false, 3 | "box-sizing": false, 4 | "box-model": false, 5 | "compatible-vendor-prefixes": false, 6 | "floats": false, 7 | "font-sizes": false, 8 | "gradients": false, 9 | "important": false, 10 | "known-properties": false, 11 | "outline-none": false, 12 | "qualified-headings": false, 13 | "regex-selectors": false, 14 | "shorthand": false, 15 | "text-indent": false, 16 | "unique-headings": false, 17 | "universal-selector": false, 18 | "unqualified-attributes": false 19 | } 20 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/badges.less: -------------------------------------------------------------------------------- 1 | // 2 | // Badges 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base class 7 | .badge { 8 | display: inline-block; 9 | min-width: 10px; 10 | padding: 3px 7px; 11 | font-size: @font-size-small; 12 | font-weight: @badge-font-weight; 13 | color: @badge-color; 14 | line-height: @badge-line-height; 15 | vertical-align: middle; 16 | white-space: nowrap; 17 | text-align: center; 18 | background-color: @badge-bg; 19 | border-radius: @badge-border-radius; 20 | 21 | // Empty badges collapse automatically (not available in IE8) 22 | &:empty { 23 | display: none; 24 | } 25 | 26 | // Quick fix for badges in buttons 27 | .btn & { 28 | position: relative; 29 | top: -1px; 30 | } 31 | 32 | .btn-xs &, 33 | .btn-group-xs > .btn & { 34 | top: 0; 35 | padding: 1px 5px; 36 | } 37 | 38 | // Hover state, but only for links 39 | a& { 40 | &:hover, 41 | &:focus { 42 | color: @badge-link-hover-color; 43 | text-decoration: none; 44 | cursor: pointer; 45 | } 46 | } 47 | 48 | // Account for badges in navs 49 | .list-group-item.active > &, 50 | .nav-pills > .active > a > & { 51 | color: @badge-active-color; 52 | background-color: @badge-active-bg; 53 | } 54 | 55 | .list-group-item > & { 56 | float: right; 57 | } 58 | 59 | .list-group-item > & + & { 60 | margin-right: 5px; 61 | } 62 | 63 | .nav-pills > li > a > & { 64 | margin-left: 3px; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/bootstrap.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | // Core variables and mixins 8 | @import "variables.less"; 9 | @import "mixins.less"; 10 | 11 | // Reset and dependencies 12 | @import "normalize.less"; 13 | @import "print.less"; 14 | 15 | // Core CSS 16 | @import "scaffolding.less"; 17 | @import "type.less"; 18 | @import "code.less"; 19 | @import "grid.less"; 20 | @import "tables.less"; 21 | @import "forms.less"; 22 | @import "buttons.less"; 23 | 24 | // Components 25 | @import "component-animations.less"; 26 | @import "dropdowns.less"; 27 | @import "button-groups.less"; 28 | @import "input-groups.less"; 29 | @import "navs.less"; 30 | @import "navbar.less"; 31 | @import "breadcrumbs.less"; 32 | @import "pagination.less"; 33 | @import "pager.less"; 34 | @import "labels.less"; 35 | @import "badges.less"; 36 | @import "jumbotron.less"; 37 | @import "thumbnails.less"; 38 | @import "alerts.less"; 39 | @import "progress-bars.less"; 40 | @import "media.less"; 41 | @import "list-group.less"; 42 | @import "panels.less"; 43 | @import "responsive-embed.less"; 44 | @import "wells.less"; 45 | @import "close.less"; 46 | 47 | // Components w/ JavaScript 48 | @import "modals.less"; 49 | @import "tooltip.less"; 50 | @import "popovers.less"; 51 | @import "carousel.less"; 52 | 53 | // Utility classes 54 | @import "utilities.less"; 55 | @import "responsive-utilities.less"; 56 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/breadcrumbs.less: -------------------------------------------------------------------------------- 1 | // 2 | // Breadcrumbs 3 | // -------------------------------------------------- 4 | 5 | 6 | .breadcrumb { 7 | padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal; 8 | margin-bottom: @line-height-computed; 9 | list-style: none; 10 | background-color: @breadcrumb-bg; 11 | border-radius: @border-radius-base; 12 | 13 | > li { 14 | display: inline-block; 15 | 16 | + li:before { 17 | content: "@{breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space 18 | padding: 0 5px; 19 | color: @breadcrumb-color; 20 | } 21 | } 22 | 23 | > .active { 24 | color: @breadcrumb-active-color; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/close.less: -------------------------------------------------------------------------------- 1 | // 2 | // Close icons 3 | // -------------------------------------------------- 4 | 5 | 6 | .close { 7 | float: right; 8 | font-size: (@font-size-base * 1.5); 9 | font-weight: @close-font-weight; 10 | line-height: 1; 11 | color: @close-color; 12 | text-shadow: @close-text-shadow; 13 | .opacity(.2); 14 | 15 | &:hover, 16 | &:focus { 17 | color: @close-color; 18 | text-decoration: none; 19 | cursor: pointer; 20 | .opacity(.5); 21 | } 22 | 23 | // Additional properties for button version 24 | // iOS requires the button element instead of an anchor tag. 25 | // If you want the anchor version, it requires `href="#"`. 26 | // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile 27 | button& { 28 | padding: 0; 29 | cursor: pointer; 30 | background: transparent; 31 | border: 0; 32 | -webkit-appearance: none; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/component-animations.less: -------------------------------------------------------------------------------- 1 | // 2 | // Component animations 3 | // -------------------------------------------------- 4 | 5 | // Heads up! 6 | // 7 | // We don't use the `.opacity()` mixin here since it causes a bug with text 8 | // fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552. 9 | 10 | .fade { 11 | opacity: 0; 12 | .transition(opacity .15s linear); 13 | &.in { 14 | opacity: 1; 15 | } 16 | } 17 | 18 | .collapse { 19 | display: none; 20 | 21 | &.in { display: block; } 22 | tr&.in { display: table-row; } 23 | tbody&.in { display: table-row-group; } 24 | } 25 | 26 | .collapsing { 27 | position: relative; 28 | height: 0; 29 | overflow: hidden; 30 | .transition-property(~"height, visibility"); 31 | .transition-duration(.35s); 32 | .transition-timing-function(ease); 33 | } 34 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/jumbotron.less: -------------------------------------------------------------------------------- 1 | // 2 | // Jumbotron 3 | // -------------------------------------------------- 4 | 5 | 6 | .jumbotron { 7 | padding-top: @jumbotron-padding; 8 | padding-bottom: @jumbotron-padding; 9 | margin-bottom: @jumbotron-padding; 10 | color: @jumbotron-color; 11 | background-color: @jumbotron-bg; 12 | 13 | h1, 14 | .h1 { 15 | color: @jumbotron-heading-color; 16 | } 17 | 18 | p { 19 | margin-bottom: (@jumbotron-padding / 2); 20 | font-size: @jumbotron-font-size; 21 | font-weight: 200; 22 | } 23 | 24 | > hr { 25 | border-top-color: darken(@jumbotron-bg, 10%); 26 | } 27 | 28 | .container &, 29 | .container-fluid & { 30 | border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container 31 | padding-left: (@grid-gutter-width / 2); 32 | padding-right: (@grid-gutter-width / 2); 33 | } 34 | 35 | .container { 36 | max-width: 100%; 37 | } 38 | 39 | @media screen and (min-width: @screen-sm-min) { 40 | padding-top: (@jumbotron-padding * 1.6); 41 | padding-bottom: (@jumbotron-padding * 1.6); 42 | 43 | .container &, 44 | .container-fluid & { 45 | padding-left: (@jumbotron-padding * 2); 46 | padding-right: (@jumbotron-padding * 2); 47 | } 48 | 49 | h1, 50 | .h1 { 51 | font-size: @jumbotron-heading-font-size; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/labels.less: -------------------------------------------------------------------------------- 1 | // 2 | // Labels 3 | // -------------------------------------------------- 4 | 5 | .label { 6 | display: inline; 7 | padding: .2em .6em .3em; 8 | font-size: 75%; 9 | font-weight: bold; 10 | line-height: 1; 11 | color: @label-color; 12 | text-align: center; 13 | white-space: nowrap; 14 | vertical-align: baseline; 15 | border-radius: .25em; 16 | 17 | // Add hover effects, but only for links 18 | a& { 19 | &:hover, 20 | &:focus { 21 | color: @label-link-hover-color; 22 | text-decoration: none; 23 | cursor: pointer; 24 | } 25 | } 26 | 27 | // Empty labels collapse automatically (not available in IE8) 28 | &:empty { 29 | display: none; 30 | } 31 | 32 | // Quick fix for labels in buttons 33 | .btn & { 34 | position: relative; 35 | top: -1px; 36 | } 37 | } 38 | 39 | // Colors 40 | // Contextual variations (linked labels get darker on :hover) 41 | 42 | .label-default { 43 | .label-variant(@label-default-bg); 44 | } 45 | 46 | .label-primary { 47 | .label-variant(@label-primary-bg); 48 | } 49 | 50 | .label-success { 51 | .label-variant(@label-success-bg); 52 | } 53 | 54 | .label-info { 55 | .label-variant(@label-info-bg); 56 | } 57 | 58 | .label-warning { 59 | .label-variant(@label-warning-bg); 60 | } 61 | 62 | .label-danger { 63 | .label-variant(@label-danger-bg); 64 | } 65 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/media.less: -------------------------------------------------------------------------------- 1 | .media { 2 | // Proper spacing between instances of .media 3 | margin-top: 15px; 4 | 5 | &:first-child { 6 | margin-top: 0; 7 | } 8 | } 9 | 10 | .media, 11 | .media-body { 12 | zoom: 1; 13 | overflow: hidden; 14 | } 15 | 16 | .media-body { 17 | width: 10000px; 18 | } 19 | 20 | .media-object { 21 | display: block; 22 | 23 | // Fix collapse in webkit from max-width: 100% and display: table-cell. 24 | &.img-thumbnail { 25 | max-width: none; 26 | } 27 | } 28 | 29 | .media-right, 30 | .media > .pull-right { 31 | padding-left: 10px; 32 | } 33 | 34 | .media-left, 35 | .media > .pull-left { 36 | padding-right: 10px; 37 | } 38 | 39 | .media-left, 40 | .media-right, 41 | .media-body { 42 | display: table-cell; 43 | vertical-align: top; 44 | } 45 | 46 | .media-middle { 47 | vertical-align: middle; 48 | } 49 | 50 | .media-bottom { 51 | vertical-align: bottom; 52 | } 53 | 54 | // Reset margins on headings for tighter default spacing 55 | .media-heading { 56 | margin-top: 0; 57 | margin-bottom: 5px; 58 | } 59 | 60 | // Media list variation 61 | // 62 | // Undo default ul/ol styles 63 | .media-list { 64 | padding-left: 0; 65 | list-style: none; 66 | } 67 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------------------------------- 3 | 4 | // Utilities 5 | @import "mixins/hide-text.less"; 6 | @import "mixins/opacity.less"; 7 | @import "mixins/image.less"; 8 | @import "mixins/labels.less"; 9 | @import "mixins/reset-filter.less"; 10 | @import "mixins/resize.less"; 11 | @import "mixins/responsive-visibility.less"; 12 | @import "mixins/size.less"; 13 | @import "mixins/tab-focus.less"; 14 | @import "mixins/reset-text.less"; 15 | @import "mixins/text-emphasis.less"; 16 | @import "mixins/text-overflow.less"; 17 | @import "mixins/vendor-prefixes.less"; 18 | 19 | // Components 20 | @import "mixins/alerts.less"; 21 | @import "mixins/buttons.less"; 22 | @import "mixins/panels.less"; 23 | @import "mixins/pagination.less"; 24 | @import "mixins/list-group.less"; 25 | @import "mixins/nav-divider.less"; 26 | @import "mixins/forms.less"; 27 | @import "mixins/progress-bar.less"; 28 | @import "mixins/table-row.less"; 29 | 30 | // Skins 31 | @import "mixins/background-variant.less"; 32 | @import "mixins/border-radius.less"; 33 | @import "mixins/gradients.less"; 34 | 35 | // Layout 36 | @import "mixins/clearfix.less"; 37 | @import "mixins/center-block.less"; 38 | @import "mixins/nav-vertical-align.less"; 39 | @import "mixins/grid-framework.less"; 40 | @import "mixins/grid.less"; 41 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/alerts.less: -------------------------------------------------------------------------------- 1 | // Alerts 2 | 3 | .alert-variant(@background; @border; @text-color) { 4 | background-color: @background; 5 | border-color: @border; 6 | color: @text-color; 7 | 8 | hr { 9 | border-top-color: darken(@border, 5%); 10 | } 11 | .alert-link { 12 | color: darken(@text-color, 10%); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/background-variant.less: -------------------------------------------------------------------------------- 1 | // Contextual backgrounds 2 | 3 | .bg-variant(@color) { 4 | background-color: @color; 5 | a&:hover, 6 | a&:focus { 7 | background-color: darken(@color, 10%); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/border-radius.less: -------------------------------------------------------------------------------- 1 | // Single side border-radius 2 | 3 | .border-top-radius(@radius) { 4 | border-top-right-radius: @radius; 5 | border-top-left-radius: @radius; 6 | } 7 | .border-right-radius(@radius) { 8 | border-bottom-right-radius: @radius; 9 | border-top-right-radius: @radius; 10 | } 11 | .border-bottom-radius(@radius) { 12 | border-bottom-right-radius: @radius; 13 | border-bottom-left-radius: @radius; 14 | } 15 | .border-left-radius(@radius) { 16 | border-bottom-left-radius: @radius; 17 | border-top-left-radius: @radius; 18 | } 19 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/center-block.less: -------------------------------------------------------------------------------- 1 | // Center-align a block level element 2 | 3 | .center-block() { 4 | display: block; 5 | margin-left: auto; 6 | margin-right: auto; 7 | } 8 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/clearfix.less: -------------------------------------------------------------------------------- 1 | // Clearfix 2 | // 3 | // For modern browsers 4 | // 1. The space content is one way to avoid an Opera bug when the 5 | // contenteditable attribute is included anywhere else in the document. 6 | // Otherwise it causes space to appear at the top and bottom of elements 7 | // that are clearfixed. 8 | // 2. The use of `table` rather than `block` is only necessary if using 9 | // `:before` to contain the top-margins of child elements. 10 | // 11 | // Source: http://nicolasgallagher.com/micro-clearfix-hack/ 12 | 13 | .clearfix() { 14 | &:before, 15 | &:after { 16 | content: " "; // 1 17 | display: table; // 2 18 | } 19 | &:after { 20 | clear: both; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/hide-text.less: -------------------------------------------------------------------------------- 1 | // CSS image replacement 2 | // 3 | // Heads up! v3 launched with only `.hide-text()`, but per our pattern for 4 | // mixins being reused as classes with the same name, this doesn't hold up. As 5 | // of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`. 6 | // 7 | // Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 8 | 9 | // Deprecated as of v3.0.1 (will be removed in v4) 10 | .hide-text() { 11 | font: ~"0/0" a; 12 | color: transparent; 13 | text-shadow: none; 14 | background-color: transparent; 15 | border: 0; 16 | } 17 | 18 | // New mixin to use as of v3.0.1 19 | .text-hide() { 20 | .hide-text(); 21 | } 22 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/image.less: -------------------------------------------------------------------------------- 1 | // Image Mixins 2 | // - Responsive image 3 | // - Retina image 4 | 5 | 6 | // Responsive image 7 | // 8 | // Keep images from scaling beyond the width of their parents. 9 | .img-responsive(@display: block) { 10 | display: @display; 11 | max-width: 100%; // Part 1: Set a maximum relative to the parent 12 | height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching 13 | } 14 | 15 | 16 | // Retina image 17 | // 18 | // Short retina mixin for setting background-image and -size. Note that the 19 | // spelling of `min--moz-device-pixel-ratio` is intentional. 20 | .img-retina(@file-1x; @file-2x; @width-1x; @height-1x) { 21 | background-image: url("@{file-1x}"); 22 | 23 | @media 24 | only screen and (-webkit-min-device-pixel-ratio: 2), 25 | only screen and ( min--moz-device-pixel-ratio: 2), 26 | only screen and ( -o-min-device-pixel-ratio: 2/1), 27 | only screen and ( min-device-pixel-ratio: 2), 28 | only screen and ( min-resolution: 192dpi), 29 | only screen and ( min-resolution: 2dppx) { 30 | background-image: url("@{file-2x}"); 31 | background-size: @width-1x @height-1x; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/labels.less: -------------------------------------------------------------------------------- 1 | // Labels 2 | 3 | .label-variant(@color) { 4 | background-color: @color; 5 | 6 | &[href] { 7 | &:hover, 8 | &:focus { 9 | background-color: darken(@color, 10%); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/list-group.less: -------------------------------------------------------------------------------- 1 | // List Groups 2 | 3 | .list-group-item-variant(@state; @background; @color) { 4 | .list-group-item-@{state} { 5 | color: @color; 6 | background-color: @background; 7 | 8 | a&, 9 | button& { 10 | color: @color; 11 | 12 | .list-group-item-heading { 13 | color: inherit; 14 | } 15 | 16 | &:hover, 17 | &:focus { 18 | color: @color; 19 | background-color: darken(@background, 5%); 20 | } 21 | &.active, 22 | &.active:hover, 23 | &.active:focus { 24 | color: #fff; 25 | background-color: @color; 26 | border-color: @color; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/nav-divider.less: -------------------------------------------------------------------------------- 1 | // Horizontal dividers 2 | // 3 | // Dividers (basically an hr) within dropdowns and nav lists 4 | 5 | .nav-divider(@color: #e5e5e5) { 6 | height: 1px; 7 | margin: ((@line-height-computed / 2) - 1) 0; 8 | overflow: hidden; 9 | background-color: @color; 10 | } 11 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/nav-vertical-align.less: -------------------------------------------------------------------------------- 1 | // Navbar vertical align 2 | // 3 | // Vertically center elements in the navbar. 4 | // Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin. 5 | 6 | .navbar-vertical-align(@element-height) { 7 | margin-top: ((@navbar-height - @element-height) / 2); 8 | margin-bottom: ((@navbar-height - @element-height) / 2); 9 | } 10 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/opacity.less: -------------------------------------------------------------------------------- 1 | // Opacity 2 | 3 | .opacity(@opacity) { 4 | opacity: @opacity; 5 | // IE8 filter 6 | @opacity-ie: (@opacity * 100); 7 | filter: ~"alpha(opacity=@{opacity-ie})"; 8 | } 9 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/pagination.less: -------------------------------------------------------------------------------- 1 | // Pagination 2 | 3 | .pagination-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) { 4 | > li { 5 | > a, 6 | > span { 7 | padding: @padding-vertical @padding-horizontal; 8 | font-size: @font-size; 9 | line-height: @line-height; 10 | } 11 | &:first-child { 12 | > a, 13 | > span { 14 | .border-left-radius(@border-radius); 15 | } 16 | } 17 | &:last-child { 18 | > a, 19 | > span { 20 | .border-right-radius(@border-radius); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/panels.less: -------------------------------------------------------------------------------- 1 | // Panels 2 | 3 | .panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) { 4 | border-color: @border; 5 | 6 | & > .panel-heading { 7 | color: @heading-text-color; 8 | background-color: @heading-bg-color; 9 | border-color: @heading-border; 10 | 11 | + .panel-collapse > .panel-body { 12 | border-top-color: @border; 13 | } 14 | .badge { 15 | color: @heading-bg-color; 16 | background-color: @heading-text-color; 17 | } 18 | } 19 | & > .panel-footer { 20 | + .panel-collapse > .panel-body { 21 | border-bottom-color: @border; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/progress-bar.less: -------------------------------------------------------------------------------- 1 | // Progress bars 2 | 3 | .progress-bar-variant(@color) { 4 | background-color: @color; 5 | 6 | // Deprecated parent class requirement as of v3.2.0 7 | .progress-striped & { 8 | #gradient > .striped(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/reset-filter.less: -------------------------------------------------------------------------------- 1 | // Reset filters for IE 2 | // 3 | // When you need to remove a gradient background, do not forget to use this to reset 4 | // the IE filter for IE9 and below. 5 | 6 | .reset-filter() { 7 | filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); 8 | } 9 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/reset-text.less: -------------------------------------------------------------------------------- 1 | .reset-text() { 2 | font-family: @font-family-base; 3 | // We deliberately do NOT reset font-size. 4 | font-style: normal; 5 | font-weight: normal; 6 | letter-spacing: normal; 7 | line-break: auto; 8 | line-height: @line-height-base; 9 | text-align: left; // Fallback for where `start` is not supported 10 | text-align: start; 11 | text-decoration: none; 12 | text-shadow: none; 13 | text-transform: none; 14 | white-space: normal; 15 | word-break: normal; 16 | word-spacing: normal; 17 | word-wrap: normal; 18 | } 19 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/resize.less: -------------------------------------------------------------------------------- 1 | // Resize anything 2 | 3 | .resizable(@direction) { 4 | resize: @direction; // Options: horizontal, vertical, both 5 | overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible` 6 | } 7 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/responsive-visibility.less: -------------------------------------------------------------------------------- 1 | // Responsive utilities 2 | 3 | // 4 | // More easily include all the states for responsive-utilities.less. 5 | .responsive-visibility() { 6 | display: block !important; 7 | table& { display: table !important; } 8 | tr& { display: table-row !important; } 9 | th&, 10 | td& { display: table-cell !important; } 11 | } 12 | 13 | .responsive-invisibility() { 14 | display: none !important; 15 | } 16 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/size.less: -------------------------------------------------------------------------------- 1 | // Sizing shortcuts 2 | 3 | .size(@width; @height) { 4 | width: @width; 5 | height: @height; 6 | } 7 | 8 | .square(@size) { 9 | .size(@size; @size); 10 | } 11 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/tab-focus.less: -------------------------------------------------------------------------------- 1 | // WebKit-style focus 2 | 3 | .tab-focus() { 4 | // Default 5 | outline: thin dotted; 6 | // WebKit 7 | outline: 5px auto -webkit-focus-ring-color; 8 | outline-offset: -2px; 9 | } 10 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/table-row.less: -------------------------------------------------------------------------------- 1 | // Tables 2 | 3 | .table-row-variant(@state; @background) { 4 | // Exact selectors below required to override `.table-striped` and prevent 5 | // inheritance to nested tables. 6 | .table > thead > tr, 7 | .table > tbody > tr, 8 | .table > tfoot > tr { 9 | > td.@{state}, 10 | > th.@{state}, 11 | &.@{state} > td, 12 | &.@{state} > th { 13 | background-color: @background; 14 | } 15 | } 16 | 17 | // Hover states for `.table-hover` 18 | // Note: this is not available for cells or rows within `thead` or `tfoot`. 19 | .table-hover > tbody > tr { 20 | > td.@{state}:hover, 21 | > th.@{state}:hover, 22 | &.@{state}:hover > td, 23 | &:hover > .@{state}, 24 | &.@{state}:hover > th { 25 | background-color: darken(@background, 5%); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/text-emphasis.less: -------------------------------------------------------------------------------- 1 | // Typography 2 | 3 | .text-emphasis-variant(@color) { 4 | color: @color; 5 | a&:hover, 6 | a&:focus { 7 | color: darken(@color, 10%); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/mixins/text-overflow.less: -------------------------------------------------------------------------------- 1 | // Text overflow 2 | // Requires inline-block or block for proper styling 3 | 4 | .text-overflow() { 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | white-space: nowrap; 8 | } 9 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/pager.less: -------------------------------------------------------------------------------- 1 | // 2 | // Pager pagination 3 | // -------------------------------------------------- 4 | 5 | 6 | .pager { 7 | padding-left: 0; 8 | margin: @line-height-computed 0; 9 | list-style: none; 10 | text-align: center; 11 | &:extend(.clearfix all); 12 | li { 13 | display: inline; 14 | > a, 15 | > span { 16 | display: inline-block; 17 | padding: 5px 14px; 18 | background-color: @pager-bg; 19 | border: 1px solid @pager-border; 20 | border-radius: @pager-border-radius; 21 | } 22 | 23 | > a:hover, 24 | > a:focus { 25 | text-decoration: none; 26 | background-color: @pager-hover-bg; 27 | } 28 | } 29 | 30 | .next { 31 | > a, 32 | > span { 33 | float: right; 34 | } 35 | } 36 | 37 | .previous { 38 | > a, 39 | > span { 40 | float: left; 41 | } 42 | } 43 | 44 | .disabled { 45 | > a, 46 | > a:hover, 47 | > a:focus, 48 | > span { 49 | color: @pager-disabled-color; 50 | background-color: @pager-bg; 51 | cursor: @cursor-disabled; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/responsive-embed.less: -------------------------------------------------------------------------------- 1 | // Embeds responsive 2 | // 3 | // Credit: Nicolas Gallagher and SUIT CSS. 4 | 5 | .embed-responsive { 6 | position: relative; 7 | display: block; 8 | height: 0; 9 | padding: 0; 10 | overflow: hidden; 11 | 12 | .embed-responsive-item, 13 | iframe, 14 | embed, 15 | object, 16 | video { 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | bottom: 0; 21 | height: 100%; 22 | width: 100%; 23 | border: 0; 24 | } 25 | } 26 | 27 | // Modifier class for 16:9 aspect ratio 28 | .embed-responsive-16by9 { 29 | padding-bottom: 56.25%; 30 | } 31 | 32 | // Modifier class for 4:3 aspect ratio 33 | .embed-responsive-4by3 { 34 | padding-bottom: 75%; 35 | } 36 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/thumbnails.less: -------------------------------------------------------------------------------- 1 | // 2 | // Thumbnails 3 | // -------------------------------------------------- 4 | 5 | 6 | // Mixin and adjust the regular image class 7 | .thumbnail { 8 | display: block; 9 | padding: @thumbnail-padding; 10 | margin-bottom: @line-height-computed; 11 | line-height: @line-height-base; 12 | background-color: @thumbnail-bg; 13 | border: 1px solid @thumbnail-border; 14 | border-radius: @thumbnail-border-radius; 15 | .transition(border .2s ease-in-out); 16 | 17 | > img, 18 | a > img { 19 | &:extend(.img-responsive); 20 | margin-left: auto; 21 | margin-right: auto; 22 | } 23 | 24 | // Add a hover state for linked versions only 25 | a&:hover, 26 | a&:focus, 27 | a&.active { 28 | border-color: @link-color; 29 | } 30 | 31 | // Image captions 32 | .caption { 33 | padding: @thumbnail-caption-padding; 34 | color: @thumbnail-caption-color; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/utilities.less: -------------------------------------------------------------------------------- 1 | // 2 | // Utility classes 3 | // -------------------------------------------------- 4 | 5 | 6 | // Floats 7 | // ------------------------- 8 | 9 | .clearfix { 10 | .clearfix(); 11 | } 12 | .center-block { 13 | .center-block(); 14 | } 15 | .pull-right { 16 | float: right !important; 17 | } 18 | .pull-left { 19 | float: left !important; 20 | } 21 | 22 | 23 | // Toggling content 24 | // ------------------------- 25 | 26 | // Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1 27 | .hide { 28 | display: none !important; 29 | } 30 | .show { 31 | display: block !important; 32 | } 33 | .invisible { 34 | visibility: hidden; 35 | } 36 | .text-hide { 37 | .text-hide(); 38 | } 39 | 40 | 41 | // Hide from screenreaders and browsers 42 | // 43 | // Credit: HTML5 Boilerplate 44 | 45 | .hidden { 46 | display: none; 47 | } 48 | 49 | 50 | // For Affix plugin 51 | // ------------------------- 52 | 53 | .affix { 54 | position: fixed; 55 | } 56 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/bootstrap/less/wells.less: -------------------------------------------------------------------------------- 1 | // 2 | // Wells 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base class 7 | .well { 8 | min-height: 20px; 9 | padding: 19px; 10 | margin-bottom: 20px; 11 | background-color: @well-bg; 12 | border: 1px solid @well-border; 13 | border-radius: @border-radius-base; 14 | .box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); 15 | blockquote { 16 | border-color: #ddd; 17 | border-color: rgba(0,0,0,.15); 18 | } 19 | } 20 | 21 | // Sizes 22 | .well-lg { 23 | padding: 24px; 24 | border-radius: @border-radius-large; 25 | } 26 | .well-sm { 27 | padding: 9px; 28 | border-radius: @border-radius-small; 29 | } 30 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/font-awesome/less/animated.less: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .@{fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/font-awesome/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .@{fa-css-prefix}-pull-left { float: left; } 11 | .@{fa-css-prefix}-pull-right { float: right; } 12 | 13 | .@{fa-css-prefix} { 14 | &.@{fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.@{fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .@{fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/font-awesome/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/font-awesome/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/font-awesome/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "animated.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | @import "screen-reader.less"; 19 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/font-awesome/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/font-awesome/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/font-awesome/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), 9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | font-display: swap; 16 | } 17 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/font-awesome/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/font-awesome/less/screen-reader.less: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { .sr-only(); } 5 | .sr-only-focusable { .sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/font-awesome/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/js/plugins/visibility.js: -------------------------------------------------------------------------------- 1 | /*! jquery-visibility v1.0.12 | MIT license | http://mths.be/visibility */ 2 | !function (e, i) { "function" == typeof define && define.amd ? define(["jquery"], function (t) { return i(e, t) }) : "object" == typeof exports ? module.exports = i(e, require("jquery")) : i(e, jQuery) }(this, function (e, i, t) { "use strict"; function o() { "hidden" !== n && (r.hidden = f.pageVisibility ? r[n] : t) } for (var n, u, r = e.document, s = ["webkit", "o", "ms", "moz", ""], f = i.support || {}, c = ("onfocusin" in r && "hasFocus" in r ? "focusin focusout" : "focus blur") ; (u = s.pop()) !== t;) if (n = (u ? u + "H" : "h") + "idden", f.pageVisibility = r[n] !== t, f.pageVisibility) { c = u + "visibilitychange"; break } o(), i(/blur$/.test(c) ? e : r).on(c, function (e) { var u = e.type, s = e.originalEvent; if (s) { var f = s.toElement; (!/^focus./.test(u) || f === t && s.fromElement === t && s.relatedTarget === t) && i(r).triggerHandler(n && r[n] || /^(?:blur|focusout)$/.test(u) ? "hide" : "show"), o() } }) }); -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/themes/_shared/animations.less: -------------------------------------------------------------------------------- 1 | .spin, .hover-spin:hover .fa { 2 | -webkit-animation: spin 2s infinite linear; 3 | -moz-animation: spin 2s infinite linear; 4 | -o-animation: spin 2s infinite linear; 5 | animation: spin 2s infinite linear; 6 | } 7 | @-moz-keyframes spin { 8 | 0% { -moz-transform: rotate(0deg); } 9 | 100% { -moz-transform: rotate(359deg); } 10 | } 11 | @-webkit-keyframes spin { 12 | 0% { -webkit-transform: rotate(0deg); } 13 | 100% { -webkit-transform: rotate(359deg); } 14 | } 15 | @-o-keyframes spin { 16 | 0% { -o-transform: rotate(0deg); } 17 | 100% { -o-transform: rotate(359deg); } 18 | } 19 | @keyframes spin { 20 | 0% { 21 | -webkit-transform: rotate(0deg); 22 | transform: rotate(0deg); 23 | } 24 | 100% { 25 | -webkit-transform: rotate(359deg); 26 | transform: rotate(359deg); 27 | } 28 | } 29 | 30 | .hover-pulsate:not(.fa-spin):hover .fa:not(.fa-spin), .hover-pulsate.fa:not(.fa-spin):not(.disabled):hover { 31 | -webkit-animation: pulsate 2s infinite linear; 32 | -moz-animation: pulsate 2s infinite linear; 33 | -o-animation: pulsate 2s infinite linear; 34 | animation: pulsate 2s infinite linear; 35 | } 36 | 37 | @-webkit-keyframes pulsate { 38 | 0% { 39 | -webkit-transform: scale(1.0, 1.0); 40 | } 41 | 42 | 50% { 43 | -webkit-transform: scale(0.8, 0.8); 44 | } 45 | 46 | 100% { 47 | -webkit-transform: scale(1.0, 1.0); 48 | } 49 | } -------------------------------------------------------------------------------- /src/Opserver.Web/ContentSource/themes/light.less: -------------------------------------------------------------------------------- 1 | // Based on Bootswatch: Yeti - https://bootswatch.com/yeti/ 2 | @import "_shared.less"; 3 | 4 | @gray-light: rgba(78,93,108,0.2); 5 | @label-muted-color: #999999; 6 | @value-block-text-color: black; 7 | //@value-block-background-color: rgba(0,0,0,0.018); 8 | @value-block-border-color: #f9f9f9; 9 | @value-block-progress-background-color: #666; 10 | 11 | @panel-inverse-heading-bg: white; 12 | 13 | @chart-area-blue: #008cba; 14 | 15 | @axis-color: #444; 16 | -------------------------------------------------------------------------------- /src/Opserver.Web/Controllers/AdminController.cs: -------------------------------------------------------------------------------- 1 | using StackExchange.Exceptional; 2 | using Opserver.Helpers; 3 | using Opserver.Models; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Options; 7 | 8 | namespace Opserver.Controllers 9 | { 10 | [OnlyAllow(Roles.GlobalAdmin)] 11 | public class AdminController : StatusController 12 | { 13 | public AdminController(IOptions _settings) : base(_settings) {} 14 | 15 | [Route("admin/security/purge-cache")] 16 | public ActionResult Dashboard() 17 | { 18 | Current.Security.PurgeCache(); 19 | return TextPlain("Cache Purged"); 20 | } 21 | 22 | /// 23 | /// Access our error log. 24 | /// 25 | [Route("admin/errors/{resource?}/{subResource?}"), AlsoAllow(Roles.LocalRequest)] 26 | public Task InvokeErrorHandler() => ExceptionalMiddleware.HandleRequestAsync(HttpContext); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Opserver.Web/Controllers/CloudflareController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.Options; 4 | using Opserver.Data.Cloudflare; 5 | using Opserver.Helpers; 6 | using Opserver.Views.Cloudflare; 7 | 8 | namespace Opserver.Controllers 9 | { 10 | [OnlyAllow(CloudflareRoles.Viewer)] 11 | public class CloudflareController : StatusController 12 | { 13 | public CloudflareController(CloudflareModule module, IOptions settings) : base(module, settings) { } 14 | 15 | [DefaultRoute("cloudflare")] 16 | public ActionResult Dashboard() => RedirectToAction(nameof(DNS)); 17 | 18 | [Route("cloudflare/dns")] 19 | public async Task DNS() 20 | { 21 | await Module.API.PollAsync(); 22 | var vd = new DNSModel 23 | { 24 | View = DashboardModel.Views.DNS, 25 | Zones = Module.API.Zones.SafeData(true), 26 | DNSRecords = Module.API.DNSRecords.Data, 27 | DataCenters = Module.AllDatacenters 28 | }; 29 | return View(vd); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Opserver.Web/Controllers/DashboardController.Admin.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Opserver.Helpers; 3 | using Opserver.Data.Dashboard; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace Opserver.Controllers 7 | { 8 | public partial class DashboardController 9 | { 10 | [Route("dashboard/node/service/action"), HttpPost, OnlyAllow(DashboardRoles.Admin)] 11 | public async Task ControlService(string node, string name, NodeService.Action serviceAction) 12 | { 13 | var n = Module.GetNodeByName(node); 14 | var s = n.GetService(name); 15 | var result = Json(await s.Update(serviceAction)); 16 | await n.DataProvider.PollAsync(true); 17 | return result; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Opserver.Web/Controllers/DashboardController.Polling.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace Opserver.Controllers 6 | { 7 | public partial class DashboardController 8 | { 9 | [ResponseCache(Duration = 1, VaryByQueryKeys = new string[] { "id", "node" }, Location = ResponseCacheLocation.Client)] 10 | [Route("dashboard/node/poll/cpu")] 11 | public async Task PollCPU(string id = null, string node = null) 12 | { 13 | var n = id.HasValue() ? Module.GetNodeById(id) : Module.GetNodeByName(node); 14 | if (n == null) 15 | return JsonNotFound(); 16 | 17 | var data = await n.GetCPUUtilization(); 18 | if (data?.Data == null) 19 | return JsonNotFound(); 20 | 21 | var total = data.Data.Find(c => c.Name == "Total"); 22 | 23 | return Json(new 24 | { 25 | duration = data.Duration.TotalMilliseconds, 26 | cores = data.Data.Where(c => c != total).Select(c => new 27 | { 28 | name = c.Name, 29 | utilization = c.Utilization 30 | }), 31 | total = total?.Utilization ?? 0 32 | }); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Opserver.Web/Controllers/DefaultRoute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace Opserver.Controllers 6 | { 7 | [AttributeUsage(AttributeTargets.Method)] 8 | public sealed class DefaultRoute : RouteAttribute 9 | { 10 | private static Dictionary AllRoutes => new Dictionary(); 11 | 12 | public DefaultRoute(string template) : base(template) { } 13 | 14 | public static DefaultRoute GetFor(Type t) => AllRoutes.TryGetValue(t, out var route) ? route : null; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Opserver.Web/Controllers/GraphController.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Controllers 2 | { 3 | public partial class GraphController : StatusController 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Opserver.Web/Controllers/HAProxyController.Admin.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Opserver.Data.HAProxy; 5 | using Opserver.Helpers; 6 | 7 | namespace Opserver.Controllers 8 | { 9 | public partial class HAProxyController 10 | { 11 | [Route("haproxy/admin/action"), HttpPost, OnlyAllow(HAProxyRoles.Admin)] 12 | public async Task HAProxyAdminProxy(string group, string proxy, string server, Action act) 13 | { 14 | // Entire server 15 | if (proxy.IsNullOrEmpty() && group.IsNullOrEmpty() && server.HasValue()) 16 | return Json(await Module.Admin.PerformServerActionAsync(server, act)); 17 | // Entire group 18 | if (proxy.IsNullOrEmpty() && server.IsNullOrEmpty() && group.HasValue()) 19 | return Json(await Module.Admin.PerformGroupActionAsync(group, act)); 20 | 21 | var haGroup = Module.GetGroup(group); 22 | var proxies = (haGroup != null ? haGroup.GetProxies() : Module.GetAllProxies()).Where(pr => pr.Name == proxy); 23 | 24 | return Json(await Module.Admin.PerformProxyActionAsync(proxies, server, act)); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Opserver.Web/Controllers/HAProxyController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.Options; 4 | using Opserver.Data.HAProxy; 5 | using Opserver.Helpers; 6 | using Opserver.Views.HAProxy; 7 | 8 | namespace Opserver.Controllers 9 | { 10 | [OnlyAllow(HAProxyRoles.Viewer)] 11 | public partial class HAProxyController : StatusController 12 | { 13 | public HAProxyController(HAProxyModule module, IOptions settings) : base(module, settings) { } 14 | 15 | [DefaultRoute("haproxy")] 16 | public ActionResult Dashboard(string group, string node, string watch = null, bool norefresh = false) 17 | { 18 | var haGroup = Module.GetGroup(group ?? node); 19 | var proxies = haGroup != null ? haGroup.GetProxies() : Module.GetAllProxies(); 20 | proxies.RemoveAll(p => !p.HasServers); 21 | 22 | var vd = new HAProxyModel 23 | { 24 | SelectedGroup = haGroup, 25 | Groups = haGroup != null ? new List { haGroup } : Module.Groups, 26 | Proxies = proxies, 27 | View = HAProxyModel.Views.Dashboard, 28 | Refresh = !norefresh, 29 | WatchProxy = watch 30 | }; 31 | return View("HAProxy.Dashboard", vd); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Opserver.Web/Controllers/HubController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Options; 3 | using Opserver.Data.Dashboard; 4 | using Opserver.Helpers; 5 | using Opserver.Models; 6 | using Opserver.Views.Hub; 7 | 8 | namespace Opserver.Controllers 9 | { 10 | [OnlyAllow(Roles.Authenticated)] 11 | public class HubController : StatusController 12 | { 13 | public HubController(DashboardModule module, IOptions settings) : base(module, settings) { } 14 | 15 | [Route("hub"), Route("headsup"), AlsoAllow(Roles.InternalRequest)] 16 | public ActionResult Index() 17 | { 18 | var vd = new HubModel(); 19 | return View(vd); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Opserver.Web/Controllers/MiscController.cs: -------------------------------------------------------------------------------- 1 | using Opserver.Views.Shared; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System.Net; 4 | using Microsoft.Extensions.Options; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Opserver.Helpers; 7 | using Opserver.Models; 8 | 9 | namespace Opserver.Controllers 10 | { 11 | [AlsoAllow(Roles.Anonymous)] 12 | public class MiscController : StatusController 13 | { 14 | public MiscController(IOptions _settings) : base(_settings) { } 15 | 16 | [Route("no-config")] 17 | public ViewResult NoConfig() => View("NoConfiguration"); 18 | 19 | [Route("404")] 20 | public ViewResult PageNotFound(string title = null, string message = null) 21 | { 22 | Response.StatusCode = (int)HttpStatusCode.NotFound; 23 | 24 | var vd = new PageNotFoundModel 25 | { 26 | Title = title, 27 | Message = message 28 | }; 29 | return View("PageNotFound", vd); 30 | } 31 | 32 | [AllowAnonymous] 33 | [Route("denied")] 34 | public ActionResult AccessDenied() 35 | { 36 | Response.StatusCode = (int)HttpStatusCode.Forbidden; 37 | return View("~/Views/Shared/AccessDenied.cshtml"); 38 | } 39 | 40 | [Route("error")] 41 | public ActionResult ErrorPage() 42 | { 43 | Response.StatusCode = (int)HttpStatusCode.InternalServerError; 44 | return View("Error"); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Opserver.Web/Controllers/PollController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Options; 6 | using Opserver.Data; 7 | using Opserver.Helpers; 8 | using Opserver.Models; 9 | 10 | namespace Opserver.Controllers 11 | { 12 | [OnlyAllow(Roles.Authenticated)] 13 | public class PollController : StatusController 14 | { 15 | private PollingService Poller { get; } 16 | 17 | public PollController(IOptions _settings, PollingService poller) : base(_settings) => Poller = poller; 18 | 19 | [Route("poll")] 20 | public async Task PollNodes(string type, string[] key, Guid? guid = null) 21 | { 22 | if (type.IsNullOrEmpty()) 23 | return JsonError("type is missing"); 24 | if (!(key?.Any() ?? false)) 25 | return JsonError("key is missing"); 26 | try 27 | { 28 | var polls = key.Select(k => Poller.PollAsync(type, k, guid)); 29 | var results = await Task.WhenAll(polls); 30 | return Json(results.Aggregate(true, (current, r) => current && r)); 31 | } 32 | catch (Exception e) 33 | { 34 | return JsonError("Error polling node: " + e.Message); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Opserver.Web/Helpers/DataKeys.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Helpers 2 | { 3 | /// 4 | /// Contains keys for common ViewData collection values 5 | /// 6 | public static class ViewDataKeys 7 | { 8 | public const string PageTitle = nameof(PageTitle); 9 | public const string TopBoxOptions = nameof(TopBoxOptions); 10 | } 11 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Helpers/MiniProfilerCacheStorage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Caching.Memory; 2 | using Microsoft.Extensions.Options; 3 | using Opserver.Data; 4 | using StackExchange.Profiling; 5 | using StackExchange.Profiling.Storage; 6 | using System; 7 | using System.Threading.Tasks; 8 | 9 | namespace Opserver.Helpers 10 | { 11 | public class MiniProfilerCacheStorage : MemoryCacheStorage, IAsyncStorage 12 | { 13 | private readonly PollingService _poller; 14 | public MiniProfilerCacheStorage(IMemoryCache cache, PollingService poller, TimeSpan cacheDuration) : base(cache, cacheDuration) 15 | { 16 | _poller = poller; 17 | } 18 | 19 | MiniProfiler IAsyncStorage.Load(Guid id) => Load(id) ?? _poller.GetCache(id)?.Profiler; 20 | Task IAsyncStorage.LoadAsync(Guid id) => LoadAsync(id) ?? Task.FromResult(_poller.GetCache(id)?.Profiler); 21 | } 22 | 23 | internal class MiniProfilerCacheStorageDefaults : IConfigureOptions 24 | { 25 | private readonly IMemoryCache _cache; 26 | private readonly PollingService _poller; 27 | public MiniProfilerCacheStorageDefaults(IMemoryCache cache, PollingService poller) 28 | { 29 | _cache = cache; 30 | _poller = poller; 31 | } 32 | 33 | public void Configure(MiniProfilerOptions options) 34 | { 35 | if (options.Storage == null) 36 | { 37 | options.Storage = new MiniProfilerCacheStorage(_cache, _poller, TimeSpan.FromMinutes(10)); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Opserver.Web/Helpers/TestItem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Opserver.Data; 3 | 4 | namespace Opserver.Helpers 5 | { 6 | public sealed class TestItem : IMonitorStatus 7 | { 8 | public static readonly TestItem Good = new TestItem(MonitorStatus.Good); 9 | public static readonly TestItem Warning = new TestItem(MonitorStatus.Warning); 10 | public static readonly TestItem Maintenance = new TestItem(MonitorStatus.Maintenance); 11 | public static readonly TestItem Critical = new TestItem(MonitorStatus.Critical); 12 | public static readonly TestItem Unknown = new TestItem(MonitorStatus.Unknown); 13 | 14 | public MonitorStatus MonitorStatus { get; } 15 | public string MonitorStatusReason { get; } 16 | 17 | private TestItem(MonitorStatus status) 18 | { 19 | MonitorStatus = status; 20 | MonitorStatusReason = status.ToString(); 21 | } 22 | 23 | public static IEnumerable Batch(int good = 0, int warning = 0, int maintenance = 0, int critical = 0, int unknown = 0) 24 | { 25 | for (var i = 0; i < good; i++) 26 | yield return Good; 27 | for (var i = 0; i < warning; i++) 28 | yield return Warning; 29 | for (var i = 0; i < maintenance; i++) 30 | yield return Maintenance; 31 | for (var i = 0; i < critical; i++) 32 | yield return Critical; 33 | for (var i = 0; i < unknown; i++) 34 | yield return Unknown; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Opserver.Web/Helpers/Theme.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace Opserver.Helpers 6 | { 7 | public static class Theme 8 | { 9 | public static List Options { get; } = new List { "light", "dark" }; 10 | 11 | public const string Default = "light"; 12 | private const string CookieName = "Op-Theme"; 13 | 14 | // Cookies need an expiration date! A sliding time period seems downright silly so... 15 | // I chose at random from https://en.wikipedia.org/wiki/List_of_dates_predicted_for_apocalyptic_events 16 | // "Members predict that the world will end in 2026, when an asteroid would collide with Earth..." 17 | private static readonly DateTime CookieExpirationDate = new DateTime(2026, 1, 1); 18 | 19 | public static string Get(HttpRequest request) => request.Cookies[CookieName] ?? Default; 20 | 21 | public static void Set(string theme, HttpResponse response) 22 | { 23 | if (Options.Contains(theme)) 24 | { 25 | response.Cookies.Append(CookieName, theme, new CookieOptions() { Expires = CookieExpirationDate }); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Opserver.Web/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:56057/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "Opserver.Web": { 12 | "commandName": "Project", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | }, 17 | "applicationUrl": "http://localhost:56058/" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Security/ActiveDirectorySecuritySettings.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Security 2 | { 3 | /// 4 | /// Security settings for configuring the . 5 | /// 6 | public class ActiveDirectorySecuritySettings : SecuritySettings 7 | { 8 | /// 9 | /// Gets or sets the server to bind to in order to authenticate users. 10 | /// 11 | public string Server { get; set; } 12 | /// 13 | /// Gets or sets the username used to bind to AD in order to authenticate users. 14 | /// 15 | public string AuthUser { get; set; } 16 | /// 17 | /// Gets or sets the password used to bind to AD in order to authenticate users. 18 | /// 19 | public string AuthPassword { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Opserver.Web/Security/EveryonesAnAdminProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Security.Claims; 3 | using Opserver.Models; 4 | 5 | namespace Opserver.Security 6 | { 7 | /// 8 | /// Does this REALLY need an explanation? 9 | /// 10 | public class EveryonesAnAdminProvider : SecurityProvider 11 | { 12 | public override string ProviderName => "Everyone's an Admin!"; 13 | public override SecurityProviderFlowType FlowType => SecurityProviderFlowType.Username; 14 | public EveryonesAnAdminProvider(SecuritySettings settings) : base(settings) { } 15 | 16 | internal override bool InAdminGroups(User user, StatusModule module) => true; 17 | protected override bool InGroupsCore(User user, string[] groupNames) => true; 18 | 19 | protected override bool TryValidateToken(UserNamePasswordToken token, out ClaimsPrincipal claimsPrincipal) 20 | { 21 | claimsPrincipal = CreateNamedPrincipal(token.UserName); 22 | return true; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Opserver.Web/Security/EveryonesReadOnlyProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using Opserver.Models; 3 | 4 | namespace Opserver.Security 5 | { 6 | /// 7 | /// Does this REALLY need an explanation? 8 | /// 9 | public class EveryonesReadOnlyProvider : SecurityProvider 10 | { 11 | public override string ProviderName => "Everyone's Read-only"; 12 | public override SecurityProviderFlowType FlowType => SecurityProviderFlowType.Username; 13 | public EveryonesReadOnlyProvider(SecuritySettings settings) : base(settings) { } 14 | 15 | internal override bool InAdminGroups(User user, StatusModule module) => false; 16 | internal override bool InReadGroups(User user, StatusModule module) => true; 17 | protected override bool TryValidateToken(UserNamePasswordToken token, out ClaimsPrincipal claimsPrincipal) 18 | { 19 | claimsPrincipal = CreateNamedPrincipal(token.UserName); 20 | return true; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Opserver.Web/Security/ISecurityProviderToken.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Security 2 | { 3 | /// 4 | /// Marker interface used to indicate a class is a token consumed by a . 5 | /// 6 | public interface ISecurityProviderToken 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Opserver.Web/Security/OIDCToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Claims; 4 | 5 | namespace Opserver.Security 6 | { 7 | /// 8 | /// that wraps the claims retrieved from an OpenId Connect login flow. 9 | /// 10 | public class OIDCToken : ISecurityProviderToken 11 | { 12 | public OIDCToken(IEnumerable claims) 13 | { 14 | Claims = claims ?? throw new ArgumentNullException(nameof(claims)); 15 | } 16 | 17 | /// 18 | /// Gets the claims retrieved as a result of an OpenId Connect login flow. 19 | /// 20 | public IEnumerable Claims { get; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Opserver.Web/Security/SecurityProviderFlowType.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Security 2 | { 3 | /// 4 | /// Types of login flows supported by a . 5 | /// 6 | public enum SecurityProviderFlowType 7 | { 8 | None, 9 | Username, 10 | UsernamePassword, 11 | OIDC, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Opserver.Web/Security/UnconfiguredProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | 3 | namespace Opserver.Security 4 | { 5 | /// 6 | /// Used when no valid configuration is present, as a default to slam the door 7 | /// If people want read-all access...it should be explicit. Default is no access. 8 | /// 9 | public class UnconfiguredProvider : SecurityProvider 10 | { 11 | public override string ProviderName => "Unconfigured"; 12 | public override SecurityProviderFlowType FlowType => SecurityProviderFlowType.None; 13 | public override bool IsConfigured => false; 14 | 15 | public UnconfiguredProvider(SecuritySettings settings) : base(settings) { } 16 | 17 | public override bool TryValidateToken(ISecurityProviderToken token, out ClaimsPrincipal claimsPrincipal) 18 | { 19 | claimsPrincipal = CreateAnonymousPrincipal(); 20 | return false; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Opserver.Web/Security/UserNamePasswordToken.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Security 2 | { 3 | public class UserNamePasswordToken : ISecurityProviderToken 4 | { 5 | public UserNamePasswordToken(string userName, string password) 6 | { 7 | UserName = userName; 8 | Password = password; 9 | } 10 | 11 | public string UserName { get; } 12 | public string Password { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Auth/Login.Model.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Views.Login 2 | { 3 | public class LoginModel 4 | { 5 | public string Message { get; set; } 6 | public string ErrorMessage { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/CloudFlare/DNS.Model.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Opserver.Data; 3 | using Opserver.Data.Cloudflare; 4 | 5 | namespace Opserver.Views.Cloudflare 6 | { 7 | public class DNSModel : DashboardModel 8 | { 9 | public override Views View => Views.DNS; 10 | 11 | public Dictionary> DataCenters { get; set; } 12 | public List Zones { get; set; } 13 | public List DNSRecords { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/CloudFlare/Dashboard.Model.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Views.Cloudflare 2 | { 3 | public class DashboardModel 4 | { 5 | public virtual Views View { get; set; } 6 | 7 | public enum Views 8 | { 9 | Overview = 0, 10 | DNS = 1, 11 | Analytics = 2 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/CloudFlare/Dashboard.cshtml: -------------------------------------------------------------------------------- 1 | @model DashboardModel 2 | @{ 3 | Layout = "~/Views/Shared/Master.cshtml"; 4 | this.SetPageTitle("Cloudflare Dashboard"); 5 | } 6 | @section head { 7 | 12 | } 13 | @functions 14 | { 15 | string TabLink(DashboardModel.Views view, string action, string name) 16 | { 17 |
  • @name
  • 18 | return null; 19 | } 20 | } 21 | 24 | @RenderBody() 25 | -------------------------------------------------------------------------------- /src/Opserver.Web/Views/CloudFlare/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Opserver.Data.Cloudflare 2 | @using Opserver.Views.Cloudflare 3 | @inject CloudflareModule Module 4 | -------------------------------------------------------------------------------- /src/Opserver.Web/Views/CloudFlare/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = this.IsAjaxRequest() ? null : "Dashboard.cshtml"; 3 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Dashboard/CurrentStatusTypes.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Opserver.Views.Dashboard 4 | { 5 | public enum CurrentStatusTypes 6 | { 7 | [Description("None")] 8 | None = 0, 9 | [Description("Stats")] 10 | Stats = 1, 11 | [Description("Interfaces")] 12 | Interfaces = 2, 13 | [Description("VM Info")] 14 | VMHost = 3, 15 | [Description("Elastic")] 16 | Elastic = 4, 17 | [Description("HAProxy")] 18 | HAProxy = 5, 19 | [Description("SQL Instance")] 20 | SQLInstance = 6, 21 | [Description("Active SQL")] 22 | SQLActive = 7, 23 | [Description("Top SQL")] 24 | SQLTop = 8, 25 | [Description("Redis Info")] 26 | Redis = 9 27 | } 28 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Dashboard/Dashboard.Model.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Opserver.Data.Dashboard; 3 | 4 | namespace Opserver.Views.Dashboard 5 | { 6 | public class DashboardModel 7 | { 8 | public string Filter { get; set; } 9 | public List ErrorMessages { get; set; } 10 | public List Nodes { get; set; } 11 | public bool IsStartingUp { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Dashboard/Dashboard.cshtml: -------------------------------------------------------------------------------- 1 | @model DashboardModel 2 | @{ 3 | Layout = "~/Views/Shared/Master.cshtml"; 4 | this.SetPageTitle("Dashboard"); 5 | 6 | this.SetTopSearch("All Nodes", 7 | Model.Filter, 8 | Url.Action(nameof(DashboardController.Dashboard))); 9 | } 10 | @section head { 11 | } 16 | @section preload { 17 | @RenderSection("preload", false) 18 | } 19 |
    20 | @RenderBody() 21 |
    -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Dashboard/Node.Graph.Model.cs: -------------------------------------------------------------------------------- 1 | using Opserver.Data.Dashboard; 2 | 3 | namespace Opserver.Views.Dashboard 4 | { 5 | public class NodeGraphModel 6 | { 7 | public Node Node { get; set; } 8 | public string Title { get; set; } 9 | public string Type { get; set; } 10 | public Interface Interface { get; set; } 11 | public Volume Volume { get; set; } 12 | 13 | public bool IsLive => Type == KnownTypes.Live; 14 | 15 | public object CpuData { get; set; } 16 | public object MemoryData { get; set; } 17 | public object NetworkData { get; set; } 18 | public object VolumeData { get; set; } 19 | public object VolumePerformanceData { get; set; } 20 | 21 | public static class KnownTypes 22 | { 23 | public const string CPU = "cpu"; 24 | public const string Memory = "memory"; 25 | public const string Network = "network"; 26 | public const string Volume = "volume"; 27 | public const string VolumePerformance = "volumePerformance"; 28 | public const string Live = "live"; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Dashboard/Node.Model.cs: -------------------------------------------------------------------------------- 1 | using Opserver.Data.Dashboard; 2 | 3 | namespace Opserver.Views.Dashboard 4 | { 5 | public class NodeModel 6 | { 7 | public Node CurrentNode { get; set; } 8 | public CurrentStatusTypes? CurrentStatusType { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Dashboard/Node.Stats.cshtml: -------------------------------------------------------------------------------- 1 | @model Node 2 |
    3 |
    4 |
    5 |
    6 | CPU Utilization 7 |
    8 |
    9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    15 |
    Memory Utilization (@(Model.TotalMemory.GetValueOrDefault(0).ToSize()))
    16 |
    17 |
    18 |
    19 |
    20 |
    21 |
    22 | 28 | @if (Model.Interfaces.Any()) 29 | { 30 | 31 | } 32 | 33 | @if (Model.Services?.Any() == true) 34 | { 35 | 36 | } 37 | 38 | @if (Module.Settings.ShowVolumePerformance && Model.Volumes.Any()) 39 | { 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Dashboard/Node.VMHost.cshtml: -------------------------------------------------------------------------------- 1 | @model Node 2 | -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Dashboard/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Opserver.Data.Dashboard 2 | @using Opserver.Views.Dashboard 3 | @inject DashboardModule Module -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Elastic/Cluster.Indexes.cshtml: -------------------------------------------------------------------------------- 1 | @model DashboardModel 2 | 5 | -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Elastic/Cluster.Selector.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | var clusters = Module.Clusters; 3 | } 4 |
    5 | 6 |
    7 |
    8 |
    9 |
    Cluster
    10 |
    11 | @foreach (var c in clusters) 12 | { 13 | var nodeInfos = c.Nodes.SafeData(true).Nodes; 14 | if (nodeInfos == null) { continue; } 15 | var info = nodeInfos.FirstOrDefault(); 16 | 17 | @c.IconSpan() @c.Name 18 | @nodeInfos.Count.Pluralize("node") - @(info?.Version ?? "") 19 | 20 | } 21 |
    22 |
    23 |
    24 |
    25 |
    -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Elastic/Cluster.Shards.cshtml: -------------------------------------------------------------------------------- 1 | @model DashboardModel 2 | @{ 3 | var c = Model.CurrentCluster; 4 | var i = Model.CurrentIndexName; 5 | var shards = Model.DisplayShards.Where(s => i == null || s.Index == i).ToList(); 6 | } 7 | 10 | -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Elastic/Indexes.cshtml: -------------------------------------------------------------------------------- 1 | @model DashboardModel 2 | @{ 3 | var c = Model.CurrentCluster; 4 | if (c == null) { return; } 5 | var indexes = c.HealthStatus.Data.Indexes.Values.ToList(); 6 | } 7 |
    8 | 13 | 14 |
    -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Elastic/Node.Selector.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | var clusters = Module.Clusters; 3 | } 4 |
    5 | 6 |
    7 | @foreach (var c in clusters) 8 | { 9 | var nodes = c.Nodes.SafeData(true).Nodes; 10 | if (nodes == null || !nodes.Any()) { continue; } 11 |
    12 |
    13 |
    @c.Name
    14 |
    15 | @foreach (var n in nodes.Where(n => !n.IsClient)) 16 | { 17 | var info = nodes.FirstOrDefault(ni => n.Name == ni.Name); 18 | 19 | @n.IconSpan() @n.ShortName 20 | @(info?.Version ?? "") 21 | 22 | } 23 |
    24 |
    25 |
    26 | } 27 |
    28 |
    -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Elastic/Node.Settings.cshtml: -------------------------------------------------------------------------------- 1 | @using Jil 2 | @model DashboardModel 3 | @{ 4 | var settings = Model.CurrentNode?.Settings ?? new Dictionary(); 5 | } 6 | 9 | @if (settings.Any()) 10 | { 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | @foreach (var s in settings.OrderBy(s => s.Key)) 20 | { 21 | 22 | 23 | 24 | 25 | } 26 | 27 |
    SettingValue
    @s.Key@JSON.Serialize(s.Value, Options.ISO8601PrettyPrint)
    28 | } 29 | else 30 | { 31 |
    No settings found
    32 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Elastic/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Opserver.Data.Elastic 2 | @using Opserver.Views.Elastic 3 | @inject ElasticModule Module -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Elastic/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = this.IsAjaxRequest() ? null : "Dashboard.cshtml"; 3 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Exceptions/Exceptions.Jira.cshtml: -------------------------------------------------------------------------------- 1 | @model List 2 | @if (Model != null && Model.Count > 0) 3 | { 4 |

    5 | Jira Actions 6 | @foreach (var action in Model) 7 | { 8 | 9 | ( @action.Caption ) 10 | 11 | } 12 |

    13 | 14 | 17 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Exceptions/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Opserver.Data.Exceptions 2 | @using Opserver.Views.Exceptions 3 | @inject ExceptionsModule Module -------------------------------------------------------------------------------- /src/Opserver.Web/Views/HAProxy/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Opserver.Data.HAProxy 2 | @using Opserver.Views.HAProxy 3 | @inject HAProxyModule Module -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Home/About.Model.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Views.Home 2 | { 3 | public class AboutModel 4 | { 5 | public bool AutoRefresh { get; set; } 6 | public string Filter { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Hub/HubModel.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Views.Hub 2 | { 3 | public class HubModel 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Hub/Index.Dashboard.cshtml: -------------------------------------------------------------------------------- 1 | @using Opserver.Data.Dashboard 2 | @inject DashboardModule Dashboard 3 | @{ 4 | var nodes = Dashboard.AllNodes; 5 | var groups = nodes.GroupBy(n => n.Category).OrderBy(g => g.Key.Index).ToList(); 6 | } 7 |
    8 |
    @nodes.GetWorstStatus().IconSpan() @nodes.Count.Pluralize("Node")
    9 |
    10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | @foreach (var g in groups) 18 | { 19 | 20 | 21 | 22 | 23 | 24 | } 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
    GroupNodesNetwork
    @g.Key.Name@Health.Description(g)@g.Sum(n => n.TotalPrimaryNetworkBitsPerSecond < 0 ? 0 : n.TotalPrimaryNetworkBitsPerSecond).ToSpeed()
    Total@Health.Description(nodes)@nodes.Sum(n => n.TotalPrimaryNetworkBitsPerSecond < 0 ? 0 : n.TotalPrimaryNetworkBitsPerSecond).ToSpeed()
    34 |
    35 |
    -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Hub/Index.Issues.cshtml: -------------------------------------------------------------------------------- 1 | @inject PollingService Poller 2 | @{ 3 | var issues = Poller.GetIssues().OrderByWorst().ToList(); 4 | } 5 |
    6 |
    @((issues.Count > 0 ? MonitorStatus.Warning : MonitorStatus.Good).IconSpan()) @issues.Count.Pluralize("Issue")
    7 |
    8 | @foreach (var i in issues) 9 | { 10 |
    11 |
    12 | @i.MonitorStatus.IconSpan() @i.Title (@(i.Type ?? "Unknown Type")) 13 | @i.Date.ToRelativeTimeSpan() 14 |
    15 |
    @i.Description
    16 |

    17 |
    18 | } 19 |
    20 |
    -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Hub/SQL.AvailabilityGroup.cshtml: -------------------------------------------------------------------------------- 1 | @model Opserver.Data.SQL.SQLNode.AGInfo 2 |
    3 | 4 |
    -------------------------------------------------------------------------------- /src/Opserver.Web/Views/PagerDuty/PagerDuty.EscFull.cshtml: -------------------------------------------------------------------------------- 1 | @model List 2 | @{ 3 | this.SetPageTitle("PagerDuty Full Escalation"); 4 | } 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | @foreach (var a in Model.OrderBy(u => u.EscalationLevel)) 20 | { 21 | 22 | } 23 | 24 |
    NameLevelPolicyEmailPhone
    25 | 26 | -------------------------------------------------------------------------------- /src/Opserver.Web/Views/PagerDuty/PagerDuty.Incident.Model.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Opserver.Data.PagerDuty; 3 | 4 | namespace Opserver.Views.PagerDuty 5 | { 6 | public class PagerDutyIncidentModel 7 | { 8 | public Incident Incident { get; set; } 9 | public List Logs { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/PagerDuty/PagerDuty.Model.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Opserver.Data.PagerDuty; 3 | 4 | namespace Opserver.Views.PagerDuty 5 | { 6 | public class PagerDutyModel 7 | { 8 | public List Schedule { get; set; } 9 | public int CachedDays { get; set; } 10 | 11 | public List AllIncidents { get; set; } 12 | public PagerDutyPerson CurrentPagerDutyPerson { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/PagerDuty/PagerDuty.OnCallRow.cshtml: -------------------------------------------------------------------------------- 1 | @model OnCall 2 | 3 | @Model.AssignedUser.FullName 4 | 5 | @if (Model.IsPrimary) 6 | { 7 | @Model.EscalationLevelDescription@Model.SharedLevelDescription 8 | } 9 | else 10 | { 11 | @Model.EscalationLevelDescription@Model.SharedLevelDescription 12 | } 13 | @if (Model.IsOverride) 14 | { 15 | (Override) 16 | } 17 | 18 | @Model.PolicyTitle 19 | @Model.AssignedUser.Email 20 | @Module.API.AllUsers.Data.FirstOrDefault(u => u.Id == @Model.AssignedUser.Id).Phone 21 | -------------------------------------------------------------------------------- /src/Opserver.Web/Views/PagerDuty/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Opserver.Data.PagerDuty 2 | @using Opserver.Views.PagerDuty 3 | @inject PagerDutyModule Module -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Redis/Dashboard.cshtml: -------------------------------------------------------------------------------- 1 | @model DashboardModel 2 | @{ 3 | Layout = "~/Views/Shared/Master.cshtml"; 4 | this.SetPageTitle("Redis Dashboard"); 5 | this.SetTopNodes(Module.Instances, "Redis Servers", Model.CurrentInstance); 6 | } 7 | @functions 8 | { 9 | string TabLink(RedisViews view, string action, string name, bool includeParams = true) 10 | { 11 |
  • 12 | @name 13 |
  • 14 | return null; 15 | } 16 | } 17 | @section head { 18 | 24 | } 25 | 29 |
    30 | @if (Model.View == RedisViews.Instance && Model.CurrentInstance == null) 31 | { 32 | 33 | } 34 | @RenderBody() 35 |
    -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Redis/Instance.Config.cshtml: -------------------------------------------------------------------------------- 1 | @model RedisInstance 2 | @{ 3 | var config = Model.Config.Data; 4 | } 5 | 8 | @if (config?.Any() ?? false) 9 | { 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | @foreach (var entry in config.OrderBy(e => e.Key)) 19 | { 20 | 21 | 22 | 23 | 24 | } 25 | 26 |
    Config ParamValue
    @entry.Key@entry.Value
    27 | } 28 | else 29 | { 30 |
    No configuration available
    31 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Redis/Instance.Selector.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | var instances = Module.Instances; 3 | var groups = instances.GroupBy(i => new {i.Name, i.Port}) 4 | .OrderBy(g => g.Key.Port) 5 | .ThenBy(g => g.Key.Name); 6 | } 7 | 8 |
    9 | @foreach (var g in groups) 10 | { 11 |
    12 |
    13 |
    @g.Key.Name - @g.Key.Port.ToString()
    14 |
    15 | @foreach (var i in g) 16 | { 17 | var info = i.Info.SafeData(true); 18 | 19 | @i.IconSpan() @i.HostAndPort 20 | @(info.Server.Version.Major > 0 ? info.Server.Version.ToString() : null) 21 | 22 | } 23 |
    24 |
    25 |
    26 | } 27 |
    -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Redis/Instance.SlowLog.cshtml: -------------------------------------------------------------------------------- 1 | @model RedisInstance 2 | @{ 3 | var log = Model.SlowLog.SafeData(true); 4 | } 5 | 8 | @if (log.Any()) 9 | { 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | @foreach (var entry in log) 21 | { 22 | 23 | 24 | 25 | 26 | 27 | 28 | } 29 | 30 |
    CommandDurationWhenRef
    @string.Join(" ", entry.Arguments)@entry.Duration.ToTimeStringMini(3)@entry.Time.ToRelativeTimeSpanMini()docs
    31 | } 32 | else 33 | { 34 |
    35 | @if (!Model.IsSlowLogEnabled) 36 | { 37 | @:Slow log is not enabled 38 | } 39 | else 40 | { 41 | @:No slow commands in the log 42 | } 43 |
    44 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Redis/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Opserver.Data.Redis 2 | @using Opserver.Views.Redis 3 | @inject RedisModule Module -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Redis/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = this.IsAjaxRequest() ? null : "Dashboard.cshtml"; 3 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/SQL/Dashboard.Model.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Opserver.Data; 3 | using Opserver.Data.SQL; 4 | 5 | namespace Opserver.Views.SQL 6 | { 7 | public enum SQLViews 8 | { 9 | Servers = 0, 10 | Jobs = 1, 11 | Instance = 3, 12 | Active = 4, 13 | Top = 5, 14 | Connections = 6, 15 | Databases = 7 16 | } 17 | 18 | public class DashboardModel 19 | { 20 | public SQLInstance CurrentInstance { get; set; } 21 | public string ErrorMessage { get; set; } 22 | 23 | public int Refresh { get; set; } 24 | public SQLViews View { get; set; } 25 | 26 | public enum LastRunInterval 27 | { 28 | FiveMinutes = 5 * 60, 29 | Hour = 60 * 60, 30 | Day = 24 * 60 * 60, 31 | Week = 7 * 24 * 60 * 60 32 | } 33 | 34 | public List Connections { get; set; } 35 | public Cache Cache { get; set; } 36 | } 37 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/SQL/Databases.Model.cs: -------------------------------------------------------------------------------- 1 | using Opserver.Data.SQL; 2 | using Opserver.Helpers; 3 | 4 | namespace Opserver.Views.SQL 5 | { 6 | public class DatabasesModel 7 | { 8 | public SQLInstance Instance { get; set; } 9 | public string Database { get; set; } 10 | public string ObjectName { get; set; } 11 | public Views View { get; set; } 12 | 13 | // TODO: Remove for extensibility, create a dictionary instead and nameof() 14 | public enum Views 15 | { 16 | Tables = 0, 17 | Backups = 1, 18 | Views = 2, 19 | BlitzIndex = 3, 20 | MissingIndexes = 4, 21 | UnusedIndexes = 5, 22 | Storage = 6, 23 | Other = 7, 24 | Restores = 8, 25 | StoredProcedures = 9 26 | } 27 | 28 | public static string GetDatabaseClass(SQLInstance.Database db) 29 | { 30 | if (db.IsSystemDatabase) return "text-primary"; 31 | if (db.State == DatabaseStates.Restoring) return StatusIndicator.WarningClass; 32 | 33 | return db.MonitorStatus.TextClass(showGood: true); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/SQL/Instance.Memory.cshtml: -------------------------------------------------------------------------------- 1 | @model SQLInstance 2 | @{ 3 | var types = Model.MemoryClerkSummary; 4 | var data = types.SafeData(true); 5 | } 6 | 9 | @if (data.Any()) 10 | { 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | @foreach (var t in data) 22 | { 23 | 24 | 25 | 26 | 27 | 33 | 34 | } 35 | 36 |
    DescriptionClerk TypeMemory Used% Used
    @t.Name@t.ClerkType@t.UsedBytes.ToSize() 28 |
    29 |
    30 | @(t.UsedPercent.ToString("#0.###"))% 31 |
    32 |
    37 | } 38 | else 39 | { 40 |
    No memory clerk information available
    41 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/SQL/Instance.Model.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Opserver.Data.SQL; 3 | 4 | namespace Opserver.Views.SQL 5 | { 6 | public class InstanceModel : DashboardModel 7 | { 8 | public List PerfCounters { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/SQL/Operations.Active.Model.cs: -------------------------------------------------------------------------------- 1 | using Opserver.Data.SQL; 2 | 3 | namespace Opserver.Views.SQL 4 | { 5 | public class OperationsActiveModel : DashboardModel 6 | { 7 | public SQLInstance.ActiveSearchOptions ActiveSearchOptions { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/SQL/Ops.Top.Detail.Model.cs: -------------------------------------------------------------------------------- 1 | using Opserver.Data.SQL; 2 | 3 | namespace Opserver.Views.SQL 4 | { 5 | public class OperationsTopDetailModel 6 | { 7 | public SQLInstance Instance { get; set; } 8 | public SQLInstance.TopOperation Op { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/SQL/Servers.Model.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Opserver.Data.SQL; 3 | 4 | namespace Opserver.Views.SQL 5 | { 6 | public class ServersModel : DashboardModel 7 | { 8 | public List AzureServers { get; set; } 9 | public List Clusters { get; set; } 10 | public List StandaloneInstances { get; set; } 11 | public List AvailabilityGroups { get; set; } 12 | 13 | public SQLCluster CurrentCluster { get; set; } 14 | 15 | public JobSort? JobSort { get; set; } 16 | public SortDir? SortDirection { get; set; } 17 | } 18 | 19 | public enum JobSort 20 | { 21 | Server, 22 | Name, 23 | LastRun, 24 | Start, 25 | End, 26 | Duration, 27 | Enabled 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Opserver.Web/Views/SQL/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Opserver.Data.SQL 2 | @using Opserver.Views.SQL 3 | @inject SQLModule Module -------------------------------------------------------------------------------- /src/Opserver.Web/Views/SQL/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = this.IsAjaxRequest() ? null : "Dashboard.cshtml"; 3 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Shared/AccessDenied.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | this.SetPageTitle("Access Denied"); 3 | } 4 |
    5 |
    6 |

    7 | Access Denied 8 |

    9 |

    10 | You do not have permissions to see this page. 11 |

    12 |
    13 |
    -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | this.SetPageTitle("Error"); 3 | } 4 |
    5 |
    6 |

    7 | Oh no, an error! 8 |

    9 |

    10 | I'm so sorry, an error occured doing the thing in the place. 11 |

    12 | @if (Current.User.IsGlobalAdmin) 13 | { 14 |

    You can check the error log for details.

    15 | } 16 |
    17 |
    -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Shared/Gauges/Circle.Model.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Opserver.Data; 3 | 4 | namespace Opserver.Views.Shared.Guages 5 | { 6 | public class CircleModel 7 | { 8 | public string Label { get; set; } 9 | public IEnumerable Items { get; set; } 10 | 11 | public CircleModel(string label, IEnumerable items) 12 | { 13 | Label = label; 14 | Items = items; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Shared/IssuesButton.cshtml: -------------------------------------------------------------------------------- 1 | @inject PollingService Poller 2 | @{ 3 | var issues = Poller.GetIssues(); 4 | } 5 | @if (issues.Any()) 6 | { 7 | 12 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Shared/NoConfiguration.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | this.SetPageTitle("No configuration"); 3 | } 4 |
    5 |
    6 |

    7 | Uh no! Configuration Problem! 8 |

    9 | @if (!Current.Security.IsConfigured) 10 | { 11 |

    12 | No security provider was found! This means the security section in your config is either missing or has an invalid provider. 13 |

    14 |

    See the configuration docs here: TODO: Fix after merge

    15 | } 16 | else 17 | { 18 |

    19 | No configuration data was found. This means no Opserver sections are configured/enabled or you have no roles assigned to you. 20 |

    21 |

    22 | See the Readme.txt in /Config and *.example.json files for examples of a basic JSON configuration...or you can implement your own provider to get settings from anywhere. 23 |

    24 |

    25 | You can browse to /about to see how you have been authenticated and what monitors have been enabled. 26 |

    27 | } 28 |
    29 |
    30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Shared/PageNotFound.Model.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Views.Shared 2 | { 3 | public class PageNotFoundModel 4 | { 5 | public string Title { get; set; } 6 | public string Message { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Shared/PageNotFound.cshtml: -------------------------------------------------------------------------------- 1 | @model Opserver.Views.Shared.PageNotFoundModel 2 | @{ 3 | this.SetPageTitle("404 - Not Found"); 4 | } 5 |
    6 |
    7 |

    8 | @Model.Title.IsNullOrEmptyReturn("404 - Not Found") 9 |

    10 |

    11 | @Model.Message.IsNullOrEmptyReturn("The page you were looking for could not be found.") 12 |

    13 |
    14 |
    -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Shared/Partials.MemoryCell.Model.cs: -------------------------------------------------------------------------------- 1 | using Opserver.Data.SQL; 2 | 3 | namespace Opserver.Views.Shared 4 | { 5 | public class PartialsMemoryCellModel 6 | { 7 | public SQLInstance Instance { get; } 8 | public int DecimalPlaces { get; } 9 | 10 | public PartialsMemoryCellModel(SQLInstance instance, int decimalPlaces = 0) => 11 | (Instance, DecimalPlaces) = (instance, decimalPlaces); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Shared/Partials.MemoryCell.cshtml: -------------------------------------------------------------------------------- 1 | @inject Opserver.Data.Dashboard.DashboardModule Dashboard 2 | @model PartialsMemoryCellModel 3 | @{ 4 | var i = Model.Instance; 5 | var serverInfo = Dashboard.GetNodeByName(i.Name); 6 | } 7 | @if (serverInfo != null) 8 | { 9 | @(serverInfo?.MemoryPercentStatusSpan()) 10 | } 11 | else 12 | { 13 | var props = i.ServerProperties.SafeData(); 14 | if (props != null && props.PhysicalMemoryBytes > 0) 15 | { 16 | @(decimal.Round(i.CurrentMemoryPercent.Value, Model.DecimalPlaces) + "%") 17 | } 18 | else 19 | { 20 | ? 21 | } 22 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Shared/PollInfo.Model.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Opserver.Data; 3 | 4 | namespace Opserver.Views 5 | { 6 | public class PollInfoModel 7 | { 8 | public string Name { get; set; } 9 | public PollNode Node { get; set; } 10 | public IEnumerable Nodes { get; set; } 11 | public Cache Cache { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Shared/TopBoxOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Opserver.Views.Shared 4 | { 5 | public class TopBoxOptions 6 | { 7 | public string SearchUrl { get; set; } 8 | public IEnumerable AllNodes { get; set; } 9 | 10 | public ISearchableNode CurrentNode { get; set; } 11 | public string Url { get; set; } 12 | public bool SearchOnly { get; set; } 13 | public string SearchText { get; set; } 14 | public string SearchValue { get; set; } 15 | public string QueryParam { get; set; } = "q"; 16 | public Dictionary SearchParams { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Shared/TopRefresh.Model.cs: -------------------------------------------------------------------------------- 1 | namespace Opserver.Views.Shared 2 | { 3 | public class TopRefreshModel 4 | { 5 | public string Tab { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Shared/TopRefresh.cshtml: -------------------------------------------------------------------------------- 1 | @model Opserver.Views.Shared.TopRefreshModel 2 |
    3 | 4 |
    5 |
      6 | 7 |
    -------------------------------------------------------------------------------- /src/Opserver.Web/Views/Shared/TopTabs.cshtml: -------------------------------------------------------------------------------- 1 | @using StackExchange.Profiling 2 | @functions 3 | { 4 | void RenderTab(NavTab tab) 5 | { 6 | if (tab.IsEnabled) 7 | { 8 | // Optimism! 9 | using (MiniProfiler.Current.Step("Render Tab: " + tab.Name)) 10 | { 11 |
  • 12 | 13 | @tab.Name 14 | @if (tab.BadgeCount > 0) 15 | { 16 | @tab.BadgeCount.ToComma() 17 | } 18 | 19 |
  • 20 | } 21 | } 22 | } 23 | } 24 | @using (MiniProfiler.Current.Step("TopTabs")) 25 | { 26 | 32 | } -------------------------------------------------------------------------------- /src/Opserver.Web/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using EnumsNET 2 | @using Microsoft.AspNetCore.Html 3 | @using Microsoft.AspNetCore.WebUtilities 4 | @using Opserver 5 | @using Opserver.Controllers 6 | @using Opserver.Data 7 | @using Opserver.Helpers 8 | @using Opserver.Helpers.Tag 9 | @using System.Globalization 10 | @using Opserver.Views.Shared 11 | @using Roles = Opserver.Models.Roles; 12 | @inject OpserverSettings Settings 13 | @addTagHelper *, Opserver 14 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -------------------------------------------------------------------------------- /src/Opserver.Web/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/Master.cshtml"; 3 | } -------------------------------------------------------------------------------- /src/Opserver.Web/bundleconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputFiles": [ 4 | "ContentSource/bootstrap/js/bootstrap.min.js", 5 | "ContentSource/js/plugins/*.js", 6 | "ContentSource/js/Scripts.js" 7 | ], 8 | "outputFileName": "wwwroot/Content/js/bundle.js", 9 | "sourceMap": true 10 | } 11 | ] 12 | -------------------------------------------------------------------------------- /src/Opserver.Web/compilerconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputFile": "ContentSource/themes/dark.less", 4 | "outputFile": "wwwroot/Content/themes/dark.css", 5 | "sourceMap": true 6 | }, 7 | { 8 | "inputFile": "ContentSource/themes/light.less", 9 | "outputFile": "wwwroot/Content/themes/light.css", 10 | "sourceMap": true 11 | } 12 | ] -------------------------------------------------------------------------------- /src/Opserver.Web/compilerconfig.json.defaults: -------------------------------------------------------------------------------- 1 | { 2 | "compilers": { 3 | "less": { 4 | "autoPrefix": "", 5 | "cssComb": "none", 6 | "ieCompat": true, 7 | "strictMath": false, 8 | "strictUnits": false, 9 | "relativeUrls": true, 10 | "rootPath": "", 11 | "soruceMapRoot": null, 12 | "soruceMapBasePath": null, 13 | "sourceMap": false 14 | }, 15 | "sass": { 16 | "includePath": null, 17 | "indentType": "space", 18 | "indentWidth": 2, 19 | "outputStyle": "nested", 20 | "Precision": 5, 21 | "relativeUrls": true, 22 | "soruceMapRoot": null, 23 | "sourceMap": false 24 | }, 25 | "stylus": { 26 | "sourceMap": false 27 | }, 28 | "babel": { 29 | "sourceMap": false 30 | }, 31 | "coffeescript": { 32 | "bare": false, 33 | "runtimeMode": "node", 34 | "sourceMap": false 35 | } 36 | }, 37 | "minifiers": { 38 | "css": { 39 | "enabled": true, 40 | "termSemicolons": true, 41 | "gzip": false 42 | }, 43 | "javascript": { 44 | "enabled": true, 45 | "termSemicolons": true, 46 | "gzip": false 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/Opserver.Web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/favicon.ico -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/font-awesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/font-awesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/apple-touch-icon.png -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/logo/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/logo/icon-128.png -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/logo/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/logo/icon-16.png -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/logo/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/logo/icon-256.png -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/logo/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/logo/icon-32.png -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/logo/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/logo/logo.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/logo/logo.eps -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/arithmetic_expression.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/arithmetic_expression.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/assert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/assert.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/assign.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/assign.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/bitmap.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/bitmap.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/bookmark_lookup.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/bookmark_lookup.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/clustered_index_delete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/clustered_index_delete.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/clustered_index_insert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/clustered_index_insert.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/clustered_index_scan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/clustered_index_scan.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/clustered_index_seek.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/clustered_index_seek.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/clustered_index_update.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/clustered_index_update.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/collapse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/collapse.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/compute_scalar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/compute_scalar.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/concatenation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/concatenation.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/constant_scan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/constant_scan.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/convert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/convert.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/declare.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/declare.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/deleted_scan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/deleted_scan.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/distribute_streams.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/distribute_streams.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/dynamic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/dynamic.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/fetch_query.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/fetch_query.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/filter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/filter.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/gather_streams.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/gather_streams.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/hash_match.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/hash_match.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/icons.png -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/if.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/if.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/inserted_scan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/inserted_scan.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/intrinsic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/intrinsic.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/keyset.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/keyset.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/log_row_scan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/log_row_scan.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/merge_interval.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/merge_interval.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/merge_join.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/merge_join.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/nested_loops.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/nested_loops.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/nonclustered_index_delete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/nonclustered_index_delete.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/nonclustered_index_insert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/nonclustered_index_insert.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/nonclustered_index_scan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/nonclustered_index_scan.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/nonclustered_index_seek.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/nonclustered_index_seek.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/nonclustered_index_spool.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/nonclustered_index_spool.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/nonclustered_index_update.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/nonclustered_index_update.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/online_index_insert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/online_index_insert.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/parameter_table_scan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/parameter_table_scan.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/population_query.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/population_query.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/rdi_lookup.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/rdi_lookup.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/refresh_query.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/refresh_query.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/remote_delete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/remote_delete.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/remote_insert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/remote_insert.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/remote_query.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/remote_query.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/remote_scan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/remote_scan.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/remote_update.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/remote_update.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/repartition_streams.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/repartition_streams.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/result.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/result.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/row_count_spool.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/row_count_spool.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/segment.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/segment.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/sequence.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/sequence.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/sequenceproject.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/sequenceproject.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/snapshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/snapshot.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/sort.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/sort.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/split.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/split.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/spool.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/spool.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/stream_aggregate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/stream_aggregate.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/switch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/switch.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/table_delete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/table_delete.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/table_insert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/table_insert.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/table_scan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/table_scan.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/table_spool.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/table_spool.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/table_update.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/table_update.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/table_valued_function.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/table_valued_function.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/top.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/top.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/udx.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/udx.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/Content/img/query-plan/while.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/Content/img/query-plan/while.gif -------------------------------------------------------------------------------- /src/Opserver.Web/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opserver/Opserver/e29191823f426423ffeb6a59fa4ad1aa56855203/src/Opserver.Web/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/Opserver.ruleset: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | Library 4 | 2.4.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/Opserver.Tests/Opserver.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Opserver.Tests 4 | Opserver.Tests 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | --------------------------------------------------------------------------------