Fixed exoscale keys

This commit is contained in:
Noe 2023-11-20 17:29:45 +01:00
parent 6a18e56cf7
commit ac8f874255
14 changed files with 407 additions and 209 deletions

View File

@ -101,6 +101,23 @@ public class Controller : ControllerBase
.ToList();
}
[HttpGet(nameof(GetAllWarningsForInstallation))]
public ActionResult<IEnumerable<Warning>> GetAllWarningsForInstallation(Int64 id, Token authToken)
{
var user = Db.GetSession(authToken)?.User;
if (user == null)
return Unauthorized();
var installation = Db.GetInstallationById(id);
if (installation is null || !user.HasAccessTo(installation))
return Unauthorized();
return Db.Warnings
.Where(error => error.InstallationId == id)
.ToList();
}
[HttpGet(nameof(GetUserById))]
public ActionResult<User> GetUserById(Int64 id, Token authToken)
{

View File

@ -2,13 +2,22 @@ using SQLite;
namespace InnovEnergy.App.Backend.DataTypes;
public class Error
public abstract class LogEntry
{
[PrimaryKey, AutoIncrement]
public Int64 Id { get; set; }
public Int64 InstallationId { get; set; }
public String ErrorDescription { get; set; } = null!;
public String Description { get; set; } = null!;
public DateTime CreatedAt { get; set; }
public String DeviceCreatedTheError { get; set; } = null!;
public String DeviceCreatedTheMessage { get; set; } = null!;
public Boolean Seen { get; set; }
}
// Derived class for errors
public class Error : LogEntry
{
}
public class Warning : LogEntry
{
}

View File

@ -13,11 +13,8 @@ namespace InnovEnergy.App.Backend.DataTypes.Methods;
public static class ExoCmd
{
private const String Key = "EXOea18f5a82bd358896154c783";
private const String Secret = "lYtzU7R5e0L6XKOgBaLVPFr41nEBDxDdXU47zBAEI6M";
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
public static readonly S3Credentials? S3Creds = JsonSerializer.Deserialize<S3Credentials>(File.OpenRead("./Resources/exoscaleS3.json"));
public static readonly S3Credentials S3Credentials = JsonSerializer.Deserialize<S3Credentials>(File.OpenRead("./Resources/exoscaleS3.json"))!;
private static Byte[] HmacSha256Digest(String message, String secret)
{
@ -47,7 +44,7 @@ public static class ExoCmd
//Console.WriteLine("Message to sign:\n" + messageToSign);
var hmac = HmacSha256Digest(messageToSign, Secret);
var hmac = HmacSha256Digest(messageToSign, S3Credentials.Secret);
return Convert.ToBase64String(hmac);
}
@ -76,7 +73,7 @@ public static class ExoCmd
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
var authheader = "credential="+Key+",signed-query-args="+",expires="+unixtime+",signature="+BuildSignature("GET", method, unixtime);
var authheader = "credential="+S3Credentials.Key+",signed-query-args="+",expires="+unixtime+",signature="+BuildSignature("GET", method, unixtime);
var client = new HttpClient();
@ -92,10 +89,12 @@ public static class ExoCmd
{
var url = "https://api-ch-dk-2.exoscale.com/v2/api-key";
var method = "api-key";
var contentString = $$"""{"role-id": "{{roleName}}", "name":"{{installation.BucketName()}}v2"}""";
var contentString = $$"""{"role-id": "{{roleName}}", "name":"{{installation.BucketName()}}"}""";
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 60;
var authheader = "credential=" + Key + ",expires=" + unixtime + ",signature=" +
var authheader = "credential=" + S3Credentials.Key + ",expires=" + unixtime + ",signature=" +
BuildSignature("POST", method, contentString, unixtime);
var client = new HttpClient();
@ -143,7 +142,7 @@ public static class ExoCmd
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
var authheader = "credential="+Key+",signed-query-args="+",expires="+unixtime+",signature="+BuildSignature("POST", method, contentString, unixtime);
var authheader = "credential="+S3Credentials.Key+",signed-query-args="+",expires="+unixtime+",signature="+BuildSignature("POST", method, contentString, unixtime);
var client = new HttpClient();
@ -171,7 +170,7 @@ public static class ExoCmd
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
var authheader = "credential="+Key+",expires="+unixtime+",signature="+BuildSignature("DELETE", method, unixtime);
var authheader = "credential="+S3Credentials.Key+",expires="+unixtime+",signature="+BuildSignature("DELETE", method, unixtime);
var client = new HttpClient();
@ -220,7 +219,7 @@ public static class ExoCmd
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
var authheader = "credential="+Key+",signed-query-args="+",expires="+unixtime+",signature="+BuildSignature("POST", method, contentString, unixtime);
var authheader = "credential="+S3Credentials.Key+",signed-query-args="+",expires="+unixtime+",signature="+BuildSignature("POST", method, contentString, unixtime);
var client = new HttpClient();
@ -245,7 +244,7 @@ public static class ExoCmd
public static async Task<Boolean> CreateBucket(this Installation installation)
{
var s3Region = new S3Region($"https://{installation.S3Region}.{installation.S3Provider}", S3Creds!);
var s3Region = new S3Region($"https://{installation.S3Region}.{installation.S3Provider}", S3Credentials!);
return await s3Region.PutBucket(installation.BucketName()) != null;
}
@ -272,7 +271,7 @@ public static class ExoCmd
// return result.ExitCode == 200;
var s3Region = new S3Region($"https://{installation.S3Region}.{installation.S3Provider}", S3Creds!);
var s3Region = new S3Region($"https://{installation.S3Region}.{installation.S3Provider}", S3Credentials!);
var url = s3Region.Bucket(installation.BucketName()).Path("config.json");
return await url.PutObject(config);

View File

@ -23,7 +23,8 @@ public static class InstallationMethods
public static async Task<Boolean> RenewS3Credentials(this Installation installation)
{
if(installation.S3Key != "") await installation.RevokeReadKey();
if(!installation.S3Key.IsNullOrEmpty())
await installation.RevokeReadKey();
var (key,secret) = await installation.CreateReadKey();

View File

@ -25,6 +25,11 @@ public static partial class Db
return Insert(error);
}
public static Boolean Create(Warning warning)
{
return Insert(warning);
}
public static Boolean Create(Folder folder)
{
return Insert(folder);
@ -80,4 +85,30 @@ public static partial class Db
Create(newError);
}
}
public static void HandleWarning(Warning newWarning,int installationId)
{
//Find the total number of warnings for this installation
var totalWarnings = Warnings.Count(warning => warning.InstallationId == installationId);
//If there are 100 warnings, remove the one with the oldest timestamp
if (totalWarnings == 100)
{
var oldestWarning =
Warnings.Where(warning => warning.InstallationId == installationId)
.OrderBy(warning => warning.CreatedAt)
.FirstOrDefault();
//Remove the old error
Delete(oldestWarning);
//Add the new error
Create(newWarning);
}
else
{
Console.WriteLine("---------------Added the new Error to the database-----------------");
Create(newWarning);
}
}
}

View File

@ -12,7 +12,6 @@ using SQLiteConnection = SQLite.SQLiteConnection;
namespace InnovEnergy.App.Backend.Database;
public static partial class Db
{
private static SQLiteConnection Connection { get; } = InitConnection();
@ -36,6 +35,7 @@ public static partial class Db
fileConnection.CreateTable<Session>();
fileConnection.CreateTable<OrderNumber2Installation>();
fileConnection.CreateTable<Error>();
fileConnection.CreateTable<Warning>();
return CopyDbToMemory(fileConnection);
}
@ -53,6 +53,7 @@ public static partial class Db
memoryConnection.CreateTable<Session>();
memoryConnection.CreateTable<OrderNumber2Installation>();
memoryConnection.CreateTable<Error>();
memoryConnection.CreateTable<Warning>();
//Copy all the existing tables from the disk to main memory
fileConnection.Table<Session>().ForEach(memoryConnection.Insert);
@ -63,6 +64,7 @@ public static partial class Db
fileConnection.Table<InstallationAccess>().ForEach(memoryConnection.Insert);
fileConnection.Table<OrderNumber2Installation>().ForEach(memoryConnection.Insert);
fileConnection.Table<Error>().ForEach(memoryConnection.Insert);
fileConnection.Table<Warning>().ForEach(memoryConnection.Insert);
return memoryConnection;
}
@ -81,8 +83,10 @@ public static partial class Db
public static TableQuery<InstallationAccess> InstallationAccess => Connection.Table<InstallationAccess>();
public static TableQuery<OrderNumber2Installation> OrderNumber2Installation => Connection.Table<OrderNumber2Installation>();
public static TableQuery<Error> Errors => Connection.Table<Error>();
public static TableQuery<Warning> Warnings => Connection.Table<Warning>();
public static void Init(){
public static void Init()
{
// used to force static constructor
//Since this class is static, we call Init method from the Program.cs to initialize all the fields of the class
}
@ -100,16 +104,46 @@ public static partial class Db
Connection.CreateTable<Session>();
Connection.CreateTable<OrderNumber2Installation>();
Connection.CreateTable<Error>();
Connection.CreateTable<Warning>();
});
Observable.Interval(TimeSpan.FromHours(0.5))
.StartWith(0) // Do it right away (on startup)
.ObserveOn(TaskPoolScheduler.Default)
.SubscribeOn(TaskPoolScheduler.Default)
.SelectMany(Cleanup)
.Subscribe();
UpdateKeys().SupressAwaitWarning();
CleanupSessions().SupressAwaitWarning();
}
private static async Task CleanupSessions()
{
while (true)
{
try
{
DeleteStaleSessions();
}
catch(Exception e)
{
Console.WriteLine("An error has occured when cleaning stale sessions, exception is:\n"+e);
}
await Task.Delay(TimeSpan.FromHours(0.5));
}
}
private static async Task UpdateKeys()
{
while (true)
{
try
{
await UpdateS3Urls();
}
catch(Exception e)
{
Console.WriteLine("An error has occured when updating S3 keys, exception is:\n"+e);
}
await Task.Delay(TimeSpan.FromHours(24));
}
}
private static Boolean RunTransaction(Func<Boolean> func)
@ -133,13 +167,6 @@ public static partial class Db
}
private static async Task<Boolean> Cleanup(Int64 _)
{
await UpdateS3Urls();
DeleteStaleSessions();
return true;
}
private static void DeleteStaleSessions()
{
var deadline = DateTime.Now.AddDays((-1) * Session.MaxAge.Days);
@ -157,7 +184,7 @@ public static partial class Db
foreach (var region in regions)
{
var s3Region = new S3Region($"https://{region}.{provider}", ExoCmd.S3Creds!);
var s3Region = new S3Region($"https://{region}.{provider}", ExoCmd.S3Credentials!);
var bucketList = await s3Region.ListAllBuckets();
var installations = from bucket in bucketList.Buckets

View File

@ -49,6 +49,20 @@ public static partial class Db
}
}
public static Boolean Delete(Warning warningToDelete)
{
var deleteSuccess = RunTransaction(DeleteWarning);
if (deleteSuccess)
BackupDatabase();
return deleteSuccess;
Boolean DeleteWarning()
{
return Warnings.Delete(error => error.Id == warningToDelete.Id) >0;
}
}
public static Boolean Delete(Installation installation)
{
var deleteSuccess = RunTransaction(DeleteInstallationAndItsDependencies);

View File

@ -4,6 +4,6 @@ public class StatusMessage
public required int InstallationId { get; init; }
public required int Status { get; init; }
public DateTime CreatedAt { get; set; }
public String Error { get; init; } = null!;
public String DeviceCreatedTheError { get; init; } = null!;
public String Description { get; init; } = null!;
public String CreatedBy { get; init; } = null!;
}

View File

@ -99,15 +99,30 @@ public static class WebsocketManager
Error newError = new Error
{
InstallationId = receivedStatusMessage.InstallationId,
ErrorDescription = receivedStatusMessage.Error,
Description = receivedStatusMessage.Description,
CreatedAt = receivedStatusMessage.CreatedAt,
DeviceCreatedTheError = receivedStatusMessage.DeviceCreatedTheError,
DeviceCreatedTheMessage = receivedStatusMessage.CreatedBy,
Seen = false
};
//Create a new error and add it to the database
Db.HandleError(newError,receivedStatusMessage.InstallationId);
}
else if (receivedStatusMessage.Status==1)
{
Console.WriteLine("-----------------------New warning-----------------------");
Warning newWarning = new Warning
{
InstallationId = receivedStatusMessage.InstallationId,
Description = receivedStatusMessage.Description,
CreatedAt = receivedStatusMessage.CreatedAt,
DeviceCreatedTheMessage = receivedStatusMessage.CreatedBy,
Seen = false
};
//Create a new error and add it to the database
Db.HandleWarning(newWarning,receivedStatusMessage.InstallationId);
}
if (!InstallationConnections.ContainsKey(installationId))
{

View File

@ -4,9 +4,9 @@ namespace InnovEnergy.App.SaliMax.MiddlewareClasses;
public class StatusMessage
{
public required int InstallationId { get; init; }
public required int Status { get; init; }
public required int InstallationId { get; set; }
public required int Status { get; set; }
public DateTime CreatedAt { get; set; }
public String Error { get; set; } = null!;
public String DeviceCreatedTheError { get; set; } = null!;
public String Description { get; set; } = null!;
public String CreatedBy { get; set; } = null!;
}

View File

@ -357,8 +357,14 @@ internal static class Program
if (status == 2)
{
jsonObject.CreatedAt = DateTime.Now;
jsonObject.Error = "Battery Temperature High";
jsonObject.DeviceCreatedTheError = "Battery/1";
jsonObject.Description = "Battery Temperature High";
jsonObject.CreatedBy = "Battery/1";
}
else if (status == 1)
{
jsonObject.CreatedAt = DateTime.Now;
jsonObject.Description = "Temp warning message";
jsonObject.CreatedBy = "Battery/4";
}
var message = JsonSerializer.Serialize(jsonObject);

View File

@ -1,13 +1,13 @@
import axios from 'axios';
export const axiosConfigWithoutToken = axios.create({
//baseURL: 'https://monitor.innov.energy/api'
baseURL: 'http://127.0.0.1:7087/api'
baseURL: 'https://monitor.innov.energy/api'
//baseURL: 'http://127.0.0.1:7087/api'
});
const axiosConfig = axios.create({
//baseURL: 'https://monitor.innov.energy/api'
baseURL: 'http://127.0.0.1:7087/api'
baseURL: 'https://monitor.innov.energy/api'
//baseURL: 'http://127.0.0.1:7087/api'
});
axiosConfig.defaults.params = {};

View File

@ -17,12 +17,14 @@ import {
import Typography from '@mui/material/Typography';
import { FormattedMessage } from 'react-intl';
import ErrorIcon from '@mui/icons-material/Error';
import WarningIcon from '@mui/icons-material/Warning';
import axiosConfig from '../../../Resources/axiosConfig';
import { AxiosError, AxiosResponse } from 'axios/index';
import routes from '../../../Resources/routes.json';
import { useNavigate } from 'react-router-dom';
import { TokenContext } from '../../../contexts/tokenContext';
import { ErrorMessage } from '../../../interfaces/S3Types';
import Button from '@mui/material/Button';
interface LogProps {
errorLoadingS3Data: boolean;
@ -31,8 +33,11 @@ interface LogProps {
function Log(props: LogProps) {
const theme = useTheme();
//const [warnings, setWarnings] = useState<Notification[]>([]);
const [warnings, setWarnings] = useState<ErrorMessage[]>([]);
const [errors, setErrors] = useState<ErrorMessage[]>([]);
const [errorButtonPressed, setErrorButtonPressed] = useState(false);
const [warningButtonPressed, setWarningButtonPressed] = useState(false);
const navigate = useNavigate();
const tokencontext = useContext(TokenContext);
const { removeToken } = tokencontext;
@ -49,14 +54,41 @@ function Log(props: LogProps) {
navigate(routes.login);
}
});
axiosConfig
.get(`/GetAllWarningsForInstallation?id=${props.id}`)
.then((res: AxiosResponse<ErrorMessage[]>) => {
setWarnings(res.data);
})
.catch((err: AxiosError) => {
if (err.response && err.response.status == 401) {
removeToken();
navigate(routes.login);
}
});
}, []);
const handleErrorButtonPressed = () => {
setErrorButtonPressed(!errorButtonPressed);
};
const handleWarningButtonPressed = () => {
setWarningButtonPressed(!warningButtonPressed);
};
return (
<Container maxWidth="xl">
<Grid container>
<Grid item xs={12} md={12}>
{/* IT SHOULD BE {(errors.length > 0 || props.warnings.length > 0) && (*/}
{errors.length > 0 && (
<Button
variant="contained"
onClick={handleErrorButtonPressed}
sx={{ marginTop: '20px' }}
>
<FormattedMessage id="Show Errors" defaultMessage="Show Errors" />
</Button>
{errorButtonPressed && errors.length > 0 && (
<Card sx={{ marginTop: '10px' }}>
<Divider />
<TableContainer>
@ -66,7 +98,6 @@ function Log(props: LogProps) {
<TableCell>
<FormattedMessage id="type" defaultMessage="Type" />
</TableCell>
<TableCell>
<FormattedMessage
id="description"
@ -85,8 +116,7 @@ function Log(props: LogProps) {
</TableRow>
</TableHead>
<TableBody>
{errors.map((error, index) => {
return (
{errors.map((error, index) => (
<TableRow hover key={index}>
<TableCell>
<ErrorIcon
@ -106,9 +136,9 @@ function Log(props: LogProps) {
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px', marginLeft: '-40px' }}
sx={{ marginTop: '5px', verticalAlign: 'middle' }}
>
{error.errorDescription}
{error.description}
</Typography>
</TableCell>
<TableCell>
@ -118,9 +148,9 @@ function Log(props: LogProps) {
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px' }}
sx={{ marginTop: '5px', verticalAlign: 'middle' }}
>
{error.deviceCreatedTheError}
{error.deviceCreatedTheMessage}
</Typography>
</TableCell>
<TableCell>
@ -130,7 +160,7 @@ function Log(props: LogProps) {
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px', marginLeft: '-40px' }}
sx={{ marginTop: '5px', verticalAlign: 'middle' }}
>
{error.createdAt}
</Typography>
@ -142,87 +172,22 @@ function Log(props: LogProps) {
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px', marginLeft: '10px' }}
sx={{ marginTop: '5px', verticalAlign: 'middle' }}
>
{error.seen == false ? 'No' : 'Yes'}
</Typography>
</TableCell>
</TableRow>
);
})}
{/*{props.warnings.map((warning, index) => {*/}
{/* return (*/}
{/* <TableRow hover key={index}>*/}
{/* <TableCell>*/}
{/* <WarningIcon*/}
{/* sx={{*/}
{/* color: '#ffc04d',*/}
{/* width: 25,*/}
{/* height: 25,*/}
{/* marginLeft: '5px',*/}
{/* marginTop: '8px'*/}
{/* }}*/}
{/* />*/}
{/* </TableCell>*/}
{/* <TableCell>*/}
{/* <Typography*/}
{/* variant="body1"*/}
{/* fontWeight="bold"*/}
{/* color="text.primary"*/}
{/* gutterBottom*/}
{/* noWrap*/}
{/* sx={{ marginTop: '10px' }}*/}
{/* >*/}
{/* {warning.device}*/}
{/* </Typography>*/}
{/* </TableCell>*/}
{/* <TableCell>*/}
{/* <Typography*/}
{/* variant="body1"*/}
{/* fontWeight="bold"*/}
{/* color="text.primary"*/}
{/* gutterBottom*/}
{/* noWrap*/}
{/* sx={{ marginTop: '10px' }}*/}
{/* >*/}
{/* {warning.description}*/}
{/* </Typography>*/}
{/* </TableCell>*/}
{/* <TableCell>*/}
{/* <Typography*/}
{/* variant="body1"*/}
{/* fontWeight="bold"*/}
{/* color="text.primary"*/}
{/* gutterBottom*/}
{/* noWrap*/}
{/* sx={{ marginTop: '10px' }}*/}
{/* >*/}
{/* {warning.date}*/}
{/* </Typography>*/}
{/* </TableCell>*/}
{/* <TableCell>*/}
{/* <Typography*/}
{/* variant="body1"*/}
{/* fontWeight="bold"*/}
{/* color="text.primary"*/}
{/* gutterBottom*/}
{/* noWrap*/}
{/* sx={{ marginTop: '10px' }}*/}
{/* >*/}
{/* {warning.time}*/}
{/* </Typography>*/}
{/* </TableCell>*/}
{/* </TableRow>*/}
{/* );*/}
{/*})}*/}
))}
</TableBody>
</Table>
</TableContainer>
</Card>
)}
{!props.errorLoadingS3Data && errors.length == 0 && (
{!props.errorLoadingS3Data &&
errorButtonPressed &&
errors.length == 0 && (
<Alert
severity="error"
sx={{
@ -244,6 +209,142 @@ function Log(props: LogProps) {
)}
</Grid>
<Grid item xs={12} md={12}>
<Button
variant="contained"
onClick={handleWarningButtonPressed}
sx={{ marginTop: '20px' }}
>
<FormattedMessage
id="Show Warnings"
defaultMessage="Show Warnings"
/>
</Button>
{warningButtonPressed && warnings.length > 0 && (
<Card sx={{ marginTop: '10px' }}>
<Divider />
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>
<FormattedMessage id="type" defaultMessage="Type" />
</TableCell>
<TableCell>
<FormattedMessage
id="description"
defaultMessage="Description"
/>
</TableCell>
<TableCell>
<FormattedMessage id="device" defaultMessage="Device" />
</TableCell>
<TableCell>
<FormattedMessage id="date" defaultMessage="Date" />
</TableCell>
<TableCell>
<FormattedMessage id="seen" defaultMessage="Seen" />
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{warnings.map((warning, index) => {
return (
<TableRow hover key={index}>
<TableCell>
<WarningIcon
sx={{
color: 'orange',
width: 25,
height: 25,
marginLeft: '5px',
marginTop: '8px'
}}
/>
</TableCell>
<TableCell>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px', verticalAlign: 'middle' }}
>
{warning.description}
</Typography>
</TableCell>
<TableCell>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px' }}
>
{warning.deviceCreatedTheMessage}
</Typography>
</TableCell>
<TableCell>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px', verticalAlign: 'middle' }}
>
{warning.createdAt}
</Typography>
</TableCell>
<TableCell>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px', verticalAlign: 'middle' }}
>
{warning.seen == false ? 'No' : 'Yes'}
</Typography>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</Card>
)}
{!props.errorLoadingS3Data &&
warningButtonPressed &&
warnings.length == 0 && (
<Alert
severity="error"
sx={{
display: 'flex',
alignItems: 'center',
//marginBottom: '20px'
marginTop: '20px'
}}
>
<FormattedMessage
id="nowarnings"
defaultMessage="There are no warnings"
/>
<IconButton
color="inherit"
size="small"
sx={{ marginLeft: '4px' }}
></IconButton>
</Alert>
)}
</Grid>
<Grid item xs={12} md={12} style={{ marginBottom: '20px' }}>
{props.errorLoadingS3Data && (
<Alert
@ -265,28 +366,6 @@ function Log(props: LogProps) {
></IconButton>
</Alert>
)}
{/*{!props.errorLoadingS3Data && props.warnings.length == 0 && (*/}
{/* <Alert*/}
{/* severity="error"*/}
{/* sx={{*/}
{/* display: 'flex',*/}
{/* alignItems: 'center',*/}
{/* //marginBottom: '20px'*/}
{/* marginTop: '20px'*/}
{/* }}*/}
{/* >*/}
{/* <FormattedMessage*/}
{/* id="nowarnings"*/}
{/* defaultMessage="There are no warnings"*/}
{/* />*/}
{/* <IconButton*/}
{/* color="inherit"*/}
{/* size="small"*/}
{/* sx={{ marginLeft: '4px' }}*/}
{/* ></IconButton>*/}
{/* </Alert>*/}
{/*)}*/}
</Grid>
</Grid>
</Container>

View File

@ -8,8 +8,8 @@ export interface I_S3Credentials {
export interface ErrorMessage {
installationId: number;
description: string;
createdAt: Date;
errorDescription: string;
deviceCreatedTheError: string;
deviceCreatedTheMessage: string;
seen: boolean;
}