Merge branch 'Development' into feature-frontend

This commit is contained in:
Stone_Red
2023-03-08 09:35:49 +01:00
committed by GitHub
43 changed files with 2195 additions and 276 deletions
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.4.33205.214
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elektrifikatsiya", "Elektrifikatsiya\Elektrifikatsiya.csproj", "{B35B4694-ACC4-4CD6-9518-B338F30111A0}"
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{7350C55E-0EAD-47DE-A1E0-2A2EA3291D0B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -15,6 +17,10 @@ Global
{B35B4694-ACC4-4CD6-9518-B338F30111A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B35B4694-ACC4-4CD6-9518-B338F30111A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B35B4694-ACC4-4CD6-9518-B338F30111A0}.Release|Any CPU.Build.0 = Release|Any CPU
{7350C55E-0EAD-47DE-A1E0-2A2EA3291D0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7350C55E-0EAD-47DE-A1E0-2A2EA3291D0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7350C55E-0EAD-47DE-A1E0-2A2EA3291D0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7350C55E-0EAD-47DE-A1E0-2A2EA3291D0B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1,4 +1,5 @@
<Blazorise.ThemeProvider Theme="@theme">
@using Blazorise.Components
<Blazorise.ThemeProvider Theme="@theme">
<Router AppAssembly="typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Elektrifikatsiya.Layouts.MainLayout)" />
@@ -16,11 +17,21 @@
{
BarOptions = new()
{
HorizontalHeight = "72px"
HorizontalHeight = "72px",
DarkColors = new()
{
ItemColorOptions = new()
{
ActiveBackgroundColor = "#1a237e",
ActiveColor = "#1a237e",
HoverColor = "#1a237e",
}
}
},
ColorOptions = new()
{
Primary = "#0288D1",
Primary = "#1a237e",
Secondary = "#A65529",
Success = "#23C02E",
Info = "#9BD8FE",
@@ -31,7 +42,7 @@
},
BackgroundOptions = new()
{
Primary = "#0288D1",
Primary = "#1a237e",
Secondary = "#A65529",
Success = "#23C02E",
Info = "#9BD8FE",
@@ -42,7 +53,12 @@
},
InputOptions = new()
{
CheckColor = "#0288D1",
CheckColor = "#1a237e",
},
TextColorOptions = new()
{
Dark = "#000000",
Primary = "#1a237e"
}
};
}
@@ -1,6 +1,6 @@
<Bar Breakpoint="Breakpoint.Desktop" NavigationBreakpoint="Breakpoint.Tablet" ThemeContrast="ThemeContrast.Dark"
Mode="BarMode.VerticalInline" CollapseMode="BarCollapseMode.Small">
<BarToggler />
<BarToggler TextColor="TextColor.Primary" />
<BarBrand>
<BarItem>
<BarLink To="">
@@ -12,36 +12,30 @@
<BarMenu>
<BarStart>
<BarItem>
<BarLink To="/">
<BarIcon IconName="IconName.Dashboard" />
<BarLink To="/" TextColor="TextColor.White">
<BarIcon Style="color:white" IconName="IconName.Dashboard" />
Dashboard
</BarLink>
</BarItem>
<BarItem>
<BarDropdown @bind-Visible="pagesBarVisible">
<BarDropdownToggle>
<BarIcon IconName="IconName.Edit" />
Pages
</BarDropdownToggle>
<BarDropdownMenu>
<BarDropdownItem To="/simple-form">Simple Form</BarDropdownItem>
</BarDropdownMenu>
</BarDropdown>
<BarLink To="/admin" TextColor="TextColor.White">
<BarIcon Style="color:white" IconName="IconName.ShieldAlt" />
Admin
</BarLink>
</BarItem>
<BarItem>
<BarLink To="/addplug" TextColor="TextColor.White">
<BarIcon Style="color:white" IconName="IconName.Plug" />
Add Plug
</BarLink>
</BarItem>
</BarStart>
<BarEnd>
<BarItem>
<BarDropdown>
<BarDropdownToggle>
<BarIcon IconName="IconName.Smartphone" />
Apps
</BarDropdownToggle>
<BarDropdownMenu>
<BarDropdownItem To="/apps/todo">
Todo
</BarDropdownItem>
</BarDropdownMenu>
</BarDropdown>
<BarLink To="/settings" TextColor="TextColor.White">
<BarIcon Style="color:white" IconName="IconName.Wrench" />
Settings
</BarLink>
</BarItem>
</BarEnd>
</BarMenu>
@@ -49,5 +43,6 @@
@code {
private bool pagesBarVisible = true;
RenderFragment customIcon =@<img src="/brand-logo.png" style="width:32px; height: 32px" />;
//TODO: replace logo png with transparent background one
RenderFragment customIcon =@<img src="/logo.png" style="width:32px; height: 32px" />;
}
@@ -1,163 +0,0 @@
@using Blazorise.Localization
<Bar @bind-Visible="@topbarVisible" Breakpoint="Breakpoint.Desktop" Background="Background.Primary" ThemeContrast="ThemeContrast.Light">
<BarBrand>
<BarItem>
<BarLink To="">
<BarIcon Margin="Margin.Is2.FromEnd" IconName="IconName.Dashboard" />
Elektrifikatsiya
</BarLink>
</BarItem>
</BarBrand>
<BarToggler />
<BarMenu>
<BarStart>
<BarItem>
<BarLink To="/">Home</BarLink>
</BarItem>
<BarItem>
<BarLink To="https://blazorise.com/docs/">Documentation</BarLink>
</BarItem>
<BarItem>
<BarDropdown>
<BarDropdownToggle>More</BarDropdownToggle>
<BarDropdownMenu>
<BarDropdownItem To="https://blazorise.com/docs/start/">
Quick-Start Guide
</BarDropdownItem>
<BarDropdownDivider />
<BarDropdownItem To="https://blazorise.com/docs/usage/">
Usage
</BarDropdownItem>
</BarDropdownMenu>
</BarDropdown>
</BarItem>
<BarItem>
<BarDropdown>
<BarDropdownToggle>Layout</BarDropdownToggle>
<BarDropdownMenu>
<BarDropdownItem Clicked="@(()=>OnLayoutTypeChecked("fixed-header"))">
@if ( LayoutType == "fixed-header" )
{
<Icon Name="IconName.CheckCircle" TextColor="TextColor.Success" />
}
Fixed Header
</BarDropdownItem>
<BarDropdownItem Clicked="@(()=>OnLayoutTypeChecked("fixed-header-footer-only"))">
@if ( LayoutType == "fixed-header-footer-only" )
{
<Icon Name="IconName.CheckCircle" TextColor="TextColor.Success" />
}
Fixed Header and Footer only
</BarDropdownItem>
<BarDropdownItem Clicked="@(()=>OnLayoutTypeChecked("sider-with-header-on-top"))">
@if ( LayoutType == "sider-with-header-on-top" )
{
<Icon Name="IconName.CheckCircle" TextColor="TextColor.Success" />
}
Sider with Header on top
</BarDropdownItem>
</BarDropdownMenu>
</BarDropdown>
</BarItem>
</BarStart>
<BarEnd>
<BarItem>
<BarDropdown RightAligned>
<BarDropdownToggle><Icon Name="IconName.Language" /></BarDropdownToggle>
<BarDropdownMenu>
@foreach ( var cultureInfo in LocalizationService.AvailableCultures )
{
<BarDropdownItem @key="@cultureInfo.Name" Clicked="@(()=>SelectCulture(cultureInfo.Name))">
@if ( cultureInfo.IsNeutralCulture )
{
@cultureInfo.EnglishName
}
else
{
@cultureInfo.Parent.EnglishName
}
</BarDropdownItem>
}
</BarDropdownMenu>
</BarDropdown>
</BarItem>
<BarItem>
<BarDropdown RightAligned>
<BarDropdownToggle><Icon Name="IconName.Tint" /> Theme</BarDropdownToggle>
<BarDropdownMenu Style="padding: 15px; min-width:550px;">
<Row>
<Column Margin="Margin.Is2.FromBottom">
<Field>
<Switch TValue="bool" Checked="@(Theme?.Enabled == true)" CheckedChanged="@ThemeEnabledChanged">Theme enabled</Switch>
</Field>
</Column>
</Row>
<Row>
<Column Margin="Margin.Is2.FromBottom">
<Field>
<Check TValue="bool" Checked="@(Theme?.IsGradient == true)" CheckedChanged="@ThemeGradientChanged">Gradient colors</Check>
</Field>
<Field>
<Check TValue="bool" Checked="@(Theme?.IsRounded == true)" CheckedChanged="@ThemeRoundedChanged">Rounded elements</Check>
</Field>
</Column>
</Row>
<Row>
<Column>
<Container Fluid>
<ThemeColorSelector Value="@(Theme?.ColorOptions?.Primary)" ValueChanged="@ThemeColorChanged"></ThemeColorSelector>
</Container>
</Column>
</Row>
</BarDropdownMenu>
</BarDropdown>
</BarItem>
<BarItem>
<BarLink To="https://github.com/Megabit/Blazorise">GitHub</BarLink>
</BarItem>
</BarEnd>
</BarMenu>
</Bar>
@code {
protected override async Task OnInitializedAsync()
{
await SelectCulture( "en-US" );
await base.OnInitializedAsync();
}
Task SelectCulture( string name )
{
LocalizationService.ChangeLanguage( name );
return Task.CompletedTask;
}
private bool topbarVisible = false;
Task OnLayoutTypeChecked( string layoutType )
{
LayoutType = layoutType;
return LayoutTypeChanged.InvokeAsync( layoutType );
}
[Parameter] public EventCallback<bool> ThemeEnabledChanged { get; set; }
[Parameter] public EventCallback<bool> ThemeGradientChanged { get; set; }
[Parameter] public EventCallback<bool> ThemeRoundedChanged { get; set; }
[Parameter] public EventCallback<string> ThemeColorChanged { get; set; }
[Parameter] public string LayoutType { get; set; }
[Parameter] public EventCallback<string> LayoutTypeChanged { get; set; }
[Inject] protected ITextLocalizerService LocalizationService { get; set; }
[CascadingParameter] protected Theme Theme { get; set; }
}
@@ -0,0 +1,14 @@
using Elektrifikatsiya.Models;
using Microsoft.EntityFrameworkCore;
namespace Elektrifikatsiya.Database
{
public class DeviceManagmentDatabaseContext : DbContext
{
public DbSet<Device> Devices { get; set; }
public DeviceManagmentDatabaseContext(DbContextOptions<DeviceManagmentDatabaseContext> options) : base(options)
{
}
}
}
@@ -0,0 +1,14 @@
using Elektrifikatsiya.Models;
using Microsoft.EntityFrameworkCore;
namespace Elektrifikatsiya.Database;
public class UserDatabaseContext : DbContext
{
public UserDatabaseContext(DbContextOptions<UserDatabaseContext> dbContextOptions) : base(dbContextOptions)
{
}
public DbSet<User> Users { get; set; }
}
@@ -14,13 +14,37 @@
<Copyright>Copyright 2018-2022 Megabit</Copyright>
<UserSecretsId>bd144edc-e5c5-4b97-84d6-38c6b1edcbf9</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Blazorise" Version="1.1.*" />
<PackageReference Include="Blazorise.Material" Version="1.1.*" />
<PackageReference Include="Blazorise.Icons.Material" Version="1.1.*" />
<Compile Remove="Middleware\**" />
<Compile Remove="Model\**" />
<Content Remove="Middleware\**" />
<Content Remove="Model\**" />
<EmbeddedResource Remove="Middleware\**" />
<EmbeddedResource Remove="Model\**" />
<None Remove="Middleware\**" />
<None Remove="Model\**" />
</ItemGroup>
<ItemGroup>
<None Remove="Models\NewFile.txt" />
<None Remove="Models\ShellyResponse" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Blazorise" Version="1.2.0" />
<PackageReference Include="Blazorise.Charts" Version="1.2.0" />
<PackageReference Include="Blazorise.Components" Version="1.2.0" />
<PackageReference Include="Blazorise.Material" Version="1.2.0" />
<PackageReference Include="Blazorise.Icons.Material" Version="1.2.0" />
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="FluentResults" Version="3.15.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.2" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<PackageReference Include="Tmds.MDns" Version="0.7.1" />
</ItemGroup>
</Project>
@@ -1,48 +1,15 @@
@using Elektrifikatsiya.Components.Layout
@inherits LayoutComponentBase
@if (layoutType == "fixed-header")
{
<Layout Sider>
<Layout Sider>
<LayoutSider>
<LayoutSiderContent>
<SideMenu />
</LayoutSiderContent>
</LayoutSider>
<Layout>
<LayoutContent Padding="Padding.Is4.OnX.Is4.FromTop">
<LayoutContent Padding="Padding.Is4.OnDesktop.Is0">
@Body
</LayoutContent>
</Layout>
</Layout>
}
else if (layoutType == "fixed-header-footer-only")
{
<Layout>
<LayoutContent Padding="Padding.Is4.OnX.Is4.FromTop">
@Body
</LayoutContent>
<LayoutFooter Fixed>
<FooterMenu></FooterMenu>
</LayoutFooter>
</Layout>
}
else if (layoutType == "sider-with-header-on-top")
{
<Layout>
<Layout Sider>
<LayoutSider>
<LayoutSiderContent>
<SideMenu />
</LayoutSiderContent>
</LayoutSider>
<Layout>
<LayoutContent Padding="Padding.Is4.OnX.Is4.FromTop">
@Body
</LayoutContent>
</Layout>
</Layout>
</Layout>
}
</Layout>
@@ -0,0 +1,37 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Net;
namespace Elektrifikatsiya.Models;
public class Device
{
[Key]
public string MacAddress { get; private set; }
[Required]
public string Name { get; set; }
[Required]
public IPAddress IpAddress { get; set; }
[Required]
public User User { get; set; }
[NotMapped]
public int PowerUsage { get; set; }
public string Room { get; set; }
[NotMapped]
public bool Available { get; set; }
public Device(string macAddress, string name, IPAddress address, User user, string room)
{
MacAddress = macAddress;
Name = name;
IpAddress = address;
User = user;
Room = room;
}
}
@@ -0,0 +1,11 @@
namespace Elektrifikatsiya.Models;
public class DeviceStatusChagedEventArgs : EventArgs
{
public string MacAddress { get; set; }
public DeviceStatusChagedEventArgs(string macAddress)
{
MacAddress = macAddress;
}
}
@@ -0,0 +1,18 @@
namespace Elektrifikatsiya.Models
{
public class Event
{
public string EventName { get; set; }
public string Description { get; set; }
public DateTime Date { get; set; }
//if Plug Class implemented -> Property for which plug this event is from
public Event(string eventName, string description, DateTime date)
{
EventName = eventName;
Description = description;
Date = date;
}
}
}
@@ -0,0 +1,73 @@
using System.Text.Json.Serialization;
namespace Elektrifikatsiya.Models;
public enum Status
{
Success,
Error
}
public enum ResultType
{
Matrix,
Vector,
Scalar,
String
}
public class PrometheusQueryResult
{
public Status Status { get; set; }
public string? ErrorType { get; set; }
public string? Error { get; set; }
public List<string>? Warnings { get; set; }
public PrometheusQueryResult(Status status, string? errorType, string? error, List<string>? warnings)
{
Status = status;
ErrorType = errorType;
Error = error;
Warnings = warnings;
}
}
internal class PrometheusDataWrapper
{
public ResultType ResultType { get; set; }
public List<PrometheusData> Result { get; set; }
public PrometheusDataWrapper(ResultType resultType, List<PrometheusData> result)
{
ResultType = resultType;
Result = result;
}
}
internal class PrometheusDataMetric
{
[JsonPropertyName("__name__")] public string Name { get; set; }
public string Job { get; set; }
public string Instance { get; set; }
public PrometheusDataMetric(string name, string job, string instance)
{
Name = name;
Job = job;
Instance = instance;
}
}
internal class PrometheusData
{
public PrometheusDataMetric Metric { get; set; }
public object Value { get; set; }
public PrometheusData(PrometheusDataMetric metric, object value)
{
Metric = metric;
Value = value;
}
}
@@ -0,0 +1,9 @@
namespace Elektrifikatsiya.Models;
// Higher up = Higher permission
// Or you can set the priority manually <Role> = <value>
public enum Role
{
Admin,
User
}
@@ -0,0 +1,13 @@
namespace Elektrifikatsiya.Models;
public class ShellyResponse
{
public string Type { get; set; }
public string Mac { get; set; }
public ShellyResponse(string type, string mac)
{
Type = type;
Mac = mac;
}
}
@@ -0,0 +1,24 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Elektrifikatsiya.Models;
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; private set; }
public string Name { get; private set; }
public string PasswordHash { get; private set; }
public string? SessionToken { get; set; }
public DateTime LastLoginDate { get; set; }
public Role Role { get; set; }
public User(string name, string passwordHash, Role role)
{
Name = name;
PasswordHash = passwordHash;
Role = role;
}
}
@@ -1,31 +1,124 @@
@page "/"
@using Blazorise.Components;
@using Elektrifikatsiya.Models;
@inject IVersionProvider VersionProvider
<Heading Size="HeadingSize.Is1" Margin="Margin.Is3.FromBottom">Blazorise</Heading>
<Paragraph>
Blazorise is a component library built on top of Blazor and CSS frameworks like <Anchor To="https://getbootstrap.com/" Target="Target.Blank">Bootstrap</Anchor>, <Anchor To="https://bulma.io/" Target="Target.Blank">Bulma</Anchor>, <Anchor To="https://ant.design/" Target="Target.Blank">Ant Design</Anchor>, and <Anchor To="http://daemonite.github.io/material/" Target="Target.Blank">Material</Anchor>. It can be used to build responsive, single-page web applications.
</Paragraph>
<Row Padding="Padding.Is3.OnDesktop.Is0">
<Div Display="Display.Flex.Row.OnDesktop" Margin="Margin.Is3.FromBottom" Width="Width.Is100">
<Column ColumnSize="ColumnSize.Is6.OnDesktop.Is12">
<ListView TItem="Device"
Data="@plugs"
TextField="@((_)=>(""))"
ValueField="@((_)=>(""))"
Mode="ListGroupMode.Static">
<ItemTemplate>
<ListGroupItem>
<Row>
<Column ColumnSize="ColumnSize.Is8">
<Row>
<Column ColumnSize="ColumnSize.Is7">
<Heading Size="HeadingSize.Is6" Margin="Margin.Is1.FromBottom">@context.Item.Name</Heading>
</Column>
</Row>
<Row>
<Column>
<Small>User: @context.Item.User</Small>
<br />
<Small>Room: @context.Item.Room</Small>
</Column>
<Column ColumnSize="ColumnSize.Is5">
<Paragraph Margin="Margin.Is1.OnY">@context.Item.PowerUsage W</Paragraph>
</Column>
</Row>
</Column>
<Column ColumnSize="ColumnSize.Is4">
<Button Position="Position.Absolute.Top.Is50.Start.Is50.Translate.Middle" Color="Color.Dark" Outline>
<BarIcon IconName="IconName.Bolt" />
</Button>
</Column>
</Row>
</ListGroupItem>
</ItemTemplate>
</ListView>
</Column>
<Column ColumnSize="ColumnSize.Is6.OnDesktop.Is12">
<ListView TItem="Event"
Data="@events"
TextField="@((_)=>(""))"
ValueField="@((_)=>(""))"
Mode="ListGroupMode.Static">
<ItemTemplate>
<Div Flex="Flex.InlineFlex.JustifyContent.Between" Width="Width.Is100">
<Heading Size="HeadingSize.Is6" Margin="Margin.Is2.FromBottom">@context.Item.EventName</Heading>
<Small>@context.Item.Date</Small>
</Div>
<Paragraph Margin="Margin.Is2.FromBottom">@context.Item.Description</Paragraph>
</ItemTemplate>
</ListView>
</Column>
</Div>
</Row>
<Row Padding="Padding.Is3">
<Column>
<Button Color="Color.Primary" Style="border-radius: 0px" Clicked="@(async () => await HandleRedraw())">Aktualisieren</Button>
<LineChart @ref="lineChart" TItem="double" />
</Column>
</Row>
<Alert Color="Color.Info" Visible>
<Paragraph>
This is a Blazorise Starting Template allowing you to quickly get started building your project!
</Paragraph>
@code {
//TODO: insert new event here if plug produces one
List<Event> events = new List<Event>() { new Event("Placeholder", "Placeholder", DateTime.Now), new Event("Placeholder", "Placeholder", DateTime.Now), new Event("Placeholder", "Placeholder", DateTime.Now) };
//TODO: insert new Device here if user adds one
List<Device> plugs = new List<Device>() { new Device("Placeholder", "Plug", System.Net.IPAddress.Broadcast, 60, "Oldin", "4AHINF"), new Device("Placeholder", "Plug", System.Net.IPAddress.Broadcast, 60, "Oldin", "4AHINF"), new Device("Placeholder", "Plug", System.Net.IPAddress.Broadcast, 60, "Oldin", "4AHINF") };
<Paragraph>
The bare minimum has been installed for you:
<ul>
<li>Blazorise @($"v{VersionProvider.MilestoneVersion}")</li>
<li>The Blazorise Provider you have selected</li>
<li>The corresponding icon provider</li>
</ul>
However Blazorise has many more extensions at your disposal.
You can find them <Anchor To="https://blazorise.com/docs/extensions" Target="Target.Blank">here</Anchor>.
</Paragraph>
//code for graph
LineChart<double> lineChart;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await HandleRedraw();
}
}
async Task HandleRedraw()
{
await lineChart.Clear();
await lineChart.AddLabelsDatasetsAndUpdate(Labels, GetLineChartDataset());
}
//TODO: insert dataset of current and last voltage usages
LineChartDataset<double> GetLineChartDataset()
{
return new LineChartDataset<double>
{
Label = "# of random number in thousands",
Data = RandomizeData(),
BackgroundColor = backgroundColors,
BorderColor = borderColors,
Fill = true,
PointRadius = 3,
CubicInterpolationMode = "monotone",
};
}
string[] Labels = { "Red", "Blue", "Yellow", "Green", "Purple", "Orange" };
List<string> backgroundColors = new List<string> { ChartColor.FromRgba(255, 99, 132, 0.2f), ChartColor.FromRgba(54, 162, 235, 0.2f), ChartColor.FromRgba(255, 206, 86, 0.2f), ChartColor.FromRgba(75, 192, 192, 0.2f), ChartColor.FromRgba(153, 102, 255, 0.2f), ChartColor.FromRgba(255, 159, 64, 0.2f) };
List<string> borderColors = new List<string> { ChartColor.FromRgba(255, 99, 132, 1f), ChartColor.FromRgba(54, 162, 235, 1f), ChartColor.FromRgba(255, 206, 86, 1f), ChartColor.FromRgba(75, 192, 192, 1f), ChartColor.FromRgba(153, 102, 255, 1f), ChartColor.FromRgba(255, 159, 64, 1f) };
List<double> RandomizeData()
{
var r = new Random(DateTime.Now.Millisecond);
return new List<double> {
r.Next( 3, 50 ) * r.NextDouble(),
r.Next( 3, 50 ) * r.NextDouble(),
r.Next( 3, 50 ) * r.NextDouble(),
r.Next( 3, 50 ) * r.NextDouble(),
r.Next( 3, 50 ) * r.NextDouble(),
r.Next( 3, 50 ) * r.NextDouble() };
}
}
<Paragraph>
Please visit the official <Anchor To="https://bootstrapdemo.blazorise.com/" Target="Target.Blank">Blazorise Demo</Anchor> for component examples.
</Paragraph>
<Paragraph>
Please visit the official <Anchor To="https://blazorise.com/docs/" Target="Target.Blank">Blazorise Documentation</Anchor> to learn more about the available components.
</Paragraph>
</Alert>
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using System.Net.Http;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.Web.Virtualization;
using Microsoft.JSInterop;
using Elektrifikatsiya;
using Blazorise;
namespace Elektrifikatsiya.Pages
{
public partial class Dashboard
{
string bgcolor { get; set; } = "00f"; // (starting value)
void SetColor()
{
bgcolor = "#fd7";
StateHasChanged(); // may not be required, but I'm at work right now, so can't check
}
}
}
@@ -40,5 +40,35 @@
<script src="js/material/material.min.js"></script>
<script src="_framework/blazor.server.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.min.js"></script>
<script>
window.blazorExtensions = {
WriteCookie: function(name, value, days) {
var expires;
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toGMTString();
}
else {
expires = "";
}
document.cookie = name + "=" + value + expires + "; path=/;SameSite=Strict";
},
ScrollToId: function(id) {
const element = document.getElementById(id);
if (element instanceof HTMLElement) {
element.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest"
});
}
}
}
</script>
</body>
</html>
@@ -1,12 +1,29 @@
using Blazorise;
using Blazorise.Bootstrap;
using Blazorise.Icons.Material;
using Blazorise.Material;
using Elektrifikatsiya.Database;
using Elektrifikatsiya.Services;
using Elektrifikatsiya.Services.Implementations;
using Microsoft.EntityFrameworkCore;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddHostedService<UpdateService>();
builder.Services.AddSingleton<IDeviceStatusService, DeviceStatusService>();
builder.Services.AddTransient<IDeviceManagmentService, DeviceManagmentService>();
builder.Services.AddDbContext<UserDatabaseContext>(options => options.UseSqlite("Data Source=./UserDatabase.sqlite"));
builder.Services.AddDbContext<DeviceManagmentDatabaseContext>((options) => options.UseSqlite("Data Source=./DeviceManagement.sqlite"));
builder.Services.AddBootstrapProviders();
builder.Services.AddBlazorise(options =>
{
options.Immediate = true;
});
AddBlazorise(builder.Services);
@@ -25,20 +42,22 @@ app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.UseEndpoints(endpoints =>
{
_ = endpoints.MapBlazorHub();
_ = endpoints.MapFallbackToPage("/_Host");
});
IServiceScope serviceScope = app.Services.GetRequiredService<IServiceScopeFactory>().CreateScope();
serviceScope.ServiceProvider.GetRequiredService<UserDatabaseContext>().Database.EnsureCreated();
app.MapControllers();
AsyncServiceScope scope = app.Services.CreateAsyncScope();
scope.ServiceProvider.GetRequiredService<DeviceManagmentDatabaseContext>().Database.EnsureCreated();
app.Run();
void AddBlazorise(IServiceCollection services)
{
_ = services
.AddBlazorise();
_ = services
.AddMaterialProviders()
.AddMaterialIcons();
_ = services.AddBlazorise();
_ = services.AddMaterialProviders();
_ = services.AddMaterialIcons();
}
@@ -19,7 +19,7 @@
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{IPAdress}:{ServicePort}",
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"publishAllPorts": true,
"useSSL": true,
"DockerfileRunArguments": "-p 80:80 -p 443:443"
@@ -0,0 +1,57 @@
using Elektrifikatsiya.Models;
using FluentResults;
namespace Elektrifikatsiya.Services;
public interface IAuthenticationService
{
/// <summary>
/// Gets the current user.
/// </summary>
/// <returns> <see cref="Task"/> to <see langword="await"/> and the currently authenticated user.</returns>
Task<Result<User>> GetUserAsync();
/// <summary>
/// Registers a new user.
/// </summary>
/// <param name="name">The name address of the user.</param>
/// <param name="password">The password of the user.</param>
/// <param name="role">The default role of the user.</param>
/// <returns>A <see cref="Task"/> to <see langword="await"/>.</returns>
Task<Result> RegisterUserAsync(string name, string password, Role role);
/// <summary>
/// Logs a user in.
/// </summary>
/// <param name="name">The name of the user.</param>
/// <param name="password">The password of the user.</param>
/// <returns>A <see cref="Task"/> to <see langword="await"/>.</returns>
Task<Result> LoginUserAsync(string name, string password);
/// <summary>
/// Logs the user out.
/// </summary>
/// <returns>A <see cref="Task"/> to <see langword="await"/>.</returns>
Task<Result> LogoutUserAsync();
/// <summary>
/// Deletes the current user.
/// </summary>
/// <returns>A <see cref="Task"/> to <see langword="await"/>.</returns>
Task<Result> DeleteUserAsync();
/// <summary>
/// Checks if a user exists.
/// </summary>
/// <param name="name">The name address of the user.</param>
/// <returns>A <see cref="Task"/> to <see langword="await"/> and a <see cref="bool"/> that indicates if the user exists.</returns>
Task<Result<bool>> UserExistsAsync(string name);
/// <summary>
/// Check if the current user is authenticated.
/// </summary>
/// <returns>A <see cref="Task"/> to <see langword="await"/> and a <see cref="bool"/> that indicates if the user is authenticated.</returns>
Task<Result<bool>> IsAuthenticated();
}
@@ -0,0 +1,15 @@
using Elektrifikatsiya.Models;
using FluentResults;
namespace Elektrifikatsiya.Services;
public interface IAuthorizationService
{
/// <summary>
/// Check if the current user is authorized.
/// </summary>
/// <param name="requiredRole">The minimum required role.</param>
/// <returns>A <see cref="Task"/> to <see langword="await"/> and a <see cref="bool"/> that indicates if the user is authorized.</returns>
Task<Result<bool>> IsAuthorized(Role requiredRole);
}
@@ -0,0 +1,13 @@
namespace Elektrifikatsiya.Services;
public interface ICookieService
{
/// <summary>
/// Writes a cookie.
/// </summary>
/// <param name="name">The name of the cookie.</param>
/// <param name="value">The value of the cookie.</param>
/// <param name="days">Indicates the maximum lifetime of the cookie.</param>
/// <returns></returns>
Task WriteCookieAsync(string name, string value, int days);
}
@@ -0,0 +1,22 @@
using Elektrifikatsiya.Models;
using FluentResults;
using System.Net;
namespace Elektrifikatsiya.Services;
public interface IDeviceManagmentService
{
public Task<Result<Device>> Register(IPAddress ip, User user, string? name = null, string room = "default");
public Task<Result> Unregister(string macAdress);
public Result<Device> GetDevice(string macAdress);
public Result<List<Device>> GetDevices();
public Result<List<Device>> GetDevicesInRoom(string room);
public Result<List<Device>> GetDevicesOfUser(int userId);
}
@@ -0,0 +1,18 @@
using Elektrifikatsiya.Models;
using FluentResults;
namespace Elektrifikatsiya.Services;
public interface IDeviceStatusService
{
public event EventHandler<DeviceStatusChagedEventArgs> OnDeviceStatusChanged;
public Result<List<Device>> GetDevices();
public Result UpdateDeviceStatus(Device device);
public Result TrackDevice(Device device);
public Result UntrackDevice(string macAddress);
}
@@ -0,0 +1,134 @@
using Elektrifikatsiya.Database;
using Elektrifikatsiya.Models;
using Elektrifikatsiya.Utilities;
using FluentResults;
using Microsoft.EntityFrameworkCore;
using BC = BCrypt.Net.BCrypt;
namespace Elektrifikatsiya.Services.Implementations;
public class AuthenticationService : IAuthenticationService
{
private readonly UserDatabaseContext userDatabaseContext;
private readonly IHttpContextAccessor httpContextAccessor;
private readonly ICookieService cookieService;
public AuthenticationService(UserDatabaseContext userDatabaseContext, IHttpContextAccessor httpContextAccessor, ICookieService cookieService)
{
this.userDatabaseContext = userDatabaseContext;
this.httpContextAccessor = httpContextAccessor;
this.cookieService = cookieService;
}
public async Task<Result> DeleteUserAsync()
{
Result<User> getUserResult = await GetUserAsync();
if (getUserResult.IsFailed)
{
return getUserResult.ToResult();
}
_ = userDatabaseContext.Users.Remove(getUserResult.Value);
return (await Result.Try(() => userDatabaseContext.SaveChangesAsync())).ToResult();
}
public async Task<Result<User>> GetUserAsync()
{
string? token = httpContextAccessor.HttpContext?.Request.Cookies["token"]?.ToString();
if (!TokenGenerator.ValidateToken(token, "auth"))
{
return Result.Fail("Token is not valid!");
}
User? user = await userDatabaseContext.Users.FirstOrDefaultAsync(u => u.SessionToken == token);
if (user is null || DateTime.UtcNow - user.LastLoginDate > TimeSpan.FromDays(7))
{
return Result.Fail("Token is not valid or expired!");
}
return user;
}
public async Task<Result<bool>> IsAuthenticated()
{
return (await GetUserAsync()).IsSuccess;
}
public async Task<Result> LoginUserAsync(string name, string password)
{
User? user = await userDatabaseContext.Users.FirstOrDefaultAsync(u => u.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
if (user is null)
{
return Result.Fail("User does not exist!");
}
if (!BC.Verify(password, user.PasswordHash))
{
return Result.Fail("Invalid credentials!");
}
string newToken = TokenGenerator.GenerateToken("auth", user.Id);
user.LastLoginDate = DateTime.UtcNow;
user.SessionToken = newToken;
_ = await userDatabaseContext.SaveChangesAsync();
await cookieService.WriteCookieAsync("token", newToken, 7);
return Result.Ok();
}
public async Task<Result> LogoutUserAsync()
{
string? token = httpContextAccessor.HttpContext?.Request.Cookies["token"]?.ToString();
await cookieService.WriteCookieAsync("token", string.Empty, 0);
if (!TokenGenerator.ValidateToken(token, "auth"))
{
return Result.Ok();
}
User? user = await userDatabaseContext.Users.FirstOrDefaultAsync(u => u.SessionToken == token);
if (user is null)
{
return Result.Ok();
}
user.SessionToken = null;
_ = await userDatabaseContext.SaveChangesAsync();
return Result.Ok();
}
public async Task<Result> RegisterUserAsync(string name, string password, Role role)
{
Result<bool> userExistsResult = await UserExistsAsync(name);
if (userExistsResult.IsFailed || userExistsResult.Value)
{
return Result.Fail("User name already exists!");
}
User user = new User(name, BC.HashPassword(password), role);
_ = userDatabaseContext.Users.Add(user);
return (await Result.Try(() => userDatabaseContext.SaveChangesAsync())).ToResult();
}
public Task<Result<bool>> UserExistsAsync(string name)
{
return Result.Try(() =>
userDatabaseContext.Users.AnyAsync(u => u.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)));
}
}
@@ -0,0 +1,29 @@
using Elektrifikatsiya.Models;
using FluentResults;
namespace Elektrifikatsiya.Services.Implementations;
public class AuthorizationService : IAuthorizationService
{
private readonly IAuthenticationService authenticationService;
public AuthorizationService(IAuthenticationService authenticationService)
{
this.authenticationService = authenticationService;
}
public async Task<Result<bool>> IsAuthorized(Role requiredRole)
{
Result<User> userResult = await authenticationService.GetUserAsync();
if (userResult.IsSuccess && userResult.Value.Role <= requiredRole)
{
return Result.Ok(true);
}
else
{
return Result.Fail("User doesn't have the required role!");
}
}
}
@@ -0,0 +1,18 @@
using Microsoft.JSInterop;
namespace Elektrifikatsiya.Services.Implementations;
public class CookieService : ICookieService
{
private readonly IJSRuntime jsRuntime;
public CookieService(IJSRuntime jsRuntime)
{
this.jsRuntime = jsRuntime;
}
public async Task WriteCookieAsync(string name, string value, int days)
{
_ = await jsRuntime.InvokeAsync<string>("blazorExtensions.WriteCookie", name, value, days);
}
}
@@ -0,0 +1,125 @@
using Elektrifikatsiya.Database;
using Elektrifikatsiya.Models;
using FluentResults;
using System.Net;
using System.Net.NetworkInformation;
namespace Elektrifikatsiya.Services.Implementations;
public class DeviceManagmentService : IDeviceManagmentService
{
private readonly DeviceManagmentDatabaseContext deviceManagmentDatabaseContext;
private readonly IDeviceStatusService deviceStatusService;
private readonly HttpClient httpClient;
public DeviceManagmentService(DeviceManagmentDatabaseContext deviceManagmentDatabaseContext, IDeviceStatusService deviceStatusService, HttpClient httpClient)
{
this.deviceManagmentDatabaseContext = deviceManagmentDatabaseContext;
this.deviceStatusService = deviceStatusService;
this.httpClient = httpClient;
}
public Result<Device> GetDevice(string macAdress)
{
Result<List<Device>> getDevicesResult = deviceStatusService.GetDevices();
if (getDevicesResult.IsFailed)
{
return getDevicesResult.ToResult();
}
Device? device = getDevicesResult.Value.FirstOrDefault(d => d.MacAddress == macAdress);
if (device is null)
{
return Result.Fail("Device does not exist!");
}
return device;
}
public Result<List<Device>> GetDevices()
{
Result<List<Device>> getDevicesResult = deviceStatusService.GetDevices();
if (getDevicesResult.IsFailed)
{
return getDevicesResult.ToResult();
}
return getDevicesResult.Value.ToList();
}
public Result<List<Device>> GetDevicesInRoom(string room)
{
Result<List<Device>> getDevicesResult = deviceStatusService.GetDevices();
if (getDevicesResult.IsFailed)
{
return getDevicesResult.ToResult();
}
return getDevicesResult.Value.Where(d => d.Room == room).ToList();
}
public Result<List<Device>> GetDevicesOfUser(int userId)
{
Result<List<Device>> getDevicesResult = deviceStatusService.GetDevices();
if (getDevicesResult.IsFailed)
{
return getDevicesResult.ToResult();
}
return getDevicesResult.Value.Where(d => d.User.Id == userId).ToList();
}
public async Task<Result<Device>> Register(IPAddress ip, User user, string? name = null, string room = "default")
{
ShellyResponse? shellyResponse = await httpClient.GetFromJsonAsync<ShellyResponse>($"{ip}/shelly");
if(shellyResponse is null || shellyResponse.Type != "SHPLG-S")
{
return Result.Fail("Device is not a \"SHPLG-S\" or not reachable!");
}
string mac = shellyResponse.Mac;
if(PhysicalAddress.TryParse(mac, out _))
{
return Result.Fail("Invalid mac address!");
}
Device device = new Device(mac, name ?? mac, ip, user, room);
deviceManagmentDatabaseContext.Add(device);
Result saveDatabaseChangesResult = await Result.Try(async Task () => await deviceManagmentDatabaseContext.SaveChangesAsync());
Result trackDeviceResult = deviceStatusService.TrackDevice(device);
return Result.Merge(saveDatabaseChangesResult, trackDeviceResult).ToResult(device);
}
public async Task<Result> Unregister(string macAdress)
{
Result<Device> getDeviceResult = GetDevice(macAdress);
if (getDeviceResult.IsFailed)
{
return getDeviceResult.ToResult();
}
Result untrackDeviceResult = deviceStatusService.UntrackDevice(macAdress);
if (untrackDeviceResult.IsFailed)
{
return untrackDeviceResult;
}
deviceManagmentDatabaseContext.Remove(getDeviceResult.Value);
return await Result.Try(async Task () => await deviceManagmentDatabaseContext.SaveChangesAsync());
}
}
@@ -0,0 +1,53 @@
using Elektrifikatsiya.Models;
using FluentResults;
namespace Elektrifikatsiya.Services.Implementations;
public class DeviceStatusService : IDeviceStatusService
{
public event EventHandler<DeviceStatusChagedEventArgs>? OnDeviceStatusChanged;
private readonly Dictionary<string, Device> devices = new();
public Result<List<Device>> GetDevices()
{
return devices.Values.ToList();
}
public Result UpdateDeviceStatus(Device device)
{
Device? modDevice = devices.GetValueOrDefault(device.MacAddress);
if (modDevice is null)
{
return Result.Fail("Device not tracked!");
}
modDevice.PowerUsage = device.PowerUsage;
modDevice.Available = device.Available;
modDevice.IpAddress = device.IpAddress;
modDevice.Name = device.Name;
OnDeviceStatusChanged?.Invoke(this, new DeviceStatusChagedEventArgs(device.MacAddress));
return Result.Ok();
}
public Result TrackDevice(Device device)
{
if (devices.ContainsKey(device.MacAddress))
{
return Result.Fail("Device already tracked!");
}
devices[device.MacAddress] = device;
return Result.Ok();
}
public Result UntrackDevice(string macAddress)
{
return devices.Remove(macAddress) ? Result.Ok() : Result.Fail("Device is not tracked!");
}
}
@@ -0,0 +1,73 @@
using Elektrifikatsiya.Database;
using Elektrifikatsiya.Models;
using FluentResults;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
namespace Elektrifikatsiya.Services.Implementations;
public class UpdateService : IHostedService, IDisposable
{
private readonly ILogger<UpdateService> logger;
private readonly IDeviceStatusService deviceStatusService;
private readonly DeviceManagmentDatabaseContext deviceManagmentDatabaseContext;
private Timer? timer = null;
public UpdateService(ILogger<UpdateService> logger, IDeviceStatusService deviceStatusService, DeviceManagmentDatabaseContext deviceManagmentDatabaseContext)
{
this.logger = logger;
this.deviceStatusService = deviceStatusService;
this.deviceManagmentDatabaseContext = deviceManagmentDatabaseContext;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Starting update service...");
foreach (Device device in await deviceManagmentDatabaseContext.Devices.AsNoTracking().ToListAsync(cancellationToken))
{
_ = deviceStatusService.TrackDevice(device);
}
timer = new Timer(async (_) => await Update(), null, TimeSpan.Zero, TimeSpan.FromSeconds(15));
logger.LogInformation("Update service started.");
}
private async Task Update()
{
Result<List<Device>> getDeviceStatusResult = deviceStatusService.GetDevices();
if (getDeviceStatusResult.IsFailed)
{
logger.LogError("Updating devices failed!");
}
foreach(Device device in getDeviceStatusResult.Value)
{
//TODO: Some update magic
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Stopping update service.");
_ = timer?.Change(Timeout.Infinite, 0);
logger.LogInformation("Stopped update service.");
return Task.CompletedTask;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
timer?.Dispose();
}
}
@@ -0,0 +1,25 @@
using System.Net;
using System.Net.Sockets;
using System.Text.Encodings.Web;
using System.Text.Json;
using Elektrifikatsiya.Models;
using FluentResults;
namespace Elektrifikatsiya.Utilities;
public class PrometheusQuery
{
private string connectionString;
private readonly HttpClient client = new();
public PrometheusQuery(string connectionString)
{
this.connectionString = connectionString;
client.BaseAddress = new Uri(connectionString);
}
public Task<PrometheusQueryResult?> Query(string query)
{
return client.GetFromJsonAsync<PrometheusQueryResult>($"/v1/query?{UrlEncoder.Create().Encode(query)}");
}
}
@@ -0,0 +1,20 @@
using System.Security.Cryptography;
namespace Elektrifikatsiya.Utilities;
public static class SecureStringGenerator
{
public static string CreateCryptographicRandomString(int count)
{
return Convert.ToBase64String(RandomNumberGenerator.GetBytes(count));
}
public static string CreateCryptographicRandomString(int count, int uid)
{
List<byte> bytes = RandomNumberGenerator.GetBytes(count).ToList();
bytes.AddRange(BitConverter.GetBytes(uid));
return Convert.ToBase64String(bytes.ToArray());
}
}
@@ -0,0 +1,32 @@
namespace Elektrifikatsiya.Utilities;
public static class TokenGenerator
{
/// <summary>
/// Creates secure token and uses an UID to make it unique.
/// </summary>
/// <param name="tokenType">The type of the token.</param>
/// <param name="uid">The UID of a user.</param>
/// <param name="length">The length of the token.</param>
/// <returns></returns>
public static string GenerateToken(string tokenType, int uid = 0, int length = 128)
{
return tokenType + "-" + SecureStringGenerator.CreateCryptographicRandomString(length, uid);
}
/// <summary>
/// Checks if a token is valid.
/// </summary>
/// <param name="token">The token to validate.</param>
/// <param name="tokenType">The type the token should have.</param>
/// <returns></returns>
public static bool ValidateToken(string? token, string tokenType)
{
if (token is null)
{
return false;
}
return token.StartsWith(tokenType + "-");
}
}
@@ -9,3 +9,4 @@
@using Elektrifikatsiya
@using Blazorise
@using Blazorise.Charts
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
<PropertyGroup Label="Globals">
<ProjectVersion>2.1</ProjectVersion>
<DockerTargetOS>Linux</DockerTargetOS>
<ProjectGuid>7350c55e-0ead-47de-a1e0-2a2ea3291d0b</ProjectGuid>
<DockerLaunchAction>LaunchBrowser</DockerLaunchAction>
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}</DockerServiceUrl>
<DockerServiceName>elektrifikatsiya</DockerServiceName>
</PropertyGroup>
<ItemGroup>
<None Include="docker-compose.override.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose.yml" />
<None Include=".dockerignore" />
</ItemGroup>
</Project>
@@ -0,0 +1,13 @@
version: '3.4'
services:
elektrifikatsiya:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=https://+:443;http://+:80
ports:
- "80"
- "443"
volumes:
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
+33
View File
@@ -0,0 +1,33 @@
version: '3.4'
services:
elektrifikatsiya:
build:
context: .
dockerfile: Elektrifikatsiya/Dockerfile
prometheus:
image: prom/prometheus:latest
volumes:
- ./host/prometheus/:/etc/prometheus/
- prometheus_data:/prometheus
ports:
- 9090:9090
mqtt2prom:
image: ghcr.io/hikhvar/mqtt2prometheus:latest
volumes:
- ./host/mqtt/mqtt2prom/config.yaml:/config.yaml
ports:
- 9641:9641
mosquitto:
image: eclipse-mosquitto:2
volumes:
- ./host/mqtt/mosquitto/config/:/mosquitto/config/:ro
- ./host/log/mqtt/mosquitto/:/mosquitto/log/
- mosquitto_data:/mosquitto/data/
ports:
- 1883:1883
- 9001:9001
volumes:
mosquitto_data:
prometheus_data:
@@ -0,0 +1,904 @@
# Config file for mosquitto
#
# See mosquitto.conf(5) for more information.
#
# Default values are shown, uncomment to change.
#
# Use the # character to indicate a comment, but only if it is the
# very first character on the line.
# =================================================================
# General configuration
# =================================================================
# Use per listener security settings.
#
# It is recommended this option be set before any other options.
#
# If this option is set to true, then all authentication and access control
# options are controlled on a per listener basis. The following options are
# affected:
#
# acl_file
# allow_anonymous
# allow_zero_length_clientid
# auto_id_prefix
# password_file
# plugin
# plugin_opt_*
# psk_file
#
# Note that if set to true, then a durable client (i.e. with clean session set
# to false) that has disconnected will use the ACL settings defined for the
# listener that it was most recently connected to.
#
# The default behaviour is for this to be set to false, which maintains the
# setting behaviour from previous versions of mosquitto.
#per_listener_settings false
# This option controls whether a client is allowed to connect with a zero
# length client id or not. This option only affects clients using MQTT v3.1.1
# and later. If set to false, clients connecting with a zero length client id
# are disconnected. If set to true, clients will be allocated a client id by
# the broker. This means it is only useful for clients with clean session set
# to true.
#allow_zero_length_clientid true
# If allow_zero_length_clientid is true, this option allows you to set a prefix
# to automatically generated client ids to aid visibility in logs.
# Defaults to 'auto-'
#auto_id_prefix auto-
# This option affects the scenario when a client subscribes to a topic that has
# retained messages. It is possible that the client that published the retained
# message to the topic had access at the time they published, but that access
# has been subsequently removed. If check_retain_source is set to true, the
# default, the source of a retained message will be checked for access rights
# before it is republished. When set to false, no check will be made and the
# retained message will always be published. This affects all listeners.
#check_retain_source true
# QoS 1 and 2 messages will be allowed inflight per client until this limit
# is exceeded. Defaults to 0. (No maximum)
# See also max_inflight_messages
#max_inflight_bytes 0
# The maximum number of QoS 1 and 2 messages currently inflight per
# client.
# This includes messages that are partway through handshakes and
# those that are being retried. Defaults to 20. Set to 0 for no
# maximum. Setting to 1 will guarantee in-order delivery of QoS 1
# and 2 messages.
#max_inflight_messages 20
# For MQTT v5 clients, it is possible to have the server send a "server
# keepalive" value that will override the keepalive value set by the client.
# This is intended to be used as a mechanism to say that the server will
# disconnect the client earlier than it anticipated, and that the client should
# use the new keepalive value. The max_keepalive option allows you to specify
# that clients may only connect with keepalive less than or equal to this
# value, otherwise they will be sent a server keepalive telling them to use
# max_keepalive. This only applies to MQTT v5 clients. The default, and maximum
# value allowable, is 65535.
#
# Set to 0 to allow clients to set keepalive = 0, which means no keepalive
# checks are made and the client will never be disconnected by the broker if no
# messages are received. You should be very sure this is the behaviour that you
# want.
#
# For MQTT v3.1.1 and v3.1 clients, there is no mechanism to tell the client
# what keepalive value they should use. If an MQTT v3.1.1 or v3.1 client
# specifies a keepalive time greater than max_keepalive they will be sent a
# CONNACK message with the "identifier rejected" reason code, and disconnected.
#
#max_keepalive 65535
# For MQTT v5 clients, it is possible to have the server send a "maximum packet
# size" value that will instruct the client it will not accept MQTT packets
# with size greater than max_packet_size bytes. This applies to the full MQTT
# packet, not just the payload. Setting this option to a positive value will
# set the maximum packet size to that number of bytes. If a client sends a
# packet which is larger than this value, it will be disconnected. This applies
# to all clients regardless of the protocol version they are using, but v3.1.1
# and earlier clients will of course not have received the maximum packet size
# information. Defaults to no limit. Setting below 20 bytes is forbidden
# because it is likely to interfere with ordinary client operation, even with
# very small payloads.
#max_packet_size 0
# QoS 1 and 2 messages above those currently in-flight will be queued per
# client until this limit is exceeded. Defaults to 0. (No maximum)
# See also max_queued_messages.
# If both max_queued_messages and max_queued_bytes are specified, packets will
# be queued until the first limit is reached.
#max_queued_bytes 0
# Set the maximum QoS supported. Clients publishing at a QoS higher than
# specified here will be disconnected.
#max_qos 2
# The maximum number of QoS 1 and 2 messages to hold in a queue per client
# above those that are currently in-flight. Defaults to 1000. Set
# to 0 for no maximum (not recommended).
# See also queue_qos0_messages.
# See also max_queued_bytes.
#max_queued_messages 1000
#
# This option sets the maximum number of heap memory bytes that the broker will
# allocate, and hence sets a hard limit on memory use by the broker. Memory
# requests that exceed this value will be denied. The effect will vary
# depending on what has been denied. If an incoming message is being processed,
# then the message will be dropped and the publishing client will be
# disconnected. If an outgoing message is being sent, then the individual
# message will be dropped and the receiving client will be disconnected.
# Defaults to no limit.
#memory_limit 0
# This option sets the maximum publish payload size that the broker will allow.
# Received messages that exceed this size will not be accepted by the broker.
# The default value is 0, which means that all valid MQTT messages are
# accepted. MQTT imposes a maximum payload size of 268435455 bytes.
#message_size_limit 0
# This option allows the session of persistent clients (those with clean
# session set to false) that are not currently connected to be removed if they
# do not reconnect within a certain time frame. This is a non-standard option
# in MQTT v3.1. MQTT v3.1.1 and v5.0 allow brokers to remove client sessions.
#
# Badly designed clients may set clean session to false whilst using a randomly
# generated client id. This leads to persistent clients that connect once and
# never reconnect. This option allows these clients to be removed. This option
# allows persistent clients (those with clean session set to false) to be
# removed if they do not reconnect within a certain time frame.
#
# The expiration period should be an integer followed by one of h d w m y for
# hour, day, week, month and year respectively. For example
#
# persistent_client_expiration 2m
# persistent_client_expiration 14d
# persistent_client_expiration 1y
#
# The default if not set is to never expire persistent clients.
#persistent_client_expiration
# Write process id to a file. Default is a blank string which means
# a pid file shouldn't be written.
# This should be set to /var/run/mosquitto/mosquitto.pid if mosquitto is
# being run automatically on boot with an init script and
# start-stop-daemon or similar.
#pid_file
# Set to true to queue messages with QoS 0 when a persistent client is
# disconnected. These messages are included in the limit imposed by
# max_queued_messages and max_queued_bytes
# Defaults to false.
# This is a non-standard option for the MQTT v3.1 spec but is allowed in
# v3.1.1.
#queue_qos0_messages false
# Set to false to disable retained message support. If a client publishes a
# message with the retain bit set, it will be disconnected if this is set to
# false.
#retain_available true
# Disable Nagle's algorithm on client sockets. This has the effect of reducing
# latency of individual messages at the potential cost of increasing the number
# of packets being sent.
#set_tcp_nodelay false
# Time in seconds between updates of the $SYS tree.
# Set to 0 to disable the publishing of the $SYS tree.
#sys_interval 10
# The MQTT specification requires that the QoS of a message delivered to a
# subscriber is never upgraded to match the QoS of the subscription. Enabling
# this option changes this behaviour. If upgrade_outgoing_qos is set true,
# messages sent to a subscriber will always match the QoS of its subscription.
# This is a non-standard option explicitly disallowed by the spec.
#upgrade_outgoing_qos false
# When run as root, drop privileges to this user and its primary
# group.
# Set to root to stay as root, but this is not recommended.
# If set to "mosquitto", or left unset, and the "mosquitto" user does not exist
# then it will drop privileges to the "nobody" user instead.
# If run as a non-root user, this setting has no effect.
# Note that on Windows this has no effect and so mosquitto should be started by
# the user you wish it to run as.
#user mosquitto
# =================================================================
# Listeners
# =================================================================
# Listen on a port/ip address combination. By using this variable
# multiple times, mosquitto can listen on more than one port. If
# this variable is used and neither bind_address nor port given,
# then the default listener will not be started.
# The port number to listen on must be given. Optionally, an ip
# address or host name may be supplied as a second argument. In
# this case, mosquitto will attempt to bind the listener to that
# address and so restrict access to the associated network and
# interface. By default, mosquitto will listen on all interfaces.
# Note that for a websockets listener it is not possible to bind to a host
# name.
#
# On systems that support Unix Domain Sockets, it is also possible
# to create a # Unix socket rather than opening a TCP socket. In
# this case, the port number should be set to 0 and a unix socket
# path must be provided, e.g.
# listener 0 /tmp/mosquitto.sock
#
# listener port-number [ip address/host name/unix socket path]
listener 1883
# By default, a listener will attempt to listen on all supported IP protocol
# versions. If you do not have an IPv4 or IPv6 interface you may wish to
# disable support for either of those protocol versions. In particular, note
# that due to the limitations of the websockets library, it will only ever
# attempt to open IPv6 sockets if IPv6 support is compiled in, and so will fail
# if IPv6 is not available.
#
# Set to `ipv4` to force the listener to only use IPv4, or set to `ipv6` to
# force the listener to only use IPv6. If you want support for both IPv4 and
# IPv6, then do not use the socket_domain option.
#
#socket_domain
# Bind the listener to a specific interface. This is similar to
# the [ip address/host name] part of the listener definition, but is useful
# when an interface has multiple addresses or the address may change. If used
# with the [ip address/host name] part of the listener definition, then the
# bind_interface option will take priority.
# Not available on Windows.
#
# Example: bind_interface eth0
#bind_interface
# When a listener is using the websockets protocol, it is possible to serve
# http data as well. Set http_dir to a directory which contains the files you
# wish to serve. If this option is not specified, then no normal http
# connections will be possible.
#http_dir
# The maximum number of client connections to allow. This is
# a per listener setting.
# Default is -1, which means unlimited connections.
# Note that other process limits mean that unlimited connections
# are not really possible. Typically the default maximum number of
# connections possible is around 1024.
#max_connections -1
# The listener can be restricted to operating within a topic hierarchy using
# the mount_point option. This is achieved be prefixing the mount_point string
# to all topics for any clients connected to this listener. This prefixing only
# happens internally to the broker; the client will not see the prefix.
#mount_point
# Choose the protocol to use when listening.
# This can be either mqtt or websockets.
# Certificate based TLS may be used with websockets, except that only the
# cafile, certfile, keyfile, ciphers, and ciphers_tls13 options are supported.
#protocol mqtt
# Set use_username_as_clientid to true to replace the clientid that a client
# connected with with its username. This allows authentication to be tied to
# the clientid, which means that it is possible to prevent one client
# disconnecting another by using the same clientid.
# If a client connects with no username it will be disconnected as not
# authorised when this option is set to true.
# Do not use in conjunction with clientid_prefixes.
# See also use_identity_as_username.
# This does not apply globally, but on a per-listener basis.
#use_username_as_clientid
# Change the websockets headers size. This is a global option, it is not
# possible to set per listener. This option sets the size of the buffer used in
# the libwebsockets library when reading HTTP headers. If you are passing large
# header data such as cookies then you may need to increase this value. If left
# unset, or set to 0, then the default of 1024 bytes will be used.
#websockets_headers_size
# -----------------------------------------------------------------
# Certificate based SSL/TLS support
# -----------------------------------------------------------------
# The following options can be used to enable certificate based SSL/TLS support
# for this listener. Note that the recommended port for MQTT over TLS is 8883,
# but this must be set manually.
#
# See also the mosquitto-tls man page and the "Pre-shared-key based SSL/TLS
# support" section. Only one of certificate or PSK encryption support can be
# enabled for any listener.
# Both of certfile and keyfile must be defined to enable certificate based
# TLS encryption.
# Path to the PEM encoded server certificate.
#certfile
# Path to the PEM encoded keyfile.
#keyfile
# If you wish to control which encryption ciphers are used, use the ciphers
# option. The list of available ciphers can be optained using the "openssl
# ciphers" command and should be provided in the same format as the output of
# that command. This applies to TLS 1.2 and earlier versions only. Use
# ciphers_tls1.3 for TLS v1.3.
#ciphers
# Choose which TLS v1.3 ciphersuites are used for this listener.
# Defaults to "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256"
#ciphers_tls1.3
# If you have require_certificate set to true, you can create a certificate
# revocation list file to revoke access to particular client certificates. If
# you have done this, use crlfile to point to the PEM encoded revocation file.
#crlfile
# To allow the use of ephemeral DH key exchange, which provides forward
# security, the listener must load DH parameters. This can be specified with
# the dhparamfile option. The dhparamfile can be generated with the command
# e.g. "openssl dhparam -out dhparam.pem 2048"
#dhparamfile
# By default an TLS enabled listener will operate in a similar fashion to a
# https enabled web server, in that the server has a certificate signed by a CA
# and the client will verify that it is a trusted certificate. The overall aim
# is encryption of the network traffic. By setting require_certificate to true,
# the client must provide a valid certificate in order for the network
# connection to proceed. This allows access to the broker to be controlled
# outside of the mechanisms provided by MQTT.
#require_certificate false
# cafile and capath define methods of accessing the PEM encoded
# Certificate Authority certificates that will be considered trusted when
# checking incoming client certificates.
# cafile defines the path to a file containing the CA certificates.
# capath defines a directory that will be searched for files
# containing the CA certificates. For capath to work correctly, the
# certificate files must have ".crt" as the file ending and you must run
# "openssl rehash <path to capath>" each time you add/remove a certificate.
#cafile
#capath
# If require_certificate is true, you may set use_identity_as_username to true
# to use the CN value from the client certificate as a username. If this is
# true, the password_file option will not be used for this listener.
#use_identity_as_username false
# -----------------------------------------------------------------
# Pre-shared-key based SSL/TLS support
# -----------------------------------------------------------------
# The following options can be used to enable PSK based SSL/TLS support for
# this listener. Note that the recommended port for MQTT over TLS is 8883, but
# this must be set manually.
#
# See also the mosquitto-tls man page and the "Certificate based SSL/TLS
# support" section. Only one of certificate or PSK encryption support can be
# enabled for any listener.
# The psk_hint option enables pre-shared-key support for this listener and also
# acts as an identifier for this listener. The hint is sent to clients and may
# be used locally to aid authentication. The hint is a free form string that
# doesn't have much meaning in itself, so feel free to be creative.
# If this option is provided, see psk_file to define the pre-shared keys to be
# used or create a security plugin to handle them.
#psk_hint
# When using PSK, the encryption ciphers used will be chosen from the list of
# available PSK ciphers. If you want to control which ciphers are available,
# use the "ciphers" option. The list of available ciphers can be optained
# using the "openssl ciphers" command and should be provided in the same format
# as the output of that command.
#ciphers
# Set use_identity_as_username to have the psk identity sent by the client used
# as its username. Authentication will be carried out using the PSK rather than
# the MQTT username/password and so password_file will not be used for this
# listener.
#use_identity_as_username false
# =================================================================
# Persistence
# =================================================================
# If persistence is enabled, save the in-memory database to disk
# every autosave_interval seconds. If set to 0, the persistence
# database will only be written when mosquitto exits. See also
# autosave_on_changes.
# Note that writing of the persistence database can be forced by
# sending mosquitto a SIGUSR1 signal.
#autosave_interval 1800
# If true, mosquitto will count the number of subscription changes, retained
# messages received and queued messages and if the total exceeds
# autosave_interval then the in-memory database will be saved to disk.
# If false, mosquitto will save the in-memory database to disk by treating
# autosave_interval as a time in seconds.
#autosave_on_changes false
# Save persistent message data to disk (true/false).
# This saves information about all messages, including
# subscriptions, currently in-flight messages and retained
# messages.
# retained_persistence is a synonym for this option.
#persistence false
# The filename to use for the persistent database, not including
# the path.
#persistence_file mosquitto.db
# Location for persistent database.
# Default is an empty string (current directory).
# Set to e.g. /var/lib/mosquitto if running as a proper service on Linux or
# similar.
#persistence_location
# =================================================================
# Logging
# =================================================================
# Places to log to. Use multiple log_dest lines for multiple
# logging destinations.
# Possible destinations are: stdout stderr syslog topic file dlt
#
# stdout and stderr log to the console on the named output.
#
# syslog uses the userspace syslog facility which usually ends up
# in /var/log/messages or similar.
#
# topic logs to the broker topic '$SYS/broker/log/<severity>',
# where severity is one of D, E, W, N, I, M which are debug, error,
# warning, notice, information and message. Message type severity is used by
# the subscribe/unsubscribe log_types and publishes log messages to
# $SYS/broker/log/M/susbcribe or $SYS/broker/log/M/unsubscribe.
#
# The file destination requires an additional parameter which is the file to be
# logged to, e.g. "log_dest file /var/log/mosquitto.log". The file will be
# closed and reopened when the broker receives a HUP signal. Only a single file
# destination may be configured.
#
# The dlt destination is for the automotive `Diagnostic Log and Trace` tool.
# This requires that Mosquitto has been compiled with DLT support.
#
# Note that if the broker is running as a Windows service it will default to
# "log_dest none" and neither stdout nor stderr logging is available.
# Use "log_dest none" if you wish to disable logging.
#log_dest stderr
# Types of messages to log. Use multiple log_type lines for logging
# multiple types of messages.
# Possible types are: debug, error, warning, notice, information,
# none, subscribe, unsubscribe, websockets, all.
# Note that debug type messages are for decoding the incoming/outgoing
# network packets. They are not logged in "topics".
#log_type error
#log_type warning
#log_type notice
#log_type information
# If set to true, client connection and disconnection messages will be included
# in the log.
#connection_messages true
# If using syslog logging (not on Windows), messages will be logged to the
# "daemon" facility by default. Use the log_facility option to choose which of
# local0 to local7 to log to instead. The option value should be an integer
# value, e.g. "log_facility 5" to use local5.
#log_facility
# If set to true, add a timestamp value to each log message.
#log_timestamp true
# Set the format of the log timestamp. If left unset, this is the number of
# seconds since the Unix epoch.
# This is a free text string which will be passed to the strftime function. To
# get an ISO 8601 datetime, for example:
# log_timestamp_format %Y-%m-%dT%H:%M:%S
#log_timestamp_format
# Change the websockets logging level. This is a global option, it is not
# possible to set per listener. This is an integer that is interpreted by
# libwebsockets as a bit mask for its lws_log_levels enum. See the
# libwebsockets documentation for more details. "log_type websockets" must also
# be enabled.
#websockets_log_level 0
# =================================================================
# Security
# =================================================================
# If set, only clients that have a matching prefix on their
# clientid will be allowed to connect to the broker. By default,
# all clients may connect.
# For example, setting "secure-" here would mean a client "secure-
# client" could connect but another with clientid "mqtt" couldn't.
#clientid_prefixes
# Boolean value that determines whether clients that connect
# without providing a username are allowed to connect. If set to
# false then a password file should be created (see the
# password_file option) to control authenticated client access.
#
# Defaults to false, unless there are no listeners defined in the configuration
# file, in which case it is set to true, but connections are only allowed from
# the local machine.
allow_anonymous true
# -----------------------------------------------------------------
# Default authentication and topic access control
# -----------------------------------------------------------------
# Control access to the broker using a password file. This file can be
# generated using the mosquitto_passwd utility. If TLS support is not compiled
# into mosquitto (it is recommended that TLS support should be included) then
# plain text passwords are used, in which case the file should be a text file
# with lines in the format:
# username:password
# The password (and colon) may be omitted if desired, although this
# offers very little in the way of security.
#
# See the TLS client require_certificate and use_identity_as_username options
# for alternative authentication options. If a plugin is used as well as
# password_file, the plugin check will be made first.
#password_file
# Access may also be controlled using a pre-shared-key file. This requires
# TLS-PSK support and a listener configured to use it. The file should be text
# lines in the format:
# identity:key
# The key should be in hexadecimal format without a leading "0x".
# If an plugin is used as well, the plugin check will be made first.
#psk_file
# Control access to topics on the broker using an access control list
# file. If this parameter is defined then only the topics listed will
# have access.
# If the first character of a line of the ACL file is a # it is treated as a
# comment.
# Topic access is added with lines of the format:
#
# topic [read|write|readwrite|deny] <topic>
#
# The access type is controlled using "read", "write", "readwrite" or "deny".
# This parameter is optional (unless <topic> contains a space character) - if
# not given then the access is read/write. <topic> can contain the + or #
# wildcards as in subscriptions.
#
# The "deny" option can used to explicity deny access to a topic that would
# otherwise be granted by a broader read/write/readwrite statement. Any "deny"
# topics are handled before topics that grant read/write access.
#
# The first set of topics are applied to anonymous clients, assuming
# allow_anonymous is true. User specific topic ACLs are added after a
# user line as follows:
#
# user <username>
#
# The username referred to here is the same as in password_file. It is
# not the clientid.
#
#
# If is also possible to define ACLs based on pattern substitution within the
# topic. The patterns available for substition are:
#
# %c to match the client id of the client
# %u to match the username of the client
#
# The substitution pattern must be the only text for that level of hierarchy.
#
# The form is the same as for the topic keyword, but using pattern as the
# keyword.
# Pattern ACLs apply to all users even if the "user" keyword has previously
# been given.
#
# If using bridges with usernames and ACLs, connection messages can be allowed
# with the following pattern:
# pattern write $SYS/broker/connection/%c/state
#
# pattern [read|write|readwrite] <topic>
#
# Example:
#
# pattern write sensor/%u/data
#
# If an plugin is used as well as acl_file, the plugin check will be
# made first.
#acl_file
# -----------------------------------------------------------------
# External authentication and topic access plugin options
# -----------------------------------------------------------------
# External authentication and access control can be supported with the
# plugin option. This is a path to a loadable plugin. See also the
# plugin_opt_* options described below.
#
# The plugin option can be specified multiple times to load multiple
# plugins. The plugins will be processed in the order that they are specified
# here. If the plugin option is specified alongside either of
# password_file or acl_file then the plugin checks will be made first.
#
# If the per_listener_settings option is false, the plugin will be apply to all
# listeners. If per_listener_settings is true, then the plugin will apply to
# the current listener being defined only.
#
# This option is also available as `auth_plugin`, but this use is deprecated
# and will be removed in the future.
#
#plugin
# If the plugin option above is used, define options to pass to the
# plugin here as described by the plugin instructions. All options named
# using the format plugin_opt_* will be passed to the plugin, for example:
#
# This option is also available as `auth_opt_*`, but this use is deprecated
# and will be removed in the future.
#
# plugin_opt_db_host
# plugin_opt_db_port
# plugin_opt_db_username
# plugin_opt_db_password
# =================================================================
# Bridges
# =================================================================
# A bridge is a way of connecting multiple MQTT brokers together.
# Create a new bridge using the "connection" option as described below. Set
# options for the bridges using the remaining parameters. You must specify the
# address and at least one topic to subscribe to.
#
# Each connection must have a unique name.
#
# The address line may have multiple host address and ports specified. See
# below in the round_robin description for more details on bridge behaviour if
# multiple addresses are used. Note that if you use an IPv6 address, then you
# are required to specify a port.
#
# The direction that the topic will be shared can be chosen by
# specifying out, in or both, where the default value is out.
# The QoS level of the bridged communication can be specified with the next
# topic option. The default QoS level is 0, to change the QoS the topic
# direction must also be given.
#
# The local and remote prefix options allow a topic to be remapped when it is
# bridged to/from the remote broker. This provides the ability to place a topic
# tree in an appropriate location.
#
# For more details see the mosquitto.conf man page.
#
# Multiple topics can be specified per connection, but be careful
# not to create any loops.
#
# If you are using bridges with cleansession set to false (the default), then
# you may get unexpected behaviour from incoming topics if you change what
# topics you are subscribing to. This is because the remote broker keeps the
# subscription for the old topic. If you have this problem, connect your bridge
# with cleansession set to true, then reconnect with cleansession set to false
# as normal.
#connection <name>
#address <host>[:<port>] [<host>[:<port>]]
#topic <topic> [[[out | in | both] qos-level] local-prefix remote-prefix]
# If you need to have the bridge connect over a particular network interface,
# use bridge_bind_address to tell the bridge which local IP address the socket
# should bind to, e.g. `bridge_bind_address 192.168.1.10`
#bridge_bind_address
# If a bridge has topics that have "out" direction, the default behaviour is to
# send an unsubscribe request to the remote broker on that topic. This means
# that changing a topic direction from "in" to "out" will not keep receiving
# incoming messages. Sending these unsubscribe requests is not always
# desirable, setting bridge_attempt_unsubscribe to false will disable sending
# the unsubscribe request.
#bridge_attempt_unsubscribe true
# Set the version of the MQTT protocol to use with for this bridge. Can be one
# of mqttv50, mqttv311 or mqttv31. Defaults to mqttv311.
#bridge_protocol_version mqttv311
# Set the clean session variable for this bridge.
# When set to true, when the bridge disconnects for any reason, all
# messages and subscriptions will be cleaned up on the remote
# broker. Note that with cleansession set to true, there may be a
# significant amount of retained messages sent when the bridge
# reconnects after losing its connection.
# When set to false, the subscriptions and messages are kept on the
# remote broker, and delivered when the bridge reconnects.
#cleansession false
# Set the amount of time a bridge using the lazy start type must be idle before
# it will be stopped. Defaults to 60 seconds.
#idle_timeout 60
# Set the keepalive interval for this bridge connection, in
# seconds.
#keepalive_interval 60
# Set the clientid to use on the local broker. If not defined, this defaults to
# 'local.<clientid>'. If you are bridging a broker to itself, it is important
# that local_clientid and clientid do not match.
#local_clientid
# If set to true, publish notification messages to the local and remote brokers
# giving information about the state of the bridge connection. Retained
# messages are published to the topic $SYS/broker/connection/<clientid>/state
# unless the notification_topic option is used.
# If the message is 1 then the connection is active, or 0 if the connection has
# failed.
# This uses the last will and testament feature.
#notifications true
# Choose the topic on which notification messages for this bridge are
# published. If not set, messages are published on the topic
# $SYS/broker/connection/<clientid>/state
#notification_topic
# Set the client id to use on the remote end of this bridge connection. If not
# defined, this defaults to 'name.hostname' where name is the connection name
# and hostname is the hostname of this computer.
# This replaces the old "clientid" option to avoid confusion. "clientid"
# remains valid for the time being.
#remote_clientid
# Set the password to use when connecting to a broker that requires
# authentication. This option is only used if remote_username is also set.
# This replaces the old "password" option to avoid confusion. "password"
# remains valid for the time being.
#remote_password
# Set the username to use when connecting to a broker that requires
# authentication.
# This replaces the old "username" option to avoid confusion. "username"
# remains valid for the time being.
#remote_username
# Set the amount of time a bridge using the automatic start type will wait
# until attempting to reconnect.
# This option can be configured to use a constant delay time in seconds, or to
# use a backoff mechanism based on "Decorrelated Jitter", which adds a degree
# of randomness to when the restart occurs.
#
# Set a constant timeout of 20 seconds:
# restart_timeout 20
#
# Set backoff with a base (start value) of 10 seconds and a cap (upper limit) of
# 60 seconds:
# restart_timeout 10 30
#
# Defaults to jitter with a base of 5 and cap of 30
#restart_timeout 5 30
# If the bridge has more than one address given in the address/addresses
# configuration, the round_robin option defines the behaviour of the bridge on
# a failure of the bridge connection. If round_robin is false, the default
# value, then the first address is treated as the main bridge connection. If
# the connection fails, the other secondary addresses will be attempted in
# turn. Whilst connected to a secondary bridge, the bridge will periodically
# attempt to reconnect to the main bridge until successful.
# If round_robin is true, then all addresses are treated as equals. If a
# connection fails, the next address will be tried and if successful will
# remain connected until it fails
#round_robin false
# Set the start type of the bridge. This controls how the bridge starts and
# can be one of three types: automatic, lazy and once. Note that RSMB provides
# a fourth start type "manual" which isn't currently supported by mosquitto.
#
# "automatic" is the default start type and means that the bridge connection
# will be started automatically when the broker starts and also restarted
# after a short delay (30 seconds) if the connection fails.
#
# Bridges using the "lazy" start type will be started automatically when the
# number of queued messages exceeds the number set with the "threshold"
# parameter. It will be stopped automatically after the time set by the
# "idle_timeout" parameter. Use this start type if you wish the connection to
# only be active when it is needed.
#
# A bridge using the "once" start type will be started automatically when the
# broker starts but will not be restarted if the connection fails.
#start_type automatic
# Set the number of messages that need to be queued for a bridge with lazy
# start type to be restarted. Defaults to 10 messages.
# Must be less than max_queued_messages.
#threshold 10
# If try_private is set to true, the bridge will attempt to indicate to the
# remote broker that it is a bridge not an ordinary client. If successful, this
# means that loop detection will be more effective and that retained messages
# will be propagated correctly. Not all brokers support this feature so it may
# be necessary to set try_private to false if your bridge does not connect
# properly.
#try_private true
# Some MQTT brokers do not allow retained messages. MQTT v5 gives a mechanism
# for brokers to tell clients that they do not support retained messages, but
# this is not possible for MQTT v3.1.1 or v3.1. If you need to bridge to a
# v3.1.1 or v3.1 broker that does not support retained messages, set the
# bridge_outgoing_retain option to false. This will remove the retain bit on
# all outgoing messages to that bridge, regardless of any other setting.
#bridge_outgoing_retain true
# If you wish to restrict the size of messages sent to a remote bridge, use the
# bridge_max_packet_size option. This sets the maximum number of bytes for
# the total message, including headers and payload.
# Note that MQTT v5 brokers may provide their own maximum-packet-size property.
# In this case, the smaller of the two limits will be used.
# Set to 0 for "unlimited".
#bridge_max_packet_size 0
# -----------------------------------------------------------------
# Certificate based SSL/TLS support
# -----------------------------------------------------------------
# Either bridge_cafile or bridge_capath must be defined to enable TLS support
# for this bridge.
# bridge_cafile defines the path to a file containing the
# Certificate Authority certificates that have signed the remote broker
# certificate.
# bridge_capath defines a directory that will be searched for files containing
# the CA certificates. For bridge_capath to work correctly, the certificate
# files must have ".crt" as the file ending and you must run "openssl rehash
# <path to capath>" each time you add/remove a certificate.
#bridge_cafile
#bridge_capath
# If the remote broker has more than one protocol available on its port, e.g.
# MQTT and WebSockets, then use bridge_alpn to configure which protocol is
# requested. Note that WebSockets support for bridges is not yet available.
#bridge_alpn
# When using certificate based encryption, bridge_insecure disables
# verification of the server hostname in the server certificate. This can be
# useful when testing initial server configurations, but makes it possible for
# a malicious third party to impersonate your server through DNS spoofing, for
# example. Use this option in testing only. If you need to resort to using this
# option in a production environment, your setup is at fault and there is no
# point using encryption.
#bridge_insecure false
# Path to the PEM encoded client certificate, if required by the remote broker.
#bridge_certfile
# Path to the PEM encoded client private key, if required by the remote broker.
#bridge_keyfile
# -----------------------------------------------------------------
# PSK based SSL/TLS support
# -----------------------------------------------------------------
# Pre-shared-key encryption provides an alternative to certificate based
# encryption. A bridge can be configured to use PSK with the bridge_identity
# and bridge_psk options. These are the client PSK identity, and pre-shared-key
# in hexadecimal format with no "0x". Only one of certificate and PSK based
# encryption can be used on one
# bridge at once.
#bridge_identity
#bridge_psk
# =================================================================
# External config files
# =================================================================
# External configuration files may be included by using the
# include_dir option. This defines a directory that will be searched
# for config files. All files that end in '.conf' will be loaded as
# a configuration file. It is best to have this as the last option
# in the main file. This option will only be processed from the main
# configuration file. The directory specified must not contain the
# main configuration file.
# Files within include_dir will be loaded sorted in case-sensitive
# alphabetical order, with capital letters ordered first. If this option is
# given multiple times, all of the files from the first instance will be
# processed before the next instance. See the man page for examples.
#include_dir
@@ -0,0 +1,73 @@
mqtt:
# The MQTT broker to connect to
server: mqtt://mosquitto:1883
# Optional: Username and Password for authenticating with the MQTT Server
# Optional: for TLS client certificates
# Optional: Used to specify ClientID. The default is <hostname>-<pid>
# The Topic path to subscribe to. Be aware that you have to specify the wildcard, if you want to follow topics for multiple sensors.
topic_path: shellies/+/#
device_id_regex: "shellies/(?P<deviceid>.*)/.*"
metric_per_topic_config:
metric_name_regex: "shellies/(?P<deviceid>.*)/(?P<metricname>.*)"
# Optional: Regular expression to extract the device ID from the topic path. The default regular expression, assumes
# that the last "element" of the topic_path is the device id.
# The regular expression must contain a named capture group with the name deviceid
# For example the expression for tasamota based sensors is "tele/(?P<deviceid>.*)/.*"
# device_id_regex: "(.*/)?(shelly-(?P<deviceid>.*?)/)"
# The MQTT QoS level
qos: 0
# NOTE: Only one of metric_per_topic_config or object_per_topic_config should be specified in the configuration
# Optional: Configures mqtt2prometheus to expect a single metric to be published as the value on an mqtt topic.
#metric_per_topic_config:
# A regex used for extracting the metric name from the topic. Must contain a named group for `metricname`.
#metric_name_regex: "(.*/)?(?P<metricname>.*)"
# Optional: Configures mqtt2prometheus to expect an object containing multiple metrics to be published as the value on an mqtt topic.
# This is the default.
#object_per_topic_config:
# The encoding of the object, currently only json is supported
# encoding: json
cache:
# Timeout. Each received metric will be presented for this time if no update is send via MQTT.
# Set the timeout to -1 to disable the deletion of metrics from the cache. The exporter presents the ingest timestamp
# to prometheus.
timeout: 24h
json_parsing:
# Separator. Used to split path to elements when accessing json fields.
# You can access json fields with dots in it. F.E. {"key.name": {"nested": "value"}}
# Just set separator to -> and use key.name->nested as mqtt_name
separator: .
# This is a list of valid metrics. Only metrics listed here will be exported
metrics:
# The name of the metric in prometheus
- prom_name: temperature
# The name of the metric in a MQTT JSON message
mqtt_name: temperature
# The prometheus help text for this metric
help: The temperature of the Shelly sensor
# The prometheus type for this metric. Valid values are: "gauge" and "counter"
type: gauge
# A map of string to string for constant labels. This labels will be attached to every prometheus metric
const_labels:
sensor_type: shelly
- prom_name: power
# The name of the metric in a MQTT JSON message
mqtt_name: power
# The scale of the metric in a MQTT JSON message (prom_value = mqtt_value * scale)
# The prometheus help text for this metric
help: The current power draw of the attached device
# The prometheus type for this metric. Valid values are: "gauge" and "counter"
type: gauge
const_labels:
sensor_type: shelly
#A map of string to string for constant labels. This labels will be attached to every prometheus metric
- prom_name: state
mqtt_name: 0
help: The current state the relay is in
type: gauge
const_labels:
sensor_type: shelly
string_value_mapping:
map:
off: 0
on: 1
error_value: 0
@@ -0,0 +1,15 @@
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
# - "first.rules"
# - "second.rules"
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ['localhost:9090']
- job_name: shellies
static_configs:
- targets: ['mqtt2prom:9641']