├── .gitignore ├── AzureStatusPage.nuspec ├── AzureStatusPageClient.nuspec ├── README.md ├── appveyor.yml ├── assets ├── icon-status-page-1024x1024.png ├── icon-status-page-20x20.png └── icon-status-page-30x30.png ├── azure_status_page.client.demo ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── azure_status_page.client.demo.csproj └── packages.config ├── azure_status_page.client ├── Contracts │ └── Repositories │ │ ├── IMeterInstanceRepository.cs │ │ └── IMeterManagerRepository.cs ├── MeterManager.cs ├── Models │ ├── MeterDefinition.cs │ └── MeterInstance.cs ├── Properties │ └── AssemblyInfo.cs ├── Repositories │ ├── AzureTableBaseRepository.cs │ ├── AzureTableMeterInstanceRepository.cs │ ├── AzureTableMeterManagerRepository.cs │ └── Entities │ │ └── MeterInstanceTableEntity.cs ├── azure_status_page.client.csproj └── packages.config ├── azure_status_page.core ├── Contracts │ ├── Repositories │ │ └── IMeterManagerExRepository.cs │ └── Services │ │ ├── ICheckMeterProvider.cs │ │ └── INotificationProvider.cs ├── Extensions │ └── DirectorEx.cs ├── Models │ ├── MeterCheckResult.cs │ ├── MeterDefinitionServerBased.cs │ ├── MeterStorageConfigurationModel.cs │ └── SiteReplacementParameters.cs ├── Properties │ └── AssemblyInfo.cs ├── Repositories │ ├── AzureTableMeterManagerExRepository.cs │ └── Entities │ │ └── AzureTableMeterDefinitionServerBased.cs ├── Services │ ├── AzureEnvironmentService.cs │ ├── AzureWebJobInstaller.cs │ ├── AzureWebJobShutdownService.cs │ ├── CheckMeterProviders │ │ ├── EqualsValueCheckMeterProvider.cs │ │ ├── HeartbeatCheckMeterProvider.cs │ │ ├── MaxValueCheckMeterProvider.cs │ │ └── MinValueCheckMeterProvider.cs │ ├── CheckMeterService.cs │ ├── MeterManagerEx.cs │ ├── NotificationProviders │ │ └── PushOverNotificationProvider.cs │ ├── SiteExtensionConfigurationService.cs │ └── StatusPageGeneratorService.cs ├── azure_status_page.core.csproj └── packages.config ├── azure_status_page.jobs ├── Assets │ └── default-template.html ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── app.config └── azure_status_page.jobs.csproj ├── azure_status_page.sln ├── azure_status_page ├── App_Start │ ├── RouteConfig.cs │ └── WebApiConfig.cs ├── Content │ └── bs-callout.css ├── Controllers │ ├── HomeController.cs │ └── MetersController.cs ├── Global.asax ├── Global.asax.cs ├── ViewModels │ └── MetersViewModel.cs ├── Views │ ├── Home │ │ └── Index.cshtml │ ├── Meters │ │ ├── Add.cshtml │ │ └── Index.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ └── _Layout.cshtml │ ├── Web.config │ └── _ViewStart.cshtml ├── Web.config ├── applicationHost.xdt ├── azure_status_page.csproj └── packages.config └── docs ├── arch-overview.png ├── arch-overview.pptx ├── extension-installed.png ├── extension-result.png └── extension-setup.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Autosave files 2 | *~ 3 | 4 | # build 5 | [Oo]bj/ 6 | [Bb]in/ 7 | packages/ 8 | TestResults/ 9 | 10 | # globs 11 | Makefile.in 12 | *.DS_Store 13 | *.sln.cache 14 | *.suo 15 | *.cache 16 | *.pidb 17 | *.userprefs 18 | *.usertasks 19 | config.log 20 | config.make 21 | config.status 22 | aclocal.m4 23 | install-sh 24 | autom4te.cache/ 25 | *.user 26 | *.tar.gz 27 | tarballs/ 28 | test-results/ 29 | Thumbs.db 30 | 31 | # Mac bundle stuff 32 | *.dmg 33 | *.app 34 | 35 | # resharper 36 | *_Resharper.* 37 | *.Resharper 38 | 39 | # dotCover 40 | *.dotCover 41 | *.nupkg 42 | azure_status_page/App_Data/WebJob 43 | 44 | credentials.json 45 | -------------------------------------------------------------------------------- /AzureStatusPage.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | statuspage 5 | Status Page 6 | 0.0.1 7 | Dirk Eisenberg 8 | https://opensource.org/licenses/MIT 9 | https://github.com/dei79/azure-status-page 10 | false 11 | This Site Extension allows to implement a nice looking status page in seconds based on Azure App Services. The extension is a full solution including the management of different meters and notifications. 12 | https://raw.githubusercontent.com/dei79/azure-status-page/master/assets/icon-status-page-30x30.png 13 | status-page status monitoring siteextension meters 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /AzureStatusPageClient.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | statuspageclient 5 | Azure Status Page Client 6 | 0.0.1 7 | Dirk Eisenberg 8 | https://opensource.org/licenses/MIT 9 | https://github.com/dei79/azure-status-page 10 | false 11 | Client Library to use with the Azure Status Page Site Extension 12 | https://raw.githubusercontent.com/dei79/azure-status-page/master/assets/icon-status-page-30x30.png 13 | status-page status monitoring siteextension meters 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > We want to let this open source project grow, beause of that we are looking for supporters who are using the Status Page Generator in their projects and will extend it as needed. Just contact us via GitHub to contribute and become an active community member! 2 | 3 | # Azure Status Page Generator 4 | [![Build status](https://ci.appveyor.com/api/projects/status/nd602nm4y669oijg?svg=true)](https://ci.appveyor.com/project/dei79/azure-status-page) 5 | 6 | If you think about Status Page there are several requirements this kind of WebSites must meet, e.g. 7 | 8 | * It must run on a separate infrastructure, e.g. a different geographical region or in a different cloud infrastructure 9 | * It must support client side meters which will be pushed to the status page directly out of the application 10 | * It must support server side meters which will be executed from the status page on a recurrent schedule 11 | * It must commmunicate service outages to the customers 12 | * It must support push communication to the operations staff with all the details to bring the service back on track 13 | 14 | There are several SaaS services offering a solution, e.g. https://statuspage.io. This project is intended to offer a similar service hosted in Azure App Services which allows everybody to create status pages in Azure within minutes. 15 | 16 | ## Solutions Overview 17 | 18 | ![Image of Status Page Generator Architecture](https://github.com/dei79/azure-status-page/blob/master/docs/arch-overview.png) 19 | 20 | ## How to setup 21 | 22 | 1. Visit the Azure Portal (https://portal.azure.com) 23 | 24 | 2. Create a new Azure WebSite based on an existing Azure Service Plan or create a new Azure Service Plan for that. (Even the free pricing tier works well) 25 | 26 | 3. Visit the "Extensions Menu" and install the "Status Page" Extension 27 | ![Extensions List](https://github.com/dei79/azure-status-page/blob/master/docs/extension-installed.png) 28 | 29 | 4. Prepare a new Azure Storage Account which can be used from the Status Page service as backend. A Local Redundant account is totally enough. 30 | 31 | 5. Visit the Status Page Extension and configure the different details, e.g. the credentials for the storage account or the image the status page should use. 32 | ![Setup Extensiom](https://github.com/dei79/azure-status-page/blob/master/docs/extension-setup.png) 33 | 34 | 6. Ensure that you installed the WebJob in your Azure WebSite and voila the Status Page will be generated 35 | ![Final](https://github.com/dei79/azure-status-page/blob/master/docs/extension-result.png) 36 | 37 | ## Meters 38 | 39 | ### Client Side Meters 40 | Client side Meters are information delivered from the services as self, e.g. a heartbeat or some complex technical checks from time to time. This meters will be shown automatically in the status page. To integrate client side meters check the Status Page SDKs here: 41 | * NET SDK for Client Side Meters - https://www.nuget.org/packages/statuspageclient/ 42 | * nodejs SDK for Client Side Meters - https://www.npmjs.com/package/azure-status-page-client 43 | As soon your client is posting data the first time client side meters will be visible at the Status Page! 44 | 45 | ### Server Side Meters 46 | Service Side Meters are executed in the App Service the Status Page is hosted and can be used to check specific metrics from outside of the applicaton, e.g. the availability of a specific application. 47 | 48 | ## Contributing 49 | 50 | 1. Fork it! 51 | 2. Create your feature branch: `git checkout -b my-new-feature` 52 | 3. Commit your changes: `git commit -m 'Add some feature'` 53 | 4. Push to the branch: `git push origin my-new-feature` 54 | 5. Submit a pull request :) 55 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.0.1.{build} 2 | configuration: Release 3 | platform: Any CPU 4 | assembly_info: 5 | patch: true 6 | file: '**\AssemblyInfo.*' 7 | assembly_version: '{version}' 8 | assembly_file_version: '{version}' 9 | assembly_informational_version: '{version}' 10 | before_build: 11 | - cmd: nuget restore 12 | build: 13 | verbosity: minimal 14 | after_build: 15 | - cmd: >- 16 | nuget pack AzureStatusPage.nuspec -Version %APPVEYOR_BUILD_VERSION% -Verbosity detailed -BasePath azure_status_page 17 | 18 | nuget pack AzureStatusPageClient.nuspec -Version %APPVEYOR_BUILD_VERSION% -Verbosity detailed -BasePath azure_status_page.client 19 | test: off 20 | artifacts: 21 | - path: '*.nupkg' 22 | name: SiteExtension -------------------------------------------------------------------------------- /assets/icon-status-page-1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dei79/azure-status-page/fdd26666bfa681828a13eda5b0a4ffd1a5e7e297/assets/icon-status-page-1024x1024.png -------------------------------------------------------------------------------- /assets/icon-status-page-20x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dei79/azure-status-page/fdd26666bfa681828a13eda5b0a4ffd1a5e7e297/assets/icon-status-page-20x20.png -------------------------------------------------------------------------------- /assets/icon-status-page-30x30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dei79/azure-status-page/fdd26666bfa681828a13eda5b0a4ffd1a5e7e297/assets/icon-status-page-30x30.png -------------------------------------------------------------------------------- /azure_status_page.client.demo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using azure_status_page.client.Models; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | 7 | namespace azure_status_page.client.demo 8 | { 9 | class MainClass 10 | { 11 | public static void Main(string[] args) 12 | { 13 | // define the meter ids 14 | var meterHeartBeatMeterId = "3ac0ab14-561f-452b-aee7-85ef79d6eef1"; 15 | 16 | // Load the storage credentials from external file 17 | // { "key": "xxx", secret="yyy" } 18 | var credentials = new { key = "", secret = "" }; 19 | dynamic d = JsonConvert.DeserializeObject(File.ReadAllText(Path.Combine("..", "..", "..", "credentials.json")), credentials.GetType()); 20 | 21 | // Initial configuration of the MeterManager 22 | MeterManager.Instance.ConfigureAzureTableStoreRepository(d.key, d.secret, "MeterInformation"); 23 | 24 | // Define a meter, e.g. a heartbeat for a continous job 25 | var meterId = MeterManager.Instance.RegisterMeter(meterHeartBeatMeterId, "Image Processing", "Background Processing", nMeterTypes.Heartbeat, 100, 500).MeterId; 26 | 27 | // Update this meter with the hearbeat 28 | MeterManager.Instance.UpdateMeter(meterId, "Node01.WebJob01.ImgProcessing"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /azure_status_page.client.demo/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle("azure_status_page.client.demo")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("")] 12 | [assembly: AssemblyCopyright("${AuthorCopyright}")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion("1.0.*")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | //[assembly: AssemblyDelaySign(false)] 26 | //[assembly: AssemblyKeyFile("")] 27 | -------------------------------------------------------------------------------- /azure_status_page.client.demo/azure_status_page.client.demo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {B35B4ED1-AD7C-4E44-956C-6E508E68711E} 7 | Exe 8 | azure_status_page.client.demo 9 | azure_status_page.client.demo 10 | v4.5 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug 17 | DEBUG; 18 | prompt 19 | 4 20 | true 21 | 22 | 23 | true 24 | bin\Release 25 | prompt 26 | 4 27 | true 28 | 29 | 30 | 31 | 32 | ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {EBD3A11E-FD34-4BF9-8768-BE5A613F7AE7} 43 | azure_status_page.client 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /azure_status_page.client.demo/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /azure_status_page.client/Contracts/Repositories/IMeterInstanceRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using azure_status_page.client.Models; 4 | 5 | namespace azure_status_page.client.Contracts.Repositories 6 | { 7 | public interface IMeterInstanceRepository 8 | { 9 | List LoadInstances(); 10 | 11 | void StoreInstance(MeterInstance instance); 12 | 13 | void DeleteInstance(string MeterId, string MeterInstanceId); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /azure_status_page.client/Contracts/Repositories/IMeterManagerRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace azure_status_page.client.Contracts.Repositories 4 | { 5 | public interface IMeterManagerRepository : IMeterInstanceRepository 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /azure_status_page.client/MeterManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using azure_status_page.client.Contracts.Repositories; 4 | using azure_status_page.client.Models; 5 | using azure_status_page.client.Repositories; 6 | 7 | namespace azure_status_page.client 8 | { 9 | public class MeterManager 10 | { 11 | /// 12 | /// Singleton implementation of the MeterManager 13 | /// 14 | private static MeterManager instance; 15 | 16 | private MeterManager() { } 17 | 18 | public static MeterManager Instance 19 | { 20 | get 21 | { 22 | if (instance == null) 23 | { 24 | instance = new MeterManager(); 25 | } 26 | 27 | return instance; 28 | } 29 | } 30 | 31 | /// 32 | /// Support for our standard repositories 33 | /// 34 | /// The meter manager repository. 35 | private IMeterManagerRepository meterManagerRepository { get; set; } 36 | 37 | public void ConfigureAzureTableStoreRepository(string storageKey, string storageSecret, string storageTable) 38 | { 39 | meterManagerRepository = new AzureTableMeterManagerRepository(storageKey, storageSecret, storageTable); 40 | } 41 | 42 | public void ConfigureRepository(IMeterManagerRepository repository) 43 | { 44 | meterManagerRepository = repository; 45 | } 46 | 47 | // properties 48 | private Dictionary meterCache { get; set; } = new Dictionary(); 49 | 50 | /// 51 | /// Allows to register a new meter 52 | /// 53 | /// Meter identifier. 54 | /// Meter name. 55 | /// Meter category. 56 | /// Meter type. 57 | /// Meter value. 58 | public MeterDefinition RegisterMeter(String meterId, String meterName, String meterCategory, nMeterTypes meterType, Int32 meterDisplayOrder, Decimal meterValue) 59 | { 60 | // double check 61 | if (meterCache.ContainsKey(meterId)) 62 | { 63 | throw new Exception("Invalid MeterId, can't add the meter because the same meterid was used before for another meter"); 64 | } 65 | 66 | // add the meter 67 | var meterDefinition = new MeterDefinition() { 68 | MeterId = meterId, 69 | MeterName = meterName, 70 | MeterCategory = meterCategory, 71 | MeterDisplayOrder = meterDisplayOrder, 72 | MeterType = meterType, 73 | MeterValue = meterValue 74 | }; 75 | 76 | meterCache.Add(meterId, meterDefinition); 77 | 78 | // done 79 | return meterDefinition; 80 | } 81 | 82 | /// 83 | /// Updates or creates an existing MeterInstance 84 | /// 85 | /// Meter identifier. 86 | /// Meter instance identifier. 87 | public MeterInstance UpdateMeter(String meterId, String meterInstanceId) 88 | { 89 | return this.UpdateMeter(meterId, meterInstanceId, Decimal.Zero); 90 | } 91 | 92 | public MeterInstance UpdateMeter(String meterId, String meterInstanceId, Decimal meterInstanceValue) 93 | { 94 | // lookup if we have the registered meter 95 | if (!meterCache.ContainsKey(meterId)) 96 | { 97 | throw new Exception("Meterdefinition for meter with id " + meterId + " is not registered"); 98 | } 99 | 100 | // update the meter by creating a new meter instance and storing this in the repository 101 | var meterInstance = new MeterInstance(meterCache[meterId]); 102 | meterInstance.MeterInstanceId = meterInstanceId; 103 | meterInstance.MeterInstanceValue = meterInstanceValue; 104 | meterInstance.MeterInstanceTimestamp = DateTime.Now; 105 | 106 | // store the meter instance 107 | meterManagerRepository.StoreInstance(meterInstance); 108 | 109 | // done 110 | return meterInstance; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /azure_status_page.client/Models/MeterDefinition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace azure_status_page.client.Models 3 | { 4 | public enum nMeterTypes 5 | { 6 | Heartbeat = 1, 7 | MinValue = 2, 8 | MaxValue = 3, 9 | EqualsValue = 4 10 | } 11 | 12 | public class MeterDefinition 13 | { 14 | public String MeterId { get; set; } 15 | public String MeterName { get; set; } 16 | public String MeterCategory { get; set; } 17 | public nMeterTypes MeterType { get; set; } 18 | public Decimal MeterValue { get; set; } 19 | public Int32 MeterDisplayOrder { get; set; } 20 | 21 | public MeterDefinition() { } 22 | 23 | public MeterDefinition(MeterDefinition src) 24 | { 25 | MeterId = src.MeterId; 26 | MeterName = src.MeterName; 27 | MeterCategory = src.MeterCategory; 28 | MeterType = src.MeterType; 29 | MeterValue = src.MeterValue; 30 | MeterDisplayOrder = src.MeterDisplayOrder; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /azure_status_page.client/Models/MeterInstance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.WindowsAzure.Storage; 4 | using Microsoft.WindowsAzure.Storage.Table; 5 | 6 | namespace azure_status_page.client.Models 7 | { 8 | public class MeterInstance : MeterDefinition 9 | { 10 | public string MeterInstanceId { get; set; } 11 | public Decimal MeterInstanceValue { get; set; } 12 | public DateTime MeterInstanceTimestamp { get; set; } 13 | 14 | public MeterInstance() { } 15 | 16 | public MeterInstance(MeterDefinition baseDefinition) 17 | : base(baseDefinition) 18 | { } 19 | 20 | public MeterInstance(MeterInstance src) 21 | : base(src) 22 | { 23 | MeterInstanceId = src.MeterInstanceId; 24 | MeterInstanceValue = src.MeterInstanceValue; 25 | MeterInstanceTimestamp = src.MeterInstanceTimestamp; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /azure_status_page.client/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle("azure_status_page.client")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("")] 12 | [assembly: AssemblyCopyright("${AuthorCopyright}")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion("1.0.*")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | //[assembly: AssemblyDelaySign(false)] 26 | //[assembly: AssemblyKeyFile("")] 27 | -------------------------------------------------------------------------------- /azure_status_page.client/Repositories/AzureTableBaseRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.WindowsAzure.Storage; 4 | using Microsoft.WindowsAzure.Storage.Table; 5 | 6 | namespace azure_status_page.client.Repositories 7 | { 8 | public class AzureTableBaseRepository 9 | { 10 | protected string StorageTable { get; private set; } 11 | protected CloudStorageAccount StorageAccount { get; private set; } 12 | 13 | public AzureTableBaseRepository(string storageKey, string storageSecret, string storageTable) 14 | { 15 | StorageTable = storageTable; 16 | StorageAccount = CloudStorageAccount.Parse(String.Format("DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}", storageKey, storageSecret)); 17 | } 18 | 19 | protected void InsertOrMerge(TableEntity entity, string tableName) 20 | { 21 | // Create the table client. 22 | CloudTableClient tableClient = StorageAccount.CreateCloudTableClient(); 23 | 24 | // Retrieve a reference to the table. 25 | CloudTable table = tableClient.GetTableReference(tableName); 26 | 27 | // Create the batch operation. 28 | TableBatchOperation batchOperation = new TableBatchOperation(); 29 | 30 | // Add the entity to the batch 31 | batchOperation.InsertOrMerge(entity); 32 | 33 | // Merge 34 | try 35 | { 36 | table.ExecuteBatch(batchOperation); 37 | } 38 | catch (StorageException e) 39 | { 40 | if (e.RequestInformation.HttpStatusCode == 404) 41 | { 42 | // Create the table if it doesn't exist. 43 | table.CreateIfNotExists(); 44 | 45 | // do the insert again 46 | table.ExecuteBatch(batchOperation); 47 | } 48 | } 49 | 50 | } 51 | 52 | public IEnumerable LoadInstances(string tableName) where T : TableEntity, new() 53 | { 54 | // Create the table client. 55 | CloudTableClient tableClient = StorageAccount.CreateCloudTableClient(); 56 | 57 | // Retrieve a reference to the table. 58 | CloudTable table = tableClient.GetTableReference(tableName); 59 | 60 | // Construct the query operation for all customer entities where PartitionKey="Smith". 61 | var query = new TableQuery(); 62 | 63 | // query 64 | return table.ExecuteQuery(query); 65 | } 66 | 67 | public T LoadInstance(string tableName, string partitionKey, string rowKey) where T : TableEntity, new() 68 | { 69 | // Create the table client. 70 | CloudTableClient tableClient = StorageAccount.CreateCloudTableClient(); 71 | 72 | // Retrieve a reference to the table. 73 | CloudTable table = tableClient.GetTableReference(tableName); 74 | 75 | // Create a retrieve operation that takes a customer entity. 76 | TableOperation retrieveOperation = TableOperation.Retrieve(partitionKey, rowKey); 77 | 78 | // Execute the retrieve operation. 79 | TableResult retrievedResult = table.Execute(retrieveOperation); 80 | 81 | // Print the phone number of the result. 82 | return retrievedResult.Result as T; 83 | } 84 | 85 | public void DeleteEntity(string tableName, string partitonKey, string rowKey) 86 | { 87 | // Create the table client. 88 | CloudTableClient tableClient = StorageAccount.CreateCloudTableClient(); 89 | 90 | // Retrieve a reference to the table. 91 | CloudTable table = tableClient.GetTableReference(tableName); 92 | 93 | // Create a retrieve operation that expects a customer entity. 94 | TableOperation retrieveOperation = TableOperation.Retrieve(partitonKey, rowKey); 95 | 96 | // Execute the operation. 97 | TableResult retrievedResult = table.Execute(retrieveOperation); 98 | 99 | // Assign the result to a CustomerEntity. 100 | var deleteEntity = (TableEntity)retrievedResult.Result; 101 | 102 | // Create the Delete TableOperation. 103 | if (deleteEntity != null) 104 | { 105 | TableOperation deleteOperation = TableOperation.Delete(deleteEntity); 106 | 107 | // Execute the operation. 108 | table.Execute(deleteOperation); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /azure_status_page.client/Repositories/AzureTableMeterInstanceRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using azure_status_page.client.Contracts.Repositories; 4 | using azure_status_page.client.Models; 5 | using azure_status_page.client.Repositories.Entities; 6 | using Microsoft.WindowsAzure.Storage; 7 | using Microsoft.WindowsAzure.Storage.Table; 8 | 9 | namespace azure_status_page.client.Repositories 10 | { 11 | public class AzureTableMeterInstanceRepository : AzureTableBaseRepository, IMeterInstanceRepository 12 | { 13 | public AzureTableMeterInstanceRepository(string storageKey, string storageSecret) 14 | : this(storageKey, storageSecret, "MeterInformation") 15 | { } 16 | 17 | public AzureTableMeterInstanceRepository(string storageKey, string storageSecret, string storageTable) 18 | : base(storageKey, storageSecret, storageTable) 19 | { } 20 | 21 | public void DeleteInstance(string MeterId, string MeterInstanceId) 22 | { 23 | DeleteEntity(StorageTable, MeterId, MeterInstanceId); 24 | } 25 | 26 | public List LoadInstances() 27 | { 28 | // query 29 | var queryResult = LoadInstances(StorageTable); 30 | 31 | // Print the fields for each customer. 32 | var result = new List(); 33 | foreach (MeterInstanceTableEntity entity in queryResult) 34 | { 35 | result.Add(entity.ToModel()); 36 | } 37 | 38 | // done 39 | return result; 40 | } 41 | 42 | public void StoreInstance(MeterInstance instance) 43 | { 44 | // Generate the table store entity 45 | var entity = MeterInstanceTableEntity.FromModel(instance); 46 | 47 | // Merge 48 | InsertOrMerge(entity, StorageTable); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /azure_status_page.client/Repositories/AzureTableMeterManagerRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using azure_status_page.client.Contracts.Repositories; 4 | using azure_status_page.client.Models; 5 | 6 | namespace azure_status_page.client.Repositories 7 | { 8 | public class AzureTableMeterManagerRepository : AzureTableBaseRepository, IMeterManagerRepository, IMeterInstanceRepository 9 | { 10 | private AzureTableMeterInstanceRepository meterInstanceRepository { get; set; } 11 | 12 | public AzureTableMeterManagerRepository(string storageKey, string storageSecret, string storageTable) 13 | :base(storageKey, storageSecret, storageTable) 14 | { 15 | meterInstanceRepository = new AzureTableMeterInstanceRepository(storageKey, storageSecret, storageTable); 16 | } 17 | 18 | public List LoadInstances() 19 | { 20 | return meterInstanceRepository.LoadInstances(); 21 | } 22 | 23 | public void StoreInstance(MeterInstance instance) 24 | { 25 | meterInstanceRepository.StoreInstance(instance); 26 | } 27 | 28 | public void DeleteInstance(string MeterId, string MeterInstanceId) 29 | { 30 | meterInstanceRepository.DeleteInstance(MeterId, MeterInstanceId); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /azure_status_page.client/Repositories/Entities/MeterInstanceTableEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using azure_status_page.client.Models; 3 | using Microsoft.WindowsAzure.Storage.Table; 4 | 5 | namespace azure_status_page.client.Repositories.Entities 6 | { 7 | internal class MeterInstanceTableEntity : TableEntity 8 | { 9 | public MeterInstanceTableEntity(string MeterId, string MeterInstanceId) 10 | { 11 | this.PartitionKey = MeterId; 12 | this.RowKey = MeterInstanceId; 13 | this.MeterId = MeterId; 14 | this.MeterInstanceId = MeterInstanceId; 15 | } 16 | 17 | public MeterInstanceTableEntity() { } 18 | 19 | public string MeterInstanceId { get; set; } 20 | public Double MeterInstanceValue { get; set; } 21 | public String MeterInstanceTimestamp { get; set; } 22 | public String MeterId { get; set; } 23 | public String MeterName { get; set; } 24 | 25 | public String MeterCategory { get; set; } 26 | public Int32 MeterDisplayOrder { get; set; } 27 | public Double MeterType { get; set; } 28 | public Double MeterValue { get; set; } 29 | 30 | public MeterInstance ToModel() { 31 | 32 | return new MeterInstance() 33 | { 34 | MeterId = this.MeterId, 35 | MeterName = this.MeterName, 36 | MeterCategory = this.MeterCategory, 37 | MeterDisplayOrder = this.MeterDisplayOrder, 38 | MeterType = (nMeterTypes)Enum.ToObject(typeof(nMeterTypes), Convert.ToInt32(this.MeterType)), 39 | MeterValue = new Decimal(this.MeterValue), 40 | MeterInstanceId = this.MeterInstanceId, 41 | MeterInstanceValue = new Decimal(this.MeterInstanceValue), 42 | MeterInstanceTimestamp = DateTime.Parse(this.MeterInstanceTimestamp) 43 | }; 44 | } 45 | 46 | public static MeterInstanceTableEntity FromModel(MeterInstance instance) 47 | { 48 | return new MeterInstanceTableEntity(instance.MeterId, instance.MeterInstanceId) 49 | { 50 | MeterName = instance.MeterName, 51 | MeterCategory = instance.MeterCategory, 52 | MeterDisplayOrder = instance.MeterDisplayOrder, 53 | MeterType = Convert.ToDouble(Convert.ToInt32(instance.MeterType)), 54 | MeterValue = Convert.ToDouble(instance.MeterValue), 55 | MeterInstanceValue = Convert.ToDouble(instance.MeterInstanceValue), 56 | MeterInstanceTimestamp = instance.MeterInstanceTimestamp.ToString("yyyy-MM-ddTHH:mm:ssZ") 57 | }; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /azure_status_page.client/azure_status_page.client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {EBD3A11E-FD34-4BF9-8768-BE5A613F7AE7} 7 | Library 8 | azure_status_page.client 9 | azure_status_page.client 10 | v4.5 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug 17 | DEBUG; 18 | prompt 19 | 4 20 | false 21 | 22 | 23 | true 24 | bin\Release 25 | prompt 26 | 4 27 | false 28 | 29 | 30 | 31 | 32 | ..\packages\Microsoft.Azure.KeyVault.Core.1.0.0\lib\net40\Microsoft.Azure.KeyVault.Core.dll 33 | 34 | 35 | ..\packages\Microsoft.Data.Edm.5.8.2\lib\net40\Microsoft.Data.Edm.dll 36 | 37 | 38 | ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll 39 | 40 | 41 | ..\packages\System.Spatial.5.8.2\lib\net40\System.Spatial.dll 42 | 43 | 44 | ..\packages\Microsoft.Data.OData.5.8.2\lib\net40\Microsoft.Data.OData.dll 45 | 46 | 47 | ..\packages\Microsoft.Data.Services.Client.5.8.2\lib\net40\Microsoft.Data.Services.Client.dll 48 | 49 | 50 | ..\packages\WindowsAzure.Storage.8.1.0\lib\net45\Microsoft.WindowsAzure.Storage.dll 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /azure_status_page.client/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /azure_status_page.core/Contracts/Repositories/IMeterManagerExRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using azure_status_page.client.Contracts.Repositories; 4 | using azure_status_page.core.Models; 5 | 6 | namespace azure_status_page.core.Repositories 7 | { 8 | public interface IMeterManagerExRepository : IMeterInstanceRepository 9 | { 10 | void CreateOrUpdateServerSideDefinition(MeterDefinitionServerBased definition); 11 | 12 | List LoadServerBasedDefinitions(); 13 | 14 | MeterDefinitionServerBased LoadServerBasedDefinition(string MeterId, string MeterCheckType); 15 | 16 | void DeleteSideDefinition(string MeterId, string MeterCheckType); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /azure_status_page.core/Contracts/Services/ICheckMeterProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using azure_status_page.client.Models; 3 | 4 | namespace azure_status_page.core 5 | { 6 | public interface ICheckMeterProvider 7 | { 8 | MeterCheckResult checkMeterInstance(MeterInstance instance); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /azure_status_page.core/Contracts/Services/INotificationProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace azure_status_page.core 3 | { 4 | public interface INotificationProvider 5 | { 6 | void notify(MeterCheckResult checkResult); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /azure_status_page.core/Extensions/DirectorEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace azure_status_page.core 5 | { 6 | public class DirectoryEx 7 | { 8 | public static void Copy(string sourceDir, string targetDir, bool overwrite = false) 9 | { 10 | Directory.CreateDirectory(targetDir); 11 | 12 | foreach (var file in Directory.GetFiles(sourceDir)) 13 | File.Copy(file, Path.Combine(targetDir, Path.GetFileName(file)), overwrite); 14 | 15 | foreach (var directory in Directory.GetDirectories(sourceDir)) 16 | DirectoryEx.Copy(directory, Path.Combine(targetDir, Path.GetFileName(directory)), overwrite); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /azure_status_page.core/Models/MeterCheckResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using azure_status_page.client.Models; 3 | 4 | namespace azure_status_page.core 5 | { 6 | public class MeterCheckResult : MeterInstance 7 | { 8 | public Boolean MeterCheckPassed { get; set; } 9 | public String MeterCheckAlarmMessage { get; set; } 10 | 11 | public MeterCheckResult(MeterInstance meterInstance, Boolean checkPassed) 12 | : this(meterInstance, checkPassed, String.Empty) 13 | { 14 | } 15 | 16 | 17 | public MeterCheckResult(MeterInstance meterInstance, Boolean checkPassed, String checkAlarmMessage) 18 | :base(meterInstance) 19 | { 20 | MeterCheckPassed = checkPassed; 21 | MeterCheckAlarmMessage = checkAlarmMessage; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /azure_status_page.core/Models/MeterDefinitionServerBased.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using azure_status_page.client.Models; 3 | 4 | namespace azure_status_page.core.Models 5 | { 6 | public class MeterDefinitionServerBased : MeterDefinition 7 | { 8 | public string MeterServerCheckType { get; set; } 9 | public Int32 MeterServerCheckInterval { get; set; } 10 | public string MeterServerCheckInformation { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /azure_status_page.core/Models/MeterStorageConfigurationModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace azure_status_page.core 3 | { 4 | public class MeterStorageConfigurationModel 5 | { 6 | // Storage Config 7 | public string StorageKey { get; set; } 8 | public string StorageSecret { get; set; } 9 | public string StorageTableName { get; set; } 10 | 11 | // Title, Brand, .... 12 | public string Title { get; set; } 13 | public string Brand { get; set; } 14 | public string FavIcon { get; set; } 15 | 16 | // PushOver Integration 17 | public Boolean PushOverEnabled { get; set; } 18 | public string PushOverToken { get; set; } 19 | public string PushOverUser { get; set; } 20 | 21 | // Check if config is valid 22 | public Boolean Valid { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /azure_status_page.core/Models/SiteReplacementParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace azure_status_page.core 5 | { 6 | public class SiteReplacementStatusParameters 7 | { 8 | public String StatusClass { get; set; } 9 | public String StatusMessage { get; set; } 10 | public String StatusUpdate { get; set; } 11 | } 12 | 13 | public class ServiceStatusParameters : SiteReplacementStatusParameters 14 | { 15 | public String ServiceName { get; set; } 16 | public Int32 ServiceOrder { get; set; } 17 | } 18 | 19 | public class SiteReplacementParameters 20 | { 21 | public String Title { get; set; } 22 | public String BrandIcon { get; set; } 23 | public String FavIcon { get; set; } 24 | public SiteReplacementStatusParameters Status { get; set; } = new SiteReplacementStatusParameters(); 25 | public List Services { get; set; } = new List(); 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /azure_status_page.core/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle("azure_status_page.core")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("")] 12 | [assembly: AssemblyCopyright("${AuthorCopyright}")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion("1.0.*")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | //[assembly: AssemblyDelaySign(false)] 26 | //[assembly: AssemblyKeyFile("")] 27 | -------------------------------------------------------------------------------- /azure_status_page.core/Repositories/AzureTableMeterManagerExRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using azure_status_page.client.Models; 4 | using azure_status_page.client.Repositories; 5 | using azure_status_page.core.Models; 6 | 7 | namespace azure_status_page.core.Repositories 8 | { 9 | public class AzureTableMeterManagerExRepository : AzureTableMeterManagerRepository, IMeterManagerExRepository 10 | { 11 | public AzureTableMeterManagerExRepository(string storageKey, string storageSecret, string instanceTableName) 12 | : base(storageKey, storageSecret, instanceTableName) 13 | { 14 | } 15 | 16 | public void CreateOrUpdateServerSideDefinition(MeterDefinitionServerBased definition) 17 | { 18 | // get the entity 19 | var entity = AzureTableMeterDefinitionServerBased.FromModel(definition); 20 | 21 | // merge 22 | InsertOrMerge(entity, "MeterDefinitionServerBased"); 23 | } 24 | 25 | public void DeleteSideDefinition(string MeterId, string MeterCheckType) 26 | { 27 | DeleteEntity("MeterDefinitionServerBased", MeterId, MeterCheckType); 28 | } 29 | 30 | public MeterDefinitionServerBased LoadServerBasedDefinition(string MeterId, string MeterCheckType) 31 | { 32 | var entity = LoadInstance("MeterDefinitionServerBased", MeterId, MeterCheckType); 33 | return entity.ToModel(); 34 | } 35 | 36 | public List LoadServerBasedDefinitions() 37 | { 38 | // query 39 | var queryResult = LoadInstances("MeterDefinitionServerBased"); 40 | 41 | // Print the fields for each customer. 42 | var result = new List(); 43 | foreach (AzureTableMeterDefinitionServerBased entity in queryResult) 44 | { 45 | result.Add(entity.ToModel()); 46 | } 47 | 48 | return result; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /azure_status_page.core/Repositories/Entities/AzureTableMeterDefinitionServerBased.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using azure_status_page.client.Models; 3 | using azure_status_page.core.Models; 4 | using Microsoft.WindowsAzure.Storage.Table; 5 | 6 | namespace azure_status_page.core 7 | { 8 | public class AzureTableMeterDefinitionServerBased : TableEntity 9 | { 10 | public String MeterId { get; set; } 11 | public String MeterName { get; set; } 12 | 13 | public String MeterCategory { get; set; } 14 | public Int32 MeterDisplayOrder { get; set; } 15 | public Double MeterType { get; set; } 16 | public Double MeterValue { get; set; } 17 | 18 | public string MeterServerCheckType { get; set; } 19 | public Int32 MeterServerCheckInterval { get; set; } 20 | public string MeterServerCheckInformation { get; set; } 21 | 22 | public AzureTableMeterDefinitionServerBased(string meterId, string meterServerCheckType) 23 | { 24 | this.PartitionKey = meterId; 25 | this.RowKey = meterServerCheckType; 26 | this.MeterId = meterId; 27 | this.MeterServerCheckType = meterServerCheckType; 28 | } 29 | 30 | public AzureTableMeterDefinitionServerBased() 31 | { } 32 | 33 | public MeterDefinitionServerBased ToModel() 34 | { 35 | return new MeterDefinitionServerBased() 36 | { 37 | MeterId = this.MeterId, 38 | MeterName = this.MeterName, 39 | MeterCategory = this.MeterCategory, 40 | MeterDisplayOrder = this.MeterDisplayOrder, 41 | MeterType = (nMeterTypes)Enum.ToObject(typeof(nMeterTypes), Convert.ToInt32(this.MeterType)), 42 | MeterValue = new Decimal(this.MeterValue), 43 | MeterServerCheckType = this.MeterServerCheckType, 44 | MeterServerCheckInterval = this.MeterServerCheckInterval, 45 | MeterServerCheckInformation = this.MeterServerCheckInformation 46 | }; 47 | } 48 | 49 | public static AzureTableMeterDefinitionServerBased FromModel(MeterDefinitionServerBased model) 50 | { 51 | return new AzureTableMeterDefinitionServerBased(model.MeterId, model.MeterServerCheckType) 52 | { 53 | MeterId = model.MeterId, 54 | MeterName = model.MeterName, 55 | MeterCategory = model.MeterCategory, 56 | MeterDisplayOrder = model.MeterDisplayOrder, 57 | MeterType = Convert.ToDouble(Convert.ToInt32(model.MeterType)), 58 | MeterValue = Convert.ToDouble(model.MeterValue), 59 | MeterServerCheckType = model.MeterServerCheckType, 60 | MeterServerCheckInterval = model.MeterServerCheckInterval, 61 | MeterServerCheckInformation = model.MeterServerCheckInformation 62 | }; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /azure_status_page.core/Services/AzureEnvironmentService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | 5 | namespace azure_status_page.core 6 | { 7 | public static class AzureEnvironmentService 8 | { 9 | public static string DataDirectory { get; private set; } 10 | public static string WebSiteLocation { get; private set; } 11 | public static string SiteExtensionLocation { get; private set; } 12 | 13 | public static void Initialize() 14 | { 15 | // fall back for local development environment (currently in my MAC) 16 | DataDirectory = "/tmp/StatusPage"; 17 | WebSiteLocation = "/tmp/WebSiteLocation"; 18 | SiteExtensionLocation = "/Users/dei79/Documents/Projects/azurecosts/azure_status_page/azure_status_page"; 19 | 20 | // get the correct path 21 | if (Environment.GetEnvironmentVariable("home") != null) 22 | { 23 | DataDirectory = Environment.ExpandEnvironmentVariables(@"%HOME%\data\StatusPage"); 24 | WebSiteLocation = Environment.ExpandEnvironmentVariables(@"%HOME%\site\wwwroot"); 25 | SiteExtensionLocation = Environment.ExpandEnvironmentVariables(@"%HOME%\SiteExtensions\statuspage"); 26 | } 27 | 28 | // check if DataDirectory exists 29 | if (!Directory.Exists(DataDirectory)) 30 | Directory.CreateDirectory(DataDirectory); 31 | 32 | // check if WebSiteLocation exists 33 | if (!Directory.Exists(WebSiteLocation)) 34 | Directory.CreateDirectory(WebSiteLocation); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /azure_status_page.core/Services/AzureWebJobInstaller.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace azure_status_page.core 5 | { 6 | public class AzureWebJobInstaller 7 | { 8 | private string WebJobName { get; set; } 9 | private string SrcDirectory { get; set; } 10 | private string WebJobDirectory { get; set; } 11 | 12 | public AzureWebJobInstaller(string webJobName, string srcDirectory, string webSiteRoot) 13 | { 14 | WebJobName = webJobName; 15 | SrcDirectory = srcDirectory; 16 | WebJobDirectory = Path.Combine(webSiteRoot, "App_Data", "jobs", "continuous", webJobName); 17 | } 18 | 19 | 20 | public Boolean IsWebJobInstalled() 21 | { 22 | return Directory.Exists(WebJobDirectory); 23 | } 24 | 25 | public void InstallOrUpdateWebJob() 26 | { 27 | // ensure the WebJobDirectory exists 28 | if (!Directory.Exists(WebJobDirectory)) 29 | Directory.CreateDirectory(WebJobDirectory); 30 | 31 | // Copy all files from the src directory to the WebJobDirectory 32 | DirectoryEx.Copy(SrcDirectory, WebJobDirectory, true); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /azure_status_page.core/Services/AzureWebJobShutdownService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace azure_status_page.core 5 | { 6 | public delegate void ShutdownEventHandler(object sender); 7 | 8 | public class AzureWebJobShutdownService : IDisposable 9 | { 10 | private FileSystemWatcher fileSystemWatcher { get; set; } 11 | private string shutdownFile { get; set; } 12 | private string peekFile { get; set; } 13 | 14 | public event ShutdownEventHandler OnShutdown; 15 | public event ShutdownEventHandler OnPeek; 16 | 17 | public AzureWebJobShutdownService() 18 | { 19 | // Get the shutdown file path from the environment 20 | shutdownFile = AzureWebJobShutdownService.GetShutdownFileName(); 21 | 22 | // get the peek file 23 | peekFile = AzureWebJobShutdownService.GetPeekFileName(); 24 | 25 | // Setup a file system watcher on that file's directory to know when the file is created 26 | fileSystemWatcher = new FileSystemWatcher(Path.GetDirectoryName(shutdownFile)); 27 | fileSystemWatcher.Created += OnChanged; 28 | fileSystemWatcher.Changed += OnChanged; 29 | fileSystemWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.FileName | NotifyFilters.LastWrite; 30 | fileSystemWatcher.IncludeSubdirectories = false; 31 | fileSystemWatcher.EnableRaisingEvents = true; 32 | } 33 | 34 | private static string GetShutdownFileName() 35 | { 36 | var triggerFileName = Environment.GetEnvironmentVariable("WEBJOBS_SHUTDOWN_FILE"); 37 | if (triggerFileName == null) { triggerFileName = "/tmp/stop.notify"; } 38 | return triggerFileName; 39 | } 40 | 41 | private static string GetPeekFileName() 42 | { 43 | var triggerFileName = "/tmp/peek.notify"; 44 | 45 | if (Environment.GetEnvironmentVariable("home") != null) 46 | triggerFileName = Environment.ExpandEnvironmentVariables(@"%HOME%\data\StatusPage\peek.notify"); 47 | 48 | return triggerFileName; 49 | } 50 | 51 | private void OnChanged(object sender, FileSystemEventArgs e) 52 | { 53 | if (e.FullPath.IndexOf(Path.GetFileName(shutdownFile), StringComparison.OrdinalIgnoreCase) >= 0) 54 | { 55 | if (OnShutdown != null) 56 | { 57 | OnShutdown(this); 58 | } 59 | } 60 | else if (e.FullPath.IndexOf(Path.GetFileName(peekFile), StringComparison.OrdinalIgnoreCase) >= 0) 61 | { 62 | if (OnPeek != null) 63 | { 64 | OnPeek(this); 65 | } 66 | } 67 | } 68 | 69 | public void Dispose() 70 | { 71 | fileSystemWatcher.EnableRaisingEvents = false; 72 | OnShutdown = null; 73 | } 74 | 75 | public static void Peek() 76 | { 77 | var peekFileName = AzureWebJobShutdownService.GetPeekFileName(); 78 | if (File.Exists(peekFileName)) 79 | File.Delete(peekFileName); 80 | 81 | File.Create(peekFileName).Dispose(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /azure_status_page.core/Services/CheckMeterProviders/EqualsValueCheckMeterProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using azure_status_page.client.Models; 3 | 4 | namespace azure_status_page.core 5 | { 6 | public class EqualsValueCheckMeterProvider : ICheckMeterProvider 7 | { 8 | public MeterCheckResult checkMeterInstance(MeterInstance instance) 9 | { 10 | return new MeterCheckResult(instance, (instance.MeterInstanceValue.Equals(instance.MeterValue))); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /azure_status_page.core/Services/CheckMeterProviders/HeartbeatCheckMeterProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using azure_status_page.client.Models; 3 | 4 | namespace azure_status_page.core 5 | { 6 | public class HeartbeatCheckMeterProvider : ICheckMeterProvider 7 | { 8 | public MeterCheckResult checkMeterInstance(MeterInstance instance) 9 | { 10 | var meterGap = DateTime.Now - instance.MeterInstanceTimestamp; 11 | if (meterGap.TotalSeconds > Convert.ToDouble(instance.MeterValue)) 12 | return new MeterCheckResult(instance, false, String.Format("Last Heartbeat {0} seconds ago", meterGap.TotalSeconds)); 13 | 14 | return new MeterCheckResult(instance, true); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /azure_status_page.core/Services/CheckMeterProviders/MaxValueCheckMeterProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using azure_status_page.client.Models; 3 | 4 | namespace azure_status_page.core 5 | { 6 | public class MaxValueCheckMeterProvider : ICheckMeterProvider 7 | { 8 | public MeterCheckResult checkMeterInstance(MeterInstance instance) 9 | { 10 | return new MeterCheckResult(instance, (instance.MeterInstanceValue < instance.MeterValue)); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /azure_status_page.core/Services/CheckMeterProviders/MinValueCheckMeterProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using azure_status_page.client.Models; 3 | 4 | namespace azure_status_page.core 5 | { 6 | public class MinValueCheckMeterProvider : ICheckMeterProvider 7 | { 8 | public MeterCheckResult checkMeterInstance(MeterInstance instance) 9 | { 10 | return new MeterCheckResult(instance, (instance.MeterInstanceValue > instance.MeterValue)); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /azure_status_page.core/Services/CheckMeterService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using azure_status_page.client; 4 | using azure_status_page.client.Contracts.Repositories; 5 | using azure_status_page.client.Models; 6 | using azure_status_page.client.Repositories; 7 | using azure_status_page.core.NotificationProviders; 8 | 9 | namespace azure_status_page.core 10 | { 11 | public class CheckMeterService 12 | { 13 | private IMeterInstanceRepository meterInstanceRepository { get; set; } 14 | private Dictionary checkMeterProviders { get; set; } 15 | private List notificationProviders { get; set; } = new List(); 16 | 17 | public CheckMeterService(MeterStorageConfigurationModel config) 18 | { 19 | meterInstanceRepository = new AzureTableMeterInstanceRepository(config.StorageKey, config.StorageSecret, config.StorageTableName); 20 | checkMeterProviders = new Dictionary(); 21 | checkMeterProviders.Add(nMeterTypes.Heartbeat, new HeartbeatCheckMeterProvider()); 22 | checkMeterProviders.Add(nMeterTypes.MinValue, new MinValueCheckMeterProvider()); 23 | checkMeterProviders.Add(nMeterTypes.MaxValue, new MaxValueCheckMeterProvider()); 24 | checkMeterProviders.Add(nMeterTypes.EqualsValue, new EqualsValueCheckMeterProvider()); 25 | 26 | // add the notification providers 27 | if (config.PushOverEnabled) 28 | notificationProviders.Add(new PushOverNotificationProvider(config.PushOverToken, config.PushOverUser)); 29 | } 30 | 31 | public List Check() 32 | { 33 | // get all instances 34 | var meterInstances = meterInstanceRepository.LoadInstances(); 35 | 36 | // check every instance 37 | var result = new List(); 38 | foreach(var meterInstance in meterInstances) 39 | { 40 | // check if we have a provider 41 | if (!checkMeterProviders.ContainsKey(meterInstance.MeterType)) 42 | { 43 | result.Add(new MeterCheckResult(meterInstance, false, "Failed to check the meter because of missing provider")); 44 | continue; 45 | } 46 | 47 | // lookup the check provider 48 | var checkProvider = checkMeterProviders[meterInstance.MeterType]; 49 | result.Add(checkProvider.checkMeterInstance(meterInstance)); 50 | } 51 | 52 | return result; 53 | } 54 | 55 | public void Notify(List checkResults) 56 | { 57 | foreach (var checkResult in checkResults) 58 | { 59 | if (checkResult.MeterCheckPassed) { continue; } 60 | 61 | foreach (var notificationProvider in notificationProviders) 62 | { 63 | notificationProvider.notify(checkResult); 64 | } 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /azure_status_page.core/Services/MeterManagerEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using azure_status_page.client.Contracts.Repositories; 5 | using azure_status_page.client.Models; 6 | using azure_status_page.core.Models; 7 | using azure_status_page.core.Repositories; 8 | 9 | namespace azure_status_page.core 10 | { 11 | public class MeterManagerEx 12 | { 13 | private IMeterManagerExRepository MeterManagerRepository { get; set; } 14 | 15 | public MeterManagerEx(IMeterManagerExRepository meterManagerRepository) 16 | { 17 | MeterManagerRepository = meterManagerRepository; 18 | } 19 | 20 | public MeterManagerEx(MeterStorageConfigurationModel config) 21 | { 22 | MeterManagerRepository = new AzureTableMeterManagerExRepository(config.StorageKey, config.StorageSecret, config.StorageTableName); 23 | } 24 | 25 | public List GetClientSideDefinitions() 26 | { 27 | // Load MeterInstances 28 | var meterInstances = MeterManagerRepository.LoadInstances(); 29 | 30 | // Aggregate all instances to the correct meterdefinitions 31 | var meterHash = new Dictionary(); 32 | var result = new List(); 33 | foreach (var meterInstacen in meterInstances) 34 | { 35 | if (meterInstacen.MeterInstanceId.StartsWith("Server.", StringComparison.CurrentCulture)) 36 | continue; 37 | 38 | if (!meterHash.ContainsKey(meterInstacen.MeterId)) 39 | { 40 | meterHash.Add(meterInstacen.MeterId, meterInstacen); 41 | result.Add(new MeterDefinition(meterInstacen)); 42 | } 43 | } 44 | 45 | // order 46 | result.Sort((x, y) => { return x.MeterDisplayOrder.CompareTo(y.MeterDisplayOrder); }); 47 | 48 | // done 49 | return result; 50 | } 51 | 52 | public List GetServerBasedDefinitions() 53 | { 54 | return MeterManagerRepository.LoadServerBasedDefinitions(); 55 | } 56 | 57 | public MeterDefinitionServerBased GetServerBasedDefinition(string MeterId, string MeterCheckType) 58 | { 59 | return MeterManagerRepository.LoadServerBasedDefinition(MeterId, MeterCheckType); 60 | } 61 | 62 | public void CreateOrUpdateServerSideDefinition(MeterDefinitionServerBased definition) 63 | { 64 | MeterManagerRepository.CreateOrUpdateServerSideDefinition(definition); 65 | } 66 | 67 | public void DeleteServerSideDefinition(string MeterId, string MeterCheckType) 68 | { 69 | // delete the instance 70 | MeterManagerRepository.DeleteInstance(MeterId, "Server." + MeterId + ".WebCheck"); 71 | MeterManagerRepository.DeleteInstance(MeterId, "Server." + MeterId + ".HeartBeat"); 72 | 73 | // delete the server side definition 74 | MeterManagerRepository.DeleteSideDefinition(MeterId, MeterCheckType); 75 | } 76 | 77 | public void ExecuteServerSideChecks() 78 | { 79 | // load the meter definitions 80 | var definitions = MeterManagerRepository.LoadServerBasedDefinitions(); 81 | 82 | // execute the check 83 | foreach (var definition in definitions) 84 | { 85 | // generate the instance for the heartbeat 86 | var meterInstance = new MeterInstance(definition); 87 | meterInstance.MeterValue = definition.MeterServerCheckInterval; 88 | meterInstance.MeterInstanceId = "Server." + definition.MeterId + ".HeartBeat"; 89 | meterInstance.MeterInstanceTimestamp = DateTime.Now; 90 | MeterManagerRepository.StoreInstance(meterInstance); 91 | 92 | // generate an instance for the http return value 93 | var meterInstanceHttpResult = new MeterInstance(definition); 94 | meterInstanceHttpResult.MeterType = nMeterTypes.EqualsValue; 95 | meterInstanceHttpResult.MeterValue = definition.MeterValue; 96 | meterInstanceHttpResult.MeterInstanceId = "Server." + definition.MeterId + ".WebCheck"; 97 | meterInstanceHttpResult.MeterInstanceTimestamp = DateTime.Now; 98 | 99 | // check 100 | try 101 | { 102 | var request = WebRequest.CreateHttp(definition.MeterServerCheckInformation); 103 | request.Method = "HEAD"; 104 | request.Timeout = 1000; 105 | var response = request.GetResponse() as HttpWebResponse; 106 | meterInstanceHttpResult.MeterInstanceValue = Convert.ToInt32(response.StatusCode); 107 | } 108 | catch (Exception e) 109 | { 110 | if (e is WebException && (((WebException)e).Response as HttpWebResponse) != null) 111 | { 112 | meterInstanceHttpResult.MeterInstanceValue = Convert.ToInt32(((e as WebException).Response as HttpWebResponse).StatusCode); 113 | } 114 | else 115 | { 116 | meterInstanceHttpResult.MeterInstanceValue = -1; 117 | } 118 | } 119 | 120 | // Logging 121 | Console.WriteLine("Checkking URL: " + definition.MeterServerCheckInformation + " - Result: " + meterInstanceHttpResult.MeterInstanceValue); 122 | 123 | // generate instances per check 124 | MeterManagerRepository.StoreInstance(meterInstanceHttpResult); 125 | } 126 | 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /azure_status_page.core/Services/NotificationProviders/PushOverNotificationProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PushoverClient; 3 | 4 | namespace azure_status_page.core.NotificationProviders 5 | { 6 | public class PushOverNotificationProvider : INotificationProvider 7 | { 8 | private string Token { get; set; } 9 | private string User { get; set; } 10 | 11 | public PushOverNotificationProvider(string token, string user) 12 | { 13 | Token = token; 14 | User = user; 15 | } 16 | 17 | public void notify(MeterCheckResult checkResult) 18 | { 19 | var pushClient = new Pushover(Token); 20 | 21 | var subject = String.Format("Meter Check {0} on {1} failed", checkResult.MeterName, checkResult.MeterInstanceId); 22 | 23 | pushClient.Push(subject, checkResult.MeterCheckAlarmMessage, User); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /azure_status_page.core/Services/SiteExtensionConfigurationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Newtonsoft.Json; 4 | 5 | namespace azure_status_page.core 6 | { 7 | public class SiteExtensionConfigurationService 8 | { 9 | private string GetSettingsFileName() 10 | { 11 | return Path.Combine(AzureEnvironmentService.DataDirectory, "settings.json"); 12 | } 13 | 14 | public Boolean VerifyAndStoreConfiguration(MeterStorageConfigurationModel configuration) 15 | { 16 | // Verify the config 17 | configuration.Valid = true; 18 | 19 | // build the file name 20 | var settingsFileName = GetSettingsFileName(); 21 | 22 | // serialize JSON directly to a file 23 | using(StreamWriter file = File.CreateText(settingsFileName)) 24 | { 25 | JsonSerializer serializer = new JsonSerializer(); 26 | serializer.Serialize(file, configuration); 27 | } 28 | 29 | // done 30 | return true; 31 | } 32 | 33 | public MeterStorageConfigurationModel LoadConfiguration() 34 | { 35 | // build the file name 36 | var settingsFileName = GetSettingsFileName(); 37 | 38 | // check if file exists 39 | if (File.Exists(settingsFileName)) 40 | { 41 | // deserialize JSON 42 | using (StreamReader file = File.OpenText(settingsFileName)) 43 | { 44 | JsonSerializer serializer = new JsonSerializer(); 45 | return (MeterStorageConfigurationModel)serializer.Deserialize(file, typeof(MeterStorageConfigurationModel)); 46 | } 47 | } 48 | else 49 | { 50 | return new MeterStorageConfigurationModel() { StorageTableName = "MeterInformation", Valid = false }; 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /azure_status_page.core/Services/StatusPageGeneratorService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace azure_status_page.core 6 | { 7 | public class StatusPageGeneratorService 8 | { 9 | private string TargetLocation { get; set; } 10 | private string Template { get; set; } 11 | 12 | public StatusPageGeneratorService(string targetLocation, string template) 13 | { 14 | TargetLocation = targetLocation; 15 | Template = template; 16 | } 17 | 18 | public void UpdateSite(string title, string brandIcon, string favIcon, List meterCheckResults) 19 | { 20 | // generate the basic replacements 21 | var replacements = new SiteReplacementParameters(); 22 | replacements.Title = title; 23 | replacements.BrandIcon = brandIcon; 24 | replacements.FavIcon = favIcon; 25 | 26 | // define our standard overall status 27 | replacements.Status.StatusClass = "alert-success"; 28 | replacements.Status.StatusMessage = "All Systems Operational"; 29 | replacements.Status.StatusUpdate = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"); 30 | 31 | // build the services and get the overall status 32 | var lookUpTable = new Dictionary(); 33 | foreach (var meterCheckResult in meterCheckResults) 34 | { 35 | try 36 | { 37 | // adapt the meter category 38 | if (meterCheckResult.MeterCategory == null) 39 | meterCheckResult.MeterCategory = meterCheckResult.MeterName; 40 | 41 | // check if we have a status for this category 42 | if (!lookUpTable.ContainsKey(meterCheckResult.MeterCategory)) 43 | { 44 | var statTemp = new ServiceStatusParameters() { ServiceName = meterCheckResult.MeterCategory, StatusClass = "success", StatusMessage = "Operational", ServiceOrder = meterCheckResult.MeterDisplayOrder }; 45 | replacements.Services.Add(statTemp); 46 | lookUpTable.Add(meterCheckResult.MeterCategory, statTemp); 47 | } 48 | 49 | var stat = lookUpTable[meterCheckResult.MeterCategory]; 50 | if (!meterCheckResult.MeterCheckPassed) 51 | { 52 | stat.StatusClass = "broken"; 53 | stat.StatusMessage = "Service Degradation"; 54 | 55 | // update overall status 56 | replacements.Status.StatusClass = "alert-warning"; 57 | replacements.Status.StatusMessage = "Minor service outage"; 58 | 59 | }; 60 | } 61 | catch (Exception e) 62 | { 63 | Console.WriteLine("Ignoring meter " + meterCheckResult.MeterInstanceId + ": " + e.Message); 64 | } 65 | } 66 | 67 | // order by display order 68 | replacements.Services.Sort((ServiceStatusParameters x, ServiceStatusParameters y) => { 69 | return x.ServiceOrder.CompareTo(y.ServiceOrder); 70 | }); 71 | 72 | // Write the file 73 | using (TextWriter pageWriter = new StreamWriter(TargetLocation)) 74 | { 75 | using (var templateReader = new StreamReader(Template)) 76 | { 77 | var compiledTemplate = HandlebarsDotNet.Handlebars.Compile(templateReader); 78 | compiledTemplate(pageWriter, replacements); 79 | } 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /azure_status_page.core/azure_status_page.core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {31878B8D-7325-47B0-BF2B-1809B72E63EC} 7 | Library 8 | azure_status_page.core 9 | azure_status_page.core 10 | v4.5 11 | 0.0.2 12 | 13 | 14 | true 15 | full 16 | false 17 | bin\Debug 18 | DEBUG; 19 | prompt 20 | 4 21 | false 22 | 23 | 24 | true 25 | bin\Release 26 | prompt 27 | 4 28 | false 29 | 30 | 31 | 32 | 33 | ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll 34 | 35 | 36 | 37 | ..\packages\Handlebars.Net.1.8.0\lib\portable-net45+sl5+wp8+win8\Handlebars.dll 38 | 39 | 40 | ..\packages\Microsoft.Azure.KeyVault.Core.1.0.0\lib\net40\Microsoft.Azure.KeyVault.Core.dll 41 | 42 | 43 | ..\packages\Microsoft.Data.Edm.5.8.2\lib\net40\Microsoft.Data.Edm.dll 44 | 45 | 46 | ..\packages\System.Spatial.5.8.2\lib\net40\System.Spatial.dll 47 | 48 | 49 | ..\packages\Microsoft.Data.OData.5.8.2\lib\net40\Microsoft.Data.OData.dll 50 | 51 | 52 | ..\packages\Microsoft.Data.Services.Client.5.8.2\lib\net40\Microsoft.Data.Services.Client.dll 53 | 54 | 55 | ..\packages\WindowsAzure.Storage.8.1.0\lib\net45\Microsoft.WindowsAzure.Storage.dll 56 | 57 | 58 | 59 | ..\packages\ServiceStack.Text.3.9.59\lib\net35\ServiceStack.Text.dll 60 | 61 | 62 | ..\packages\PushoverNET.1.0.11\lib\net40\PushoverClient.dll 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | {EBD3A11E-FD34-4BF9-8768-BE5A613F7AE7} 108 | azure_status_page.client 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /azure_status_page.core/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /azure_status_page.jobs/Assets/default-template.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | {{Title}} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 63 | 64 | 65 | 66 | 74 | 75 |
76 | 84 |
85 | 86 |
87 |
88 | 89 |
    90 | 91 | {{#Services}} 92 |
  • 93 | 94 | {{ServiceName}} 95 | 96 | 97 | {{StatusMessage}} 98 | 99 |
  • 100 | {{/Services}} 101 | 102 |
103 |
104 |
105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 127 | 128 | -------------------------------------------------------------------------------- /azure_status_page.jobs/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using System.Threading; 5 | using azure_status_page.core; 6 | using azure_status_page.core.Repositories; 7 | 8 | namespace azure_status_page.jobs 9 | { 10 | class MainClass 11 | { 12 | public static void Main(string[] args) 13 | { 14 | // initialize the environment 15 | AzureEnvironmentService.Initialize(); 16 | 17 | using (var shutdownService = new AzureWebJobShutdownService()) 18 | { 19 | // generate the waithandle we are using for the innermost loop 20 | ManualResetEvent timeoutHandle = new ManualResetEvent(false); 21 | bool bShutdown = false; 22 | 23 | // configure our shutdown handler 24 | shutdownService.OnShutdown += (sender) => { 25 | Console.WriteLine("Detected Shutdown, interupting the loop"); 26 | bShutdown = true; 27 | timeoutHandle.Set(); 28 | }; 29 | 30 | shutdownService.OnPeek += (sender) => { 31 | Console.WriteLine("Detected Peek, forcing a loop"); 32 | timeoutHandle.Set(); 33 | }; 34 | 35 | // start the inner most loop 36 | do 37 | { 38 | // reset our event 39 | timeoutHandle.Reset(); 40 | 41 | // load the config 42 | Console.WriteLine("Loading the Azure WebJob Configuration..."); 43 | var config = (new SiteExtensionConfigurationService()).LoadConfiguration(); 44 | 45 | // executing the server side checks 46 | Console.WriteLine("Executing Server Side Checks"); 47 | var meterManagerEx = new MeterManagerEx(config); 48 | meterManagerEx.ExecuteServerSideChecks(); 49 | 50 | // check the meters 51 | Console.WriteLine("Checking the different Meters"); 52 | var checkMeterService = new CheckMeterService(config); 53 | var results = checkMeterService.Check(); 54 | 55 | // notify if needed 56 | Console.WriteLine("Notify failed Meters"); 57 | checkMeterService.Notify(results); 58 | 59 | // get the path of the job 60 | var jobAssetPath = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Assets"); 61 | var jobSiteTemplate = Path.Combine(jobAssetPath, "default-template.html"); 62 | 63 | // update the static status page 64 | Console.WriteLine("Updating Status Page"); 65 | var siteGenerator = new StatusPageGeneratorService(Path.Combine(AzureEnvironmentService.WebSiteLocation, "index.html"), jobSiteTemplate); 66 | siteGenerator.UpdateSite(config.Title, config.Brand, config.FavIcon, results); 67 | 68 | // done 69 | Console.WriteLine("Waiting for the next cycle (5mins)"); 70 | 71 | } while (!timeoutHandle.WaitOne(5 * 60 * 1000) || !bShutdown); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /azure_status_page.jobs/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle("azure_status_page.jobs")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("")] 12 | [assembly: AssemblyCopyright("${AuthorCopyright}")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion("1.0.*")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | //[assembly: AssemblyDelaySign(false)] 26 | //[assembly: AssemblyKeyFile("")] 27 | -------------------------------------------------------------------------------- /azure_status_page.jobs/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /azure_status_page.jobs/azure_status_page.jobs.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {FF395F21-BE10-4C4F-A27C-238AD6697C5E} 7 | Exe 8 | azure_status_page.jobs 9 | run 10 | v4.5 11 | 0.0.2 12 | 13 | 14 | true 15 | full 16 | false 17 | ..\azure_status_page\App_Data\WebJob 18 | DEBUG; 19 | prompt 20 | 4 21 | true 22 | 23 | 24 | true 25 | ..\azure_status_page\App_Data\WebJob 26 | prompt 27 | 4 28 | true 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {31878B8D-7325-47B0-BF2B-1809B72E63EC} 41 | azure_status_page.core 42 | 43 | 44 | {EBD3A11E-FD34-4BF9-8768-BE5A613F7AE7} 45 | azure_status_page.client 46 | 47 | 48 | 49 | 50 | 51 | PreserveNewest 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /azure_status_page.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "azure_status_page", "azure_status_page\azure_status_page.csproj", "{25464043-8444-49E0-8EDE-566B1409D7DF}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "azure_status_page.core", "azure_status_page.core\azure_status_page.core.csproj", "{31878B8D-7325-47B0-BF2B-1809B72E63EC}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "azure_status_page.jobs", "azure_status_page.jobs\azure_status_page.jobs.csproj", "{FF395F21-BE10-4C4F-A27C-238AD6697C5E}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "azure_status_page.client", "azure_status_page.client\azure_status_page.client.csproj", "{EBD3A11E-FD34-4BF9-8768-BE5A613F7AE7}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "azure_status_page.client.demo", "azure_status_page.client.demo\azure_status_page.client.demo.csproj", "{B35B4ED1-AD7C-4E44-956C-6E508E68711E}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {25464043-8444-49E0-8EDE-566B1409D7DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {25464043-8444-49E0-8EDE-566B1409D7DF}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {25464043-8444-49E0-8EDE-566B1409D7DF}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {25464043-8444-49E0-8EDE-566B1409D7DF}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {31878B8D-7325-47B0-BF2B-1809B72E63EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {31878B8D-7325-47B0-BF2B-1809B72E63EC}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {31878B8D-7325-47B0-BF2B-1809B72E63EC}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {31878B8D-7325-47B0-BF2B-1809B72E63EC}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {FF395F21-BE10-4C4F-A27C-238AD6697C5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {FF395F21-BE10-4C4F-A27C-238AD6697C5E}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {FF395F21-BE10-4C4F-A27C-238AD6697C5E}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {FF395F21-BE10-4C4F-A27C-238AD6697C5E}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {EBD3A11E-FD34-4BF9-8768-BE5A613F7AE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {EBD3A11E-FD34-4BF9-8768-BE5A613F7AE7}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {EBD3A11E-FD34-4BF9-8768-BE5A613F7AE7}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {EBD3A11E-FD34-4BF9-8768-BE5A613F7AE7}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {B35B4ED1-AD7C-4E44-956C-6E508E68711E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {B35B4ED1-AD7C-4E44-956C-6E508E68711E}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {B35B4ED1-AD7C-4E44-956C-6E508E68711E}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {B35B4ED1-AD7C-4E44-956C-6E508E68711E}.Release|Any CPU.Build.0 = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(MonoDevelopProperties) = preSolution 42 | version = 0.0.2 43 | EndGlobalSection 44 | EndGlobal 45 | -------------------------------------------------------------------------------- /azure_status_page/App_Start/RouteConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | using System.Web.Routing; 3 | 4 | namespace azure_status_page 5 | { 6 | public class RouteConfig 7 | { 8 | public static void RegisterRoutes(RouteCollection routes) 9 | { 10 | routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 11 | 12 | routes.MapRoute( 13 | name: "Default", 14 | url: "{controller}/{action}/{id}", 15 | defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 16 | ); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /azure_status_page/App_Start/WebApiConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Http; 2 | 3 | namespace azure_status_page 4 | { 5 | public static class WebApiConfig 6 | { 7 | public static void Register(HttpConfiguration config) 8 | { 9 | // Web API configuration and services 10 | 11 | // Web API routes 12 | config.MapHttpAttributeRoutes(); 13 | 14 | config.Routes.MapHttpRoute( 15 | name: "DefaultApi", 16 | routeTemplate: "api/{controller}/{id}", 17 | defaults: new { id = RouteParameter.Optional } 18 | ); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /azure_status_page/Content/bs-callout.css: -------------------------------------------------------------------------------- 1 |  2 | /* Side notes for calling out things 3 | -------------------------------------------------- */ 4 | 5 | /* Base styles (regardless of theme) */ 6 | .bs-callout { 7 | margin: 20px 0; 8 | padding: 15px 30px 15px 15px; 9 | border-left: 5px solid #eee; 10 | } 11 | .bs-callout h4 { 12 | margin-top: 0; 13 | } 14 | .bs-callout p:last-child { 15 | margin-bottom: 0; 16 | } 17 | .bs-callout code, 18 | .bs-callout .highlight { 19 | background-color: #fff; 20 | } 21 | 22 | /* Themes for different contexts */ 23 | .bs-callout-danger { 24 | background-color: #fcf2f2; 25 | border-color: #dFb5b4; 26 | } 27 | .bs-callout-warning { 28 | background-color: #fefbed; 29 | border-color: #f1e7bc; 30 | } 31 | .bs-callout-info { 32 | background-color: #f0f7fd; 33 | border-color: #d0e3f0; 34 | } -------------------------------------------------------------------------------- /azure_status_page/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Web; 6 | using System.Web.Mvc; 7 | using System.Web.Mvc.Ajax; 8 | using azure_status_page.core; 9 | 10 | namespace azure_status_page.Controllers 11 | { 12 | public class HomeController : Controller 13 | { 14 | private SiteExtensionConfigurationService configService = new SiteExtensionConfigurationService(); 15 | private AzureWebJobInstaller webJobInstaller = new AzureWebJobInstaller("CheckMetersAndUpdateSite", Path.Combine(AzureEnvironmentService.SiteExtensionLocation, "App_Data", "WebJob"), AzureEnvironmentService.WebSiteLocation); 16 | 17 | private void PrepareViewBag() 18 | { 19 | // check if the webjob is installed 20 | ViewBag.WebJobInstalled = webJobInstaller.IsWebJobInstalled(); 21 | } 22 | 23 | [HttpGet] 24 | public ActionResult Index() 25 | { 26 | // load the configuration 27 | var config = configService.LoadConfiguration(); 28 | 29 | // prepare our default view bag 30 | PrepareViewBag(); 31 | 32 | // render the config 33 | return View(config); 34 | } 35 | 36 | [HttpPost] 37 | public ActionResult Index(MeterStorageConfigurationModel config) 38 | { 39 | // store the config 40 | configService.VerifyAndStoreConfiguration(config); 41 | 42 | // prepare our default view bag 43 | PrepareViewBag(); 44 | 45 | // render the config 46 | return View(config); 47 | } 48 | 49 | [HttpPost] 50 | public ActionResult Install() 51 | { 52 | // install the webjob 53 | webJobInstaller.InstallOrUpdateWebJob(); 54 | 55 | // render the config 56 | return RedirectToAction("Index"); 57 | } 58 | 59 | [HttpPost] 60 | public ActionResult Generate() 61 | { 62 | // Trigger 63 | AzureWebJobShutdownService.Peek(); 64 | 65 | // prepare our default view bag 66 | PrepareViewBag(); 67 | 68 | // render the config 69 | return RedirectToAction("Index"); 70 | } 71 | 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /azure_status_page/Controllers/MetersController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | using azure_status_page.core; 7 | using azure_status_page.core.Models; 8 | using azure_status_page.core.Repositories; 9 | using azure_status_page.ViewModels; 10 | 11 | namespace azure_status_page.Controllers 12 | { 13 | public class MetersController : Controller 14 | { 15 | private SiteExtensionConfigurationService configService = new SiteExtensionConfigurationService(); 16 | 17 | private MeterManagerEx initializeMeterManager() 18 | { 19 | // try to load the configruation 20 | var config = configService.LoadConfiguration(); 21 | 22 | // generate the repository 23 | var repository = new AzureTableMeterManagerExRepository(config.StorageKey, config.StorageSecret, config.StorageTableName); 24 | 25 | // instantiate the meter manager 26 | return new MeterManagerEx(repository); 27 | } 28 | 29 | [HttpGet] 30 | public ActionResult Index() 31 | { 32 | // instantiate the meter manager 33 | var meterManagerEx = initializeMeterManager(); 34 | 35 | // build the view model 36 | var viewModel = new MetersViewModel(); 37 | 38 | // get all the MeterDefinitions for the client side meters 39 | viewModel.ClientSideMeters = meterManagerEx.GetClientSideDefinitions(); 40 | 41 | // get all Server Based MeterDefinitions 42 | viewModel.StatusPageMeters = meterManagerEx.GetServerBasedDefinitions(); 43 | 44 | // render the view 45 | return View (viewModel); 46 | } 47 | 48 | [HttpGet] 49 | public ActionResult Add() 50 | { 51 | // create the model 52 | var meterDefinition = new MeterDefinitionServerBased() 53 | { 54 | MeterId = Guid.NewGuid().ToString(), 55 | MeterType = client.Models.nMeterTypes.Heartbeat, 56 | MeterServerCheckType = "HTTP", 57 | MeterServerCheckInformation = String.Empty 58 | }; 59 | 60 | return View("Add", meterDefinition); 61 | } 62 | 63 | [HttpPost] 64 | public ActionResult Add(MeterDefinitionServerBased model) 65 | { 66 | // instantiate the meter manager 67 | var meterManagerEx = initializeMeterManager(); 68 | 69 | // store the meterdefinition to the settings 70 | meterManagerEx.CreateOrUpdateServerSideDefinition(model); 71 | 72 | // trigger the webjib 73 | AzureWebJobShutdownService.Peek(); 74 | 75 | // Render the meters 76 | return RedirectToAction("Index"); 77 | } 78 | 79 | [HttpGet] 80 | public ActionResult Edit(String Id, String CheckType) 81 | { 82 | // instantiate the meter manager 83 | var meterManagerEx = initializeMeterManager(); 84 | var meterDefinition = meterManagerEx.GetServerBasedDefinition(Id, CheckType); 85 | 86 | // show the dialog 87 | return View("Add", meterDefinition); 88 | } 89 | 90 | [HttpGet] 91 | public ActionResult Delete(String Id, String CheckType) 92 | { 93 | // instantiate the meter manager 94 | var meterManagerEx = initializeMeterManager(); 95 | meterManagerEx.DeleteServerSideDefinition(Id, CheckType); 96 | 97 | // Render the meters 98 | return RedirectToAction("Index"); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /azure_status_page/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Inherits="azure_status_page.Global" %> 2 | -------------------------------------------------------------------------------- /azure_status_page/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Mvc; 3 | using System.Web.Routing; 4 | using System.Web.Http; 5 | using azure_status_page.core; 6 | 7 | namespace azure_status_page 8 | { 9 | public class Global : HttpApplication 10 | { 11 | protected void Application_Start() 12 | { 13 | AreaRegistration.RegisterAllAreas(); 14 | GlobalConfiguration.Configure(WebApiConfig.Register); 15 | RouteConfig.RegisterRoutes(RouteTable.Routes); 16 | AzureEnvironmentService.Initialize(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /azure_status_page/ViewModels/MetersViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using azure_status_page.client.Models; 4 | using azure_status_page.core.Models; 5 | 6 | namespace azure_status_page.ViewModels 7 | { 8 | public class MetersViewModel 9 | { 10 | public List ClientSideMeters { get; set; } = new List(); 11 | 12 | public List StatusPageMeters { get; set; } = new List(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /azure_status_page/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model azure_status_page.core.MeterStorageConfigurationModel 2 | 3 | @{ 4 | ViewBag.Title = "Azure Status Page Generator - Configuration"; 5 | } 6 | 7 | 10 | 11 | @if (!Model.Valid) { 12 | 16 | } 17 | 18 | @if (!ViewBag.WebJobInstalled && Model.Valid) { 19 | 27 | 28 | } 29 | 30 | @using(Html.BeginForm("Index", "Home", FormMethod.Post, new { @class="form-horizontal", role="form" })) { 31 | 32 |
33 | Style & Theming 34 | 35 |
36 | @Html.LabelFor(m => m.Title, new { @class = "col-sm-2 control-label" }) 37 |
38 | @Html.ValidationMessageFor(m => m.Title) 39 | @Html.EditorFor(m => m.Title, new { htmlAttributes = new { @class = "form-control" } }) 40 |
41 |
42 | 43 |
44 | @Html.LabelFor(m => m.Brand, new { @class = "col-sm-2 control-label" }) 45 |
46 | @Html.ValidationMessageFor(m => m.Brand) 47 | @Html.EditorFor(m => m.Brand, new { htmlAttributes = new { @class = "form-control" } }) 48 |
49 |
50 | 51 |
52 | @Html.LabelFor(m => m.FavIcon, new { @class = "col-sm-2 control-label" }) 53 |
54 | @Html.ValidationMessageFor(m => m.FavIcon) 55 | @Html.EditorFor(m => m.FavIcon, new { htmlAttributes = new { @class = "form-control" } }) 56 |
57 |
58 | 59 |
60 |
61 | Storage Account Information 62 | 63 |
64 | @Html.LabelFor(m => m.StorageKey, new { @class = "col-sm-2 control-label" }) 65 |
66 | @Html.ValidationMessageFor(m => m.StorageKey) 67 | @Html.EditorFor(m => m.StorageKey, new { htmlAttributes = new { @class = "form-control" } }) 68 |
69 |
70 | 71 |
72 | @Html.LabelFor(m => m.StorageSecret, new { @class = "col-sm-2 control-label" }) 73 |
74 | @Html.ValidationMessageFor(m => m.StorageSecret) 75 | @Html.EditorFor(m => m.StorageSecret, new { htmlAttributes = new { @class = "form-control" } }) 76 |
77 |
78 | 79 |
80 | @Html.LabelFor(m => m.StorageTableName, new { @class = "col-sm-2 control-label" }) 81 |
82 | @Html.ValidationMessageFor(m => m.StorageTableName) 83 | @Html.EditorFor(m => m.StorageTableName, new { htmlAttributes = new { @class = "form-control" } }) 84 |
85 |
86 | 87 |
88 | 89 |
90 | PushOver Integration 91 | 92 | 93 |
94 | @Html.LabelFor(m => m.PushOverEnabled, new { @class = "col-sm-2 control-label" }) 95 |
96 | @Html.CheckBoxFor(m => m.PushOverEnabled, new { @checked = "checked", @class = "checkbox" }) 97 |
98 |
99 | 100 |
101 | @Html.LabelFor(m => m.PushOverToken, new { @class = "col-sm-2 control-label" }) 102 |
103 | @Html.ValidationMessageFor(m => m.PushOverToken) 104 | @Html.EditorFor(m => m.PushOverToken, new { htmlAttributes = new { @class = "form-control" } }) 105 |
106 |
107 | 108 |
109 | @Html.LabelFor(m => m.PushOverUser, new { @class = "col-sm-2 control-label" }) 110 |
111 | @Html.ValidationMessageFor(m => m.PushOverUser) 112 | @Html.EditorFor(m => m.PushOverUser, new { htmlAttributes = new { @class = "form-control" } }) 113 |
114 |
115 |
116 | 117 |
118 |
119 | 122 |
123 |
124 | } 125 | 126 | @section ActionsBar { 127 | 147 | } -------------------------------------------------------------------------------- /azure_status_page/Views/Meters/Add.cshtml: -------------------------------------------------------------------------------- 1 | @model azure_status_page.core.Models.MeterDefinitionServerBased 2 | 3 | @{ 4 | ViewBag.Title = "Add Meter"; 5 | } 6 | 7 | 8 | @using(Html.BeginForm("Add", "Meters", FormMethod.Post, new { @class="form-horizontal", role="form" })) { 9 |

Add/Edit Status Page Meter

10 | 11 |
12 | General 13 | 14 |
15 | @Html.LabelFor(m => m.MeterId, new { @class = "col-sm-2 control-label" }) 16 |
17 | @Html.ValidationMessageFor(m => m.MeterId) 18 | @Html.EditorFor(m => m.MeterId, new { htmlAttributes = new { @class = "form-control", @readonly="readonly" } }) 19 |
20 |
21 | 22 |
23 | @Html.LabelFor(m => m.MeterName, new { @class = "col-sm-2 control-label" }) 24 |
25 | @Html.ValidationMessageFor(m => m.MeterName) 26 | @Html.EditorFor(m => m.MeterName, new { htmlAttributes = new { @class = "form-control" } }) 27 |
28 |
29 | 30 |
31 | @Html.LabelFor(m => m.MeterCategory, new { @class = "col-sm-2 control-label" }) 32 |
33 | @Html.ValidationMessageFor(m => m.MeterCategory) 34 | @Html.EditorFor(m => m.MeterCategory, new { htmlAttributes = new { @class = "form-control" } }) 35 |
36 |
37 | 38 |
39 | @Html.LabelFor(m => m.MeterDisplayOrder, new { @class = "col-sm-2 control-label" }) 40 |
41 | @Html.ValidationMessageFor(m => m.MeterDisplayOrder) 42 | @Html.EditorFor(m => m.MeterDisplayOrder, new { htmlAttributes = new { @class = "form-control" } }) 43 |
44 |
45 | 46 |
47 | 48 |
49 | URL to check 50 | 51 |
52 | @Html.LabelFor(m => m.MeterServerCheckInformation, new { @class = "col-sm-2 control-label" }) 53 |
54 | @Html.ValidationMessageFor(m => m.MeterServerCheckInformation) 55 | @Html.EditorFor(m => m.MeterServerCheckInformation, new { htmlAttributes = new { @class = "form-control" } }) 56 |
57 |
58 | 59 |
60 | @Html.LabelFor(m => m.MeterValue, new { @class = "col-sm-2 control-label" }) 61 |
62 | @Html.ValidationMessageFor(m => m.MeterValue) 63 | @Html.EditorFor(m => m.MeterValue, new { htmlAttributes = new { @class = "form-control" } }) 64 |
65 |
66 | 67 |
68 | @Html.LabelFor(m => m.MeterServerCheckInterval, new { @class = "col-sm-2 control-label" }) 69 |
70 | @Html.ValidationMessageFor(m => m.MeterServerCheckInterval) 71 | @Html.EditorFor(m => m.MeterServerCheckInterval, new { htmlAttributes = new { @class = "form-control" } }) 72 |
73 |
74 | 75 |
76 | 77 | @Html.HiddenFor(m => m.MeterId, new { id = "MeterId" }) 78 | @Html.HiddenFor(m => m.MeterType, new { id = "MeterType" }) 79 | @Html.HiddenFor(m => m.MeterServerCheckType, new { id = "MeterServerCheckType" }) 80 | 81 |
82 |
83 | 86 |
87 |
88 | } -------------------------------------------------------------------------------- /azure_status_page/Views/Meters/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model azure_status_page.ViewModels.MetersViewModel 2 | 3 | @{ 4 | ViewBag.Title = "Meters"; 5 | } 6 | 7 | Client Side Meters 8 | @if (Model.ClientSideMeters.Count == 0) { 9 |
10 |

No Meters are defined or no client posted data

11 |

12 | Client side Meters are information delivered from the services as self, e.g. a heartbeat or some complex 13 | technical checks from time to time. This meters will be shown automatically in the status page. To integrate 14 | client side meters check our Status Page SDKs here: 15 |

16 | 20 |

As soon your client is posting data the first time client side meters will be visible here!

21 |
22 | } else { 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | @foreach(var item in Model.ClientSideMeters) 32 | { 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | } 41 |
MeterIdNameCategoryTypeValue
@item.MeterId@item.MeterName@item.MeterCategory@item.MeterType@item.MeterValue
42 | } 43 | 44 | Status Page Meters (Server Based Meters) 45 |
46 |

Register Meters manually

47 |

48 | Status Page Meters are executed in the App Service the Status Page is hosted and can be used to check specific 49 | metrics from outside of the applicaton, e.g. the availability of a specific application 50 |

51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | @foreach(var item in Model.StatusPageMeters) 62 | { 63 | 64 | 65 | 66 | 67 | 68 | 69 | 73 | 74 | } 75 |
MeterIdNameCategoryTypeValue Add
@item.MeterId@item.MeterName@item.MeterCategory@item.MeterType / @item.MeterServerCheckType@item.MeterValue 70 | 71 | 72 |
-------------------------------------------------------------------------------- /azure_status_page/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model System.Web.Mvc.HandleErrorInfo 2 | 3 |
4 |

An error occurred while processing your request.

5 |
6 | -------------------------------------------------------------------------------- /azure_status_page/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | @ViewBag.Title 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 57 | 58 |
59 | @RenderBody() 60 |
61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /azure_status_page/Views/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /azure_status_page/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /azure_status_page/Web.config: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /azure_status_page/applicationHost.xdt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /azure_status_page/azure_status_page.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {25464043-8444-49E0-8EDE-566B1409D7DF} 7 | {349C5851-65DF-11DA-9384-00065B846F21};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 8 | Library 9 | azure_status_page 10 | azure_status_page 11 | v4.5 12 | 0.0.2 13 | 14 | 15 | true 16 | full 17 | false 18 | bin 19 | DEBUG; 20 | prompt 21 | 4 22 | 23 | 24 | true 25 | bin 26 | prompt 27 | 4 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll 44 | 45 | 46 | ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 47 | 48 | 49 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll 50 | 51 | 52 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll 53 | 54 | 55 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll 56 | 57 | 58 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll 59 | 60 | 61 | ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll 62 | 63 | 64 | ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll 65 | 66 | 67 | 68 | ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll 69 | 70 | 71 | ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll 72 | 73 | 74 | ..\packages\Microsoft.Azure.KeyVault.Core.1.0.0\lib\net40\Microsoft.Azure.KeyVault.Core.dll 75 | 76 | 77 | ..\packages\Microsoft.Data.Edm.5.8.2\lib\net40\Microsoft.Data.Edm.dll 78 | 79 | 80 | ..\packages\System.Spatial.5.8.2\lib\net40\System.Spatial.dll 81 | 82 | 83 | ..\packages\Microsoft.Data.OData.5.8.2\lib\net40\Microsoft.Data.OData.dll 84 | 85 | 86 | ..\packages\Microsoft.Data.Services.Client.5.8.2\lib\net40\Microsoft.Data.Services.Client.dll 87 | 88 | 89 | ..\packages\WindowsAzure.Storage.8.1.0\lib\net45\Microsoft.WindowsAzure.Storage.dll 90 | 91 | 92 | 93 | ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | Global.asax 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | {31878B8D-7325-47B0-BF2B-1809B72E63EC} 131 | azure_status_page.core 132 | 133 | 134 | {EBD3A11E-FD34-4BF9-8768-BE5A613F7AE7} 135 | azure_status_page.client 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /azure_status_page/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/arch-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dei79/azure-status-page/fdd26666bfa681828a13eda5b0a4ffd1a5e7e297/docs/arch-overview.png -------------------------------------------------------------------------------- /docs/arch-overview.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dei79/azure-status-page/fdd26666bfa681828a13eda5b0a4ffd1a5e7e297/docs/arch-overview.pptx -------------------------------------------------------------------------------- /docs/extension-installed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dei79/azure-status-page/fdd26666bfa681828a13eda5b0a4ffd1a5e7e297/docs/extension-installed.png -------------------------------------------------------------------------------- /docs/extension-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dei79/azure-status-page/fdd26666bfa681828a13eda5b0a4ffd1a5e7e297/docs/extension-result.png -------------------------------------------------------------------------------- /docs/extension-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dei79/azure-status-page/fdd26666bfa681828a13eda5b0a4ffd1a5e7e297/docs/extension-setup.png --------------------------------------------------------------------------------