Added network provider in Information tab
This commit is contained in:
parent
c102ab3335
commit
98abd68366
|
|
@ -753,6 +753,16 @@ public class Controller : ControllerBase
|
||||||
return installation.HideParentIfUserHasNoAccessToParent(session!.User);
|
return installation.HideParentIfUserHasNoAccessToParent(session!.User);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet(nameof(GetNetworkProviders))]
|
||||||
|
public ActionResult<IReadOnlyList<string>> GetNetworkProviders(Token authToken)
|
||||||
|
{
|
||||||
|
var session = Db.GetSession(authToken);
|
||||||
|
if (session is null)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
return Ok(NetworkProviderService.GetProviders());
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost(nameof(AcknowledgeError))]
|
[HttpPost(nameof(AcknowledgeError))]
|
||||||
public ActionResult AcknowledgeError(Int64 id, Token authToken)
|
public ActionResult AcknowledgeError(Int64 id, Token authToken)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -54,4 +54,5 @@ public class Installation : TreeNode
|
||||||
public String OrderNumbers { get; set; }
|
public String OrderNumbers { get; set; }
|
||||||
public String VrmLink { get; set; } = "";
|
public String VrmLink { get; set; } = "";
|
||||||
public string Configuration { get; set; } = "";
|
public string Configuration { get; set; } = "";
|
||||||
|
public string NetworkProvider { get; set; } = "";
|
||||||
}
|
}
|
||||||
|
|
@ -28,6 +28,7 @@ public static class Program
|
||||||
LoadEnvFile();
|
LoadEnvFile();
|
||||||
DiagnosticService.Initialize();
|
DiagnosticService.Initialize();
|
||||||
TicketDiagnosticService.Initialize();
|
TicketDiagnosticService.Initialize();
|
||||||
|
NetworkProviderService.Initialize();
|
||||||
AlarmReviewService.StartDailyScheduler();
|
AlarmReviewService.StartDailyScheduler();
|
||||||
// DailyIngestionService.StartScheduler(); // Phase 2: enable when S3 auto-push is ready
|
// DailyIngestionService.StartScheduler(); // Phase 2: enable when S3 auto-push is ready
|
||||||
// ReportAggregationService.StartScheduler(); // Phase 2: enable when scheduler should auto-run monthly/yearly
|
// ReportAggregationService.StartScheduler(); // Phase 2: enable when scheduler should auto-run monthly/yearly
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
using Flurl.Http;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace InnovEnergy.App.Backend.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches and caches the list of Swiss electricity network providers (Netzbetreiber)
|
||||||
|
/// from the ELCOM/LINDAS SPARQL endpoint. Refreshes every 24 hours.
|
||||||
|
/// </summary>
|
||||||
|
public static class NetworkProviderService
|
||||||
|
{
|
||||||
|
private static IReadOnlyList<string> _providers = Array.Empty<string>();
|
||||||
|
private static Timer? _refreshTimer;
|
||||||
|
|
||||||
|
private const string SparqlEndpoint = "https://ld.admin.ch/query";
|
||||||
|
|
||||||
|
private const string SparqlQuery = @"
|
||||||
|
PREFIX schema: <http://schema.org/>
|
||||||
|
SELECT DISTINCT ?name
|
||||||
|
FROM <https://lindas.admin.ch/elcom/electricityprice>
|
||||||
|
WHERE {
|
||||||
|
?operator a schema:Organization ;
|
||||||
|
schema:name ?name .
|
||||||
|
}
|
||||||
|
ORDER BY ?name";
|
||||||
|
|
||||||
|
public static void Initialize()
|
||||||
|
{
|
||||||
|
// Fire-and-forget initial load
|
||||||
|
Task.Run(RefreshAsync);
|
||||||
|
|
||||||
|
// Refresh every 24 hours
|
||||||
|
_refreshTimer = new Timer(
|
||||||
|
_ => Task.Run(RefreshAsync),
|
||||||
|
null,
|
||||||
|
TimeSpan.FromHours(24),
|
||||||
|
TimeSpan.FromHours(24)
|
||||||
|
);
|
||||||
|
|
||||||
|
Console.WriteLine("[NetworkProviderService] initialised.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyList<string> GetProviders() => _providers;
|
||||||
|
|
||||||
|
private static async Task RefreshAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await SparqlEndpoint
|
||||||
|
.WithHeader("Accept", "application/sparql-results+json")
|
||||||
|
.PostUrlEncodedAsync(new { query = SparqlQuery });
|
||||||
|
|
||||||
|
var json = await response.GetStringAsync();
|
||||||
|
var parsed = JObject.Parse(json);
|
||||||
|
|
||||||
|
var names = parsed["results"]?["bindings"]?
|
||||||
|
.Select(b => b["name"]?["value"]?.ToString())
|
||||||
|
.Where(n => !string.IsNullOrWhiteSpace(n))
|
||||||
|
.Distinct()
|
||||||
|
.OrderBy(n => n)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (names is { Count: > 0 })
|
||||||
|
{
|
||||||
|
_providers = names!;
|
||||||
|
Console.WriteLine($"[NetworkProviderService] Loaded {names.Count} providers from ELCOM.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("[NetworkProviderService] SPARQL query returned no results.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"[NetworkProviderService] Failed to fetch providers: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
|
Autocomplete,
|
||||||
Box,
|
Box,
|
||||||
CardContent,
|
CardContent,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
|
|
@ -14,13 +15,14 @@ import {
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import { Close as CloseIcon } from '@mui/icons-material';
|
import { Close as CloseIcon } from '@mui/icons-material';
|
||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { I_S3Credentials } from '../../../interfaces/S3Types';
|
import { I_S3Credentials } from '../../../interfaces/S3Types';
|
||||||
import { I_Installation } from '../../../interfaces/InstallationTypes';
|
import { I_Installation } from '../../../interfaces/InstallationTypes';
|
||||||
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
|
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
|
||||||
import { UserContext } from '../../../contexts/userContext';
|
import { UserContext } from '../../../contexts/userContext';
|
||||||
import { UserType } from '../../../interfaces/UserTypes';
|
import { UserType } from '../../../interfaces/UserTypes';
|
||||||
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
||||||
|
import axiosConfig from '../../../Resources/axiosConfig';
|
||||||
|
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
|
@ -53,6 +55,24 @@ function Information(props: InformationProps) {
|
||||||
deleteInstallation
|
deleteInstallation
|
||||||
} = installationContext;
|
} = installationContext;
|
||||||
|
|
||||||
|
const canEdit = currentUser.userType == UserType.admin;
|
||||||
|
const isPartner = currentUser.userType == UserType.partner;
|
||||||
|
const isSodistore = formValues.product === 3 || formValues.product === 4;
|
||||||
|
|
||||||
|
const [networkProviders, setNetworkProviders] = useState<string[]>([]);
|
||||||
|
const [loadingProviders, setLoadingProviders] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSodistore) {
|
||||||
|
setLoadingProviders(true);
|
||||||
|
axiosConfig
|
||||||
|
.get('/GetNetworkProviders')
|
||||||
|
.then((res) => setNetworkProviders(res.data))
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => setLoadingProviders(false));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
setFormValues({
|
setFormValues({
|
||||||
|
|
@ -286,6 +306,54 @@ function Information(props: InformationProps) {
|
||||||
error={formValues.country === ''}
|
error={formValues.country === ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{isSodistore && (
|
||||||
|
<div>
|
||||||
|
<Autocomplete
|
||||||
|
freeSolo
|
||||||
|
options={networkProviders}
|
||||||
|
value={formValues.networkProvider || ''}
|
||||||
|
onChange={(_e, val) =>
|
||||||
|
setFormValues({
|
||||||
|
...formValues,
|
||||||
|
networkProvider: (val as string) || ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onInputChange={(_e, val) =>
|
||||||
|
setFormValues({
|
||||||
|
...formValues,
|
||||||
|
networkProvider: val || ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={!canEdit && !isPartner}
|
||||||
|
loading={loadingProviders}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="networkProvider"
|
||||||
|
defaultMessage="Network Provider"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
InputProps={{
|
||||||
|
...params.InputProps,
|
||||||
|
endAdornment: (
|
||||||
|
<>
|
||||||
|
{loadingProviders ? (
|
||||||
|
<CircularProgress size={20} />
|
||||||
|
) : null}
|
||||||
|
{params.InputProps.endAdornment}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
label={
|
label={
|
||||||
|
|
@ -341,7 +409,7 @@ function Information(props: InformationProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{currentUser.userType == UserType.admin && (
|
{canEdit && (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
@ -400,7 +468,7 @@ function Information(props: InformationProps) {
|
||||||
marginTop: 10
|
marginTop: 10
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currentUser.userType == UserType.admin && (
|
{canEdit && (
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
|
|
@ -414,7 +482,7 @@ function Information(props: InformationProps) {
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{currentUser.userType == UserType.admin && (
|
{(canEdit || (isPartner && isSodistore)) && (
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
|
Autocomplete,
|
||||||
Box,
|
Box,
|
||||||
CardContent,
|
CardContent,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
|
|
@ -26,6 +27,7 @@ import { UserContext } from '../../../contexts/userContext';
|
||||||
import routes from '../../../Resources/routes.json';
|
import routes from '../../../Resources/routes.json';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { UserType } from '../../../interfaces/UserTypes';
|
import { UserType } from '../../../interfaces/UserTypes';
|
||||||
|
import axiosConfig from '../../../Resources/axiosConfig';
|
||||||
|
|
||||||
interface InformationSodistorehomeProps {
|
interface InformationSodistorehomeProps {
|
||||||
values: I_Installation;
|
values: I_Installation;
|
||||||
|
|
@ -178,6 +180,18 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
||||||
const canEdit = currentUser.userType === UserType.admin;
|
const canEdit = currentUser.userType === UserType.admin;
|
||||||
const isPartner = currentUser.userType === UserType.partner;
|
const isPartner = currentUser.userType === UserType.partner;
|
||||||
|
|
||||||
|
const [networkProviders, setNetworkProviders] = useState<string[]>([]);
|
||||||
|
const [loadingProviders, setLoadingProviders] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoadingProviders(true);
|
||||||
|
axiosConfig
|
||||||
|
.get('/GetNetworkProviders')
|
||||||
|
.then((res) => setNetworkProviders(res.data))
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => setLoadingProviders(false));
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{openModalDeleteInstallation && (
|
{openModalDeleteInstallation && (
|
||||||
|
|
@ -361,6 +375,52 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Autocomplete
|
||||||
|
freeSolo
|
||||||
|
options={networkProviders}
|
||||||
|
value={formValues.networkProvider || ''}
|
||||||
|
onChange={(_e, val) =>
|
||||||
|
setFormValues({
|
||||||
|
...formValues,
|
||||||
|
networkProvider: (val as string) || ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onInputChange={(_e, val) =>
|
||||||
|
setFormValues({
|
||||||
|
...formValues,
|
||||||
|
networkProvider: val || ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={!canEdit && !isPartner}
|
||||||
|
loading={loadingProviders}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="networkProvider"
|
||||||
|
defaultMessage="Network Provider"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
InputProps={{
|
||||||
|
...params.InputProps,
|
||||||
|
endAdornment: (
|
||||||
|
<>
|
||||||
|
{loadingProviders ? (
|
||||||
|
<CircularProgress size={20} />
|
||||||
|
) : null}
|
||||||
|
{params.InputProps.endAdornment}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ export interface I_Installation extends I_S3Credentials {
|
||||||
testingMode?: boolean;
|
testingMode?: boolean;
|
||||||
status?: number;
|
status?: number;
|
||||||
serialNumber?: string;
|
serialNumber?: string;
|
||||||
|
networkProvider: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface I_Folder {
|
export interface I_Folder {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
"alarms": "Alarme",
|
"alarms": "Alarme",
|
||||||
"applyChanges": "Änderungen speichern",
|
"applyChanges": "Änderungen speichern",
|
||||||
"country": "Land",
|
"country": "Land",
|
||||||
|
"networkProvider": "Netzbetreiber",
|
||||||
"createNewFolder": "Neuer Ordner",
|
"createNewFolder": "Neuer Ordner",
|
||||||
"createNewUser": "Neuer Benutzer",
|
"createNewUser": "Neuer Benutzer",
|
||||||
"customerName": "Kundenname",
|
"customerName": "Kundenname",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
"allInstallations": "All installations",
|
"allInstallations": "All installations",
|
||||||
"applyChanges": "Apply changes",
|
"applyChanges": "Apply changes",
|
||||||
"country": "Country",
|
"country": "Country",
|
||||||
|
"networkProvider": "Network Provider",
|
||||||
"customerName": "Customer name",
|
"customerName": "Customer name",
|
||||||
"english": "English",
|
"english": "English",
|
||||||
"german": "German",
|
"german": "German",
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
"alarms": "Alarmes",
|
"alarms": "Alarmes",
|
||||||
"applyChanges": "Appliquer",
|
"applyChanges": "Appliquer",
|
||||||
"country": "Pays",
|
"country": "Pays",
|
||||||
|
"networkProvider": "Gestionnaire de réseau",
|
||||||
"createNewFolder": "Nouveau dossier",
|
"createNewFolder": "Nouveau dossier",
|
||||||
"createNewUser": "Nouvel utilisateur",
|
"createNewUser": "Nouvel utilisateur",
|
||||||
"customerName": "Nom du client",
|
"customerName": "Nom du client",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
"allInstallations": "Tutte le installazioni",
|
"allInstallations": "Tutte le installazioni",
|
||||||
"applyChanges": "Applica modifiche",
|
"applyChanges": "Applica modifiche",
|
||||||
"country": "Paese",
|
"country": "Paese",
|
||||||
|
"networkProvider": "Gestore di rete",
|
||||||
"customerName": "Nome cliente",
|
"customerName": "Nome cliente",
|
||||||
"english": "Inglese",
|
"english": "Inglese",
|
||||||
"german": "Tedesco",
|
"german": "Tedesco",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue