improved delete installation logic with S3 bucket purge and delete and reminder for manual check
This commit is contained in:
parent
584abe5b53
commit
f82190afc1
|
|
@ -264,42 +264,70 @@ public static class ExoCmd
|
||||||
|
|
||||||
public static async Task<Boolean> RevokeReadKey(this Installation installation)
|
public static async Task<Boolean> RevokeReadKey(this Installation installation)
|
||||||
{
|
{
|
||||||
//Check exoscale documentation https://openapi-v2.exoscale.com/topic/topic-api-request-signature
|
|
||||||
|
|
||||||
var url = $"https://api-ch-dk-2.exoscale.com/v2/access-key/{installation.S3Key}";
|
var url = $"https://api-ch-dk-2.exoscale.com/v2/access-key/{installation.S3Key}";
|
||||||
var method = $"access-key/{installation.S3Key}";
|
var method = $"access-key/{installation.S3Key}";
|
||||||
|
|
||||||
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
|
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
|
||||||
|
|
||||||
var authheader = "credential="+S3Credentials.Key+",expires="+unixtime+",signature="+BuildSignature("DELETE", method, unixtime);
|
var authheader = "credential="+S3Credentials.Key+",expires="+unixtime+",signature="+BuildSignature("DELETE", method, unixtime);
|
||||||
|
|
||||||
var client = new HttpClient();
|
var client = new HttpClient();
|
||||||
|
|
||||||
client.DefaultRequestHeaders.Authorization =
|
client.DefaultRequestHeaders.Authorization =
|
||||||
new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
|
new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
|
||||||
|
|
||||||
var response = await client.DeleteAsync(url);
|
try
|
||||||
return response.IsSuccessStatusCode;
|
{
|
||||||
|
var response = await client.DeleteAsync(url);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Successfully revoked read key for installation {installation.Id}.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Failed to revoke read key. HTTP Status: {response.StatusCode}. Response: {await response.Content.ReadAsStringAsync()}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error occurred while revoking read key: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<Boolean> RevokeWriteKey(this Installation installation)
|
public static async Task<Boolean> RevokeWriteKey(this Installation installation)
|
||||||
{
|
{
|
||||||
//Check exoscale documentation https://openapi-v2.exoscale.com/topic/topic-api-request-signature
|
|
||||||
|
|
||||||
var url = $"https://api-ch-dk-2.exoscale.com/v2/access-key/{installation.S3WriteKey}";
|
var url = $"https://api-ch-dk-2.exoscale.com/v2/access-key/{installation.S3WriteKey}";
|
||||||
var method = $"access-key/{installation.S3WriteKey}";
|
var method = $"access-key/{installation.S3WriteKey}";
|
||||||
|
|
||||||
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
|
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
|
||||||
|
|
||||||
var authheader = "credential="+S3Credentials.Key+",expires="+unixtime+",signature="+BuildSignature("DELETE", method, unixtime);
|
var authheader = "credential="+S3Credentials.Key+",expires="+unixtime+",signature="+BuildSignature("DELETE", method, unixtime);
|
||||||
|
|
||||||
var client = new HttpClient();
|
var client = new HttpClient();
|
||||||
|
|
||||||
client.DefaultRequestHeaders.Authorization =
|
client.DefaultRequestHeaders.Authorization =
|
||||||
new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
|
new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
|
||||||
|
|
||||||
var response = await client.DeleteAsync(url);
|
try
|
||||||
return response.IsSuccessStatusCode;
|
{
|
||||||
|
var response = await client.DeleteAsync(url);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Successfully revoked write key for installation {installation.Id}.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Failed to revoke write key. HTTP Status: {response.StatusCode}. Response: {await response.Content.ReadAsStringAsync()}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error occurred while revoking write key: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<(String, String)> CreateWriteKey(this Installation installation)
|
public static async Task<(String, String)> CreateWriteKey(this Installation installation)
|
||||||
|
|
@ -373,10 +401,35 @@ public static class ExoCmd
|
||||||
return await s3Region.PutBucket(installation.BucketName()) != null;
|
return await s3Region.PutBucket(installation.BucketName()) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<Boolean> DeleteBucket(this Installation installation)
|
public static async Task<Boolean> PurgeAndDeleteBucket(this Installation installation)
|
||||||
{
|
{
|
||||||
var s3Region = new S3Region($"https://{installation.S3Region}.{installation.S3Provider}", S3Credentials!);
|
var s3Region = new S3Region($"https://{installation.S3Region}.{installation.S3Provider}", S3Credentials!);
|
||||||
return await s3Region.DeleteBucket(installation.BucketName()) ;
|
var bucket = s3Region.Bucket(installation.BucketName());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var purged = await bucket.PurgeBucket();
|
||||||
|
if (!purged)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Failed to purge bucket {installation.BucketName()} for installation {installation.Id}.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var deleted = await s3Region.DeleteBucket(installation.BucketName());
|
||||||
|
if (!deleted)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Failed to delete bucket {installation.BucketName()} for installation {installation.Id}.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Successfully purged and deleted bucket {installation.BucketName()} for installation {installation.Id}.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error occurred while purging/deleting bucket {installation.BucketName()}: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -324,23 +324,22 @@ public static class SessionMethods
|
||||||
{
|
{
|
||||||
var user = session?.User;
|
var user = session?.User;
|
||||||
|
|
||||||
if (user is not null
|
if (user is null || installation is null || user.UserType == 0)
|
||||||
&& installation is not null
|
return false;
|
||||||
&& user.UserType != 0)
|
|
||||||
{
|
|
||||||
|
|
||||||
return
|
|
||||||
Db.Delete(installation)
|
|
||||||
&& await installation.RevokeReadKey()
|
|
||||||
&& await installation.RevokeWriteKey()
|
|
||||||
&& await installation.RemoveReadRole()
|
|
||||||
&& await installation.RemoveWriteRole()
|
|
||||||
&& await installation.DeleteBucket();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
// Try all Exoscale operations independently (don't short-circuit)
|
||||||
|
var readKeyOk = await installation.RevokeReadKey();
|
||||||
|
var writeKeyOk = await installation.RevokeWriteKey();
|
||||||
|
var readRoleOk = await installation.RemoveReadRole();
|
||||||
|
var writeRoleOk = await installation.RemoveWriteRole();
|
||||||
|
var bucketOk = await installation.PurgeAndDeleteBucket();
|
||||||
|
|
||||||
|
if (!readKeyOk || !writeKeyOk || !readRoleOk || !writeRoleOk || !bucketOk)
|
||||||
|
Console.WriteLine($"[Delete] Partial Exoscale cleanup for installation {installation.Id}: " +
|
||||||
|
$"readKey={readKeyOk}, writeKey={writeKeyOk}, readRole={readRoleOk}, writeRole={writeRoleOk}, bucket={bucketOk}");
|
||||||
|
|
||||||
|
// Always delete from DB (best-effort — admin wants it gone)
|
||||||
|
return Db.Delete(installation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Boolean Create(this Session? session, User newUser)
|
public static Boolean Create(this Session? session, User newUser)
|
||||||
|
|
|
||||||
|
|
@ -102,10 +102,28 @@ public static partial class Db
|
||||||
InstallationAccess.Delete(i => i.InstallationId == installation.Id);
|
InstallationAccess.Delete(i => i.InstallationId == installation.Id);
|
||||||
if (installation.Product == (int)ProductType.Salimax)
|
if (installation.Product == (int)ProductType.Salimax)
|
||||||
{
|
{
|
||||||
//For Salimax, delete the OrderNumber2Installation entries associated with this installation id.
|
|
||||||
OrderNumber2Installation.Delete(i => i.InstallationId == installation.Id);
|
OrderNumber2Installation.Delete(i => i.InstallationId == installation.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up AI insight cache entries linked to this installation's reports
|
||||||
|
var weeklyIds = WeeklyReports .Where(r => r.InstallationId == installation.Id).Select(r => r.Id).ToList();
|
||||||
|
var monthlyIds = MonthlyReports.Where(r => r.InstallationId == installation.Id).Select(r => r.Id).ToList();
|
||||||
|
var yearlyIds = YearlyReports .Where(r => r.InstallationId == installation.Id).Select(r => r.Id).ToList();
|
||||||
|
|
||||||
|
foreach (var id in weeklyIds) AiInsightCaches.Delete(c => c.ReportType == "weekly" && c.ReportId == id);
|
||||||
|
foreach (var id in monthlyIds) AiInsightCaches.Delete(c => c.ReportType == "monthly" && c.ReportId == id);
|
||||||
|
foreach (var id in yearlyIds) AiInsightCaches.Delete(c => c.ReportType == "yearly" && c.ReportId == id);
|
||||||
|
|
||||||
|
// Clean up energy records, report summaries, errors, warnings, and user actions
|
||||||
|
DailyRecords .Delete(r => r.InstallationId == installation.Id);
|
||||||
|
HourlyRecords .Delete(r => r.InstallationId == installation.Id);
|
||||||
|
WeeklyReports .Delete(r => r.InstallationId == installation.Id);
|
||||||
|
MonthlyReports.Delete(r => r.InstallationId == installation.Id);
|
||||||
|
YearlyReports .Delete(r => r.InstallationId == installation.Id);
|
||||||
|
Errors .Delete(e => e.InstallationId == installation.Id);
|
||||||
|
Warnings .Delete(w => w.InstallationId == installation.Id);
|
||||||
|
UserActions .Delete(a => a.InstallationId == installation.Id);
|
||||||
|
|
||||||
return Installations.Delete(i => i.Id == installation.Id) > 0;
|
return Installations.Delete(i => i.Id == installation.Id) > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -201,10 +201,52 @@ public static class S3
|
||||||
return response.HttpStatusCode == HttpStatusCode.OK;
|
return response.HttpStatusCode == HttpStatusCode.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<Boolean> PurgeBucket(this S3Bucket bucket)
|
||||||
|
{
|
||||||
|
var client = bucket.Region.GetS3Client();
|
||||||
|
var totalDeleted = 0;
|
||||||
|
|
||||||
|
Console.WriteLine($"[PurgeBucket] Starting purge of bucket {bucket.Name}");
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var listResponse = await client.ListObjectsV2Async(new ListObjectsV2Request
|
||||||
|
{
|
||||||
|
BucketName = bucket.Name,
|
||||||
|
MaxKeys = 1000
|
||||||
|
});
|
||||||
|
|
||||||
|
if (listResponse.S3Objects.Count == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[PurgeBucket] Completed purge of bucket {bucket.Name}: {totalDeleted} objects deleted");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var deleteResponse = await client.DeleteObjectsAsync(new DeleteObjectsRequest
|
||||||
|
{
|
||||||
|
BucketName = bucket.Name,
|
||||||
|
Objects = listResponse.S3Objects
|
||||||
|
.Select(o => new KeyVersion { Key = o.Key })
|
||||||
|
.ToList()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (deleteResponse.HttpStatusCode != HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[PurgeBucket] Failed at batch after {totalDeleted} objects deleted from bucket {bucket.Name}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalDeleted += listResponse.S3Objects.Count;
|
||||||
|
|
||||||
|
if (totalDeleted % 10000 == 0)
|
||||||
|
Console.WriteLine($"[PurgeBucket] Progress: {totalDeleted} objects deleted from bucket {bucket.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<Boolean> DeleteBucket(this S3Region region, String bucketName)
|
public static async Task<Boolean> DeleteBucket(this S3Region region, String bucketName)
|
||||||
{
|
{
|
||||||
var request = new DeleteBucketRequest { BucketName = bucketName };
|
var request = new DeleteBucketRequest { BucketName = bucketName };
|
||||||
|
|
||||||
var response = await region
|
var response = await region
|
||||||
.GetS3Client()
|
.GetS3Client()
|
||||||
.DeleteBucketAsync(request);
|
.DeleteBucketAsync(request);
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ function Information(props: InformationProps) {
|
||||||
top: '50%',
|
top: '50%',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
width: 350,
|
width: 450,
|
||||||
bgcolor: 'background.paper',
|
bgcolor: 'background.paper',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
boxShadow: 24,
|
boxShadow: 24,
|
||||||
|
|
@ -143,7 +143,37 @@ function Information(props: InformationProps) {
|
||||||
gutterBottom
|
gutterBottom
|
||||||
sx={{ fontWeight: 'bold' }}
|
sx={{ fontWeight: 'bold' }}
|
||||||
>
|
>
|
||||||
Do you want to delete this installation?
|
<FormattedMessage
|
||||||
|
id="confirmDeleteInstallation"
|
||||||
|
defaultMessage="Do you want to delete this installation?"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
mt: 1,
|
||||||
|
p: 1,
|
||||||
|
bgcolor: '#f5f5f5',
|
||||||
|
borderRadius: 1,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
wordBreak: 'break-all',
|
||||||
|
userSelect: 'all',
|
||||||
|
width: '100%',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.s3Credentials.s3Bucket}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{ mt: 1, fontSize: '0.75rem', color: 'text.secondary', textAlign: 'center' }}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="deleteInstallationWarning"
|
||||||
|
defaultMessage="Please note the bucket name above. Purging S3 data may take several minutes. If deletion fails, you can manually remove the bucket from Exoscale."
|
||||||
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ function InformationSalidomo(props: InformationSalidomoProps) {
|
||||||
top: '50%',
|
top: '50%',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
width: 350,
|
width: 450,
|
||||||
bgcolor: 'background.paper',
|
bgcolor: 'background.paper',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
boxShadow: 24,
|
boxShadow: 24,
|
||||||
|
|
@ -134,7 +134,37 @@ function InformationSalidomo(props: InformationSalidomoProps) {
|
||||||
gutterBottom
|
gutterBottom
|
||||||
sx={{ fontWeight: 'bold' }}
|
sx={{ fontWeight: 'bold' }}
|
||||||
>
|
>
|
||||||
Do you want to delete this installation?
|
<FormattedMessage
|
||||||
|
id="confirmDeleteInstallation"
|
||||||
|
defaultMessage="Do you want to delete this installation?"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
mt: 1,
|
||||||
|
p: 1,
|
||||||
|
bgcolor: '#f5f5f5',
|
||||||
|
borderRadius: 1,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
wordBreak: 'break-all',
|
||||||
|
userSelect: 'all',
|
||||||
|
width: '100%',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.s3Credentials.s3Bucket}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{ mt: 1, fontSize: '0.75rem', color: 'text.secondary', textAlign: 'center' }}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="deleteInstallationWarning"
|
||||||
|
defaultMessage="Please note the bucket name above. Purging S3 data may take several minutes. If deletion fails, you can manually remove the bucket from Exoscale."
|
||||||
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
||||||
top: '50%',
|
top: '50%',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
width: 350,
|
width: 450,
|
||||||
bgcolor: 'background.paper',
|
bgcolor: 'background.paper',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
boxShadow: 24,
|
boxShadow: 24,
|
||||||
|
|
@ -208,7 +208,37 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
||||||
gutterBottom
|
gutterBottom
|
||||||
sx={{ fontWeight: 'bold' }}
|
sx={{ fontWeight: 'bold' }}
|
||||||
>
|
>
|
||||||
Do you want to delete this installation?
|
<FormattedMessage
|
||||||
|
id="confirmDeleteInstallation"
|
||||||
|
defaultMessage="Do you want to delete this installation?"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
mt: 1,
|
||||||
|
p: 1,
|
||||||
|
bgcolor: '#f5f5f5',
|
||||||
|
borderRadius: 1,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
wordBreak: 'break-all',
|
||||||
|
userSelect: 'all',
|
||||||
|
width: '100%',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.s3Credentials.s3Bucket}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{ mt: 1, fontSize: '0.75rem', color: 'text.secondary', textAlign: 'center' }}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="deleteInstallationWarning"
|
||||||
|
defaultMessage="Please note the bucket name above. Purging S3 data may take several minutes. If deletion fails, you can manually remove the bucket from Exoscale."
|
||||||
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,9 @@
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"live": "Live Daten",
|
"live": "Live Daten",
|
||||||
"deleteInstallation": "Installation löschen",
|
"deleteInstallation": "Installation löschen",
|
||||||
|
"confirmDeleteInstallation": "Möchten Sie diese Installation löschen?",
|
||||||
|
"bucketLabel": "Bucket",
|
||||||
|
"deleteInstallationWarning": "Bitte notieren Sie den Bucket-Namen oben. Das Löschen der S3-Daten kann mehrere Minuten dauern. Überprüfen Sie nach dem Löschen in Exoscale, ob der Bucket entfernt wurde. Falls nicht, leeren und löschen Sie den Bucket manuell.",
|
||||||
"errorOccured": "Ein Fehler ist aufgetreten",
|
"errorOccured": "Ein Fehler ist aufgetreten",
|
||||||
"successfullyUpdated": "Erfolgreich aktualisiert",
|
"successfullyUpdated": "Erfolgreich aktualisiert",
|
||||||
"grantAccess": "Zugriff gewähren",
|
"grantAccess": "Zugriff gewähren",
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,9 @@
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"live": "Live View",
|
"live": "Live View",
|
||||||
"deleteInstallation": "Delete Installation",
|
"deleteInstallation": "Delete Installation",
|
||||||
|
"confirmDeleteInstallation": "Do you want to delete this installation?",
|
||||||
|
"bucketLabel": "Bucket",
|
||||||
|
"deleteInstallationWarning": "Please note the bucket name above. Purging S3 data may take several minutes. After deletion, verify in Exoscale that the bucket has been removed. If not, purge and delete the bucket manually.",
|
||||||
"errorOccured": "An error has occurred",
|
"errorOccured": "An error has occurred",
|
||||||
"successfullyUpdated": "Successfully updated",
|
"successfullyUpdated": "Successfully updated",
|
||||||
"grantAccess": "Grant Access",
|
"grantAccess": "Grant Access",
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,9 @@
|
||||||
"status": "Statut",
|
"status": "Statut",
|
||||||
"live": "Diffusion en direct",
|
"live": "Diffusion en direct",
|
||||||
"deleteInstallation": "Supprimer l'installation",
|
"deleteInstallation": "Supprimer l'installation",
|
||||||
|
"confirmDeleteInstallation": "Voulez-vous supprimer cette installation ?",
|
||||||
|
"bucketLabel": "Bucket",
|
||||||
|
"deleteInstallationWarning": "Veuillez noter le nom du bucket ci-dessus. La purge des données S3 peut prendre plusieurs minutes. Après la suppression, vérifiez dans Exoscale que le bucket a bien été supprimé. Sinon, purgez et supprimez le bucket manuellement.",
|
||||||
"errorOccured": "Une erreur s'est produite",
|
"errorOccured": "Une erreur s'est produite",
|
||||||
"successfullyUpdated": "Mise à jour réussie",
|
"successfullyUpdated": "Mise à jour réussie",
|
||||||
"grantAccess": "Accorder l'accès",
|
"grantAccess": "Accorder l'accès",
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,9 @@
|
||||||
"status": "Stato",
|
"status": "Stato",
|
||||||
"live": "Vista in diretta",
|
"live": "Vista in diretta",
|
||||||
"deleteInstallation": "Elimina installazione",
|
"deleteInstallation": "Elimina installazione",
|
||||||
|
"confirmDeleteInstallation": "Vuoi eliminare questa installazione?",
|
||||||
|
"bucketLabel": "Bucket",
|
||||||
|
"deleteInstallationWarning": "Prendi nota del nome del bucket qui sopra. L'eliminazione dei dati S3 potrebbe richiedere diversi minuti. Dopo l'eliminazione, verifica in Exoscale che il bucket sia stato rimosso. In caso contrario, svuota ed elimina il bucket manualmente.",
|
||||||
"errorOccured": "Si è verificato un errore",
|
"errorOccured": "Si è verificato un errore",
|
||||||
"successfullyUpdated": "Aggiornamento riuscito",
|
"successfullyUpdated": "Aggiornamento riuscito",
|
||||||
"grantAccess": "Concedi accesso",
|
"grantAccess": "Concedi accesso",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue