mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-08 00:51:20 +00:00
chore: Merge branch master into backups-rewrite
This commit is contained in:
commit
02cb4dbf8b
|
@ -47,4 +47,11 @@ enum DnsProviderType {
|
|||
return unknown;
|
||||
}
|
||||
}
|
||||
|
||||
String toInfectName() => switch (this) {
|
||||
digitalOcean => 'DIGITALOCEAN',
|
||||
cloudflare => 'CLOUDFLARE',
|
||||
desec => 'DESEC',
|
||||
unknown => 'UNKNOWN',
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/digital_oc
|
|||
import 'package:selfprivacy/logic/models/callback_dialogue_branching.dart';
|
||||
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||
import 'package:selfprivacy/logic/models/json/digital_ocean_server_info.dart';
|
||||
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||
import 'package:selfprivacy/logic/models/price.dart';
|
||||
|
@ -52,86 +51,41 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
ServerProviderType get type => ServerProviderType.digitalOcean;
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> trySetServerLocation(
|
||||
final String location,
|
||||
) async {
|
||||
final bool apiInitialized = _adapter.api().isWithToken;
|
||||
if (!apiInitialized) {
|
||||
Future<GenericResult<List<ServerBasicInfo>>> getServers() async {
|
||||
List<ServerBasicInfo> servers = [];
|
||||
final result = await _adapter.api().getServers();
|
||||
if (result.data.isEmpty || !result.success) {
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: false,
|
||||
message: 'Not authorized!',
|
||||
success: result.success,
|
||||
data: servers,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
_adapter = ApiAdapter(
|
||||
isWithToken: true,
|
||||
region: location,
|
||||
);
|
||||
return success;
|
||||
}
|
||||
final List rawServers = result.data;
|
||||
servers = rawServers.map<ServerBasicInfo>(
|
||||
(final server) {
|
||||
String ipv4 = '0.0.0.0';
|
||||
if (server['networks']['v4'].isNotEmpty) {
|
||||
for (final v4 in server['networks']['v4']) {
|
||||
if (v4['type'].toString() == 'public') {
|
||||
ipv4 = v4['ip_address'].toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
|
||||
final api = _adapter.api(getInitialized: false);
|
||||
final result = await api.isApiTokenValid(token);
|
||||
if (!result.data || !result.success) {
|
||||
return result;
|
||||
}
|
||||
return ServerBasicInfo(
|
||||
id: server['id'],
|
||||
reverseDns: server['name'],
|
||||
created: DateTime.now(),
|
||||
ip: ipv4,
|
||||
name: server['name'],
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
|
||||
_adapter = ApiAdapter(region: api.region, isWithToken: true);
|
||||
return result;
|
||||
}
|
||||
|
||||
String? getEmojiFlag(final String query) {
|
||||
String? emoji;
|
||||
|
||||
switch (query.toLowerCase().substring(0, 3)) {
|
||||
case 'fra':
|
||||
emoji = '🇩🇪';
|
||||
break;
|
||||
|
||||
case 'ams':
|
||||
emoji = '🇳🇱';
|
||||
break;
|
||||
|
||||
case 'sgp':
|
||||
emoji = '🇸🇬';
|
||||
break;
|
||||
|
||||
case 'lon':
|
||||
emoji = '🇬🇧';
|
||||
break;
|
||||
|
||||
case 'tor':
|
||||
emoji = '🇨🇦';
|
||||
break;
|
||||
|
||||
case 'blr':
|
||||
emoji = '🇮🇳';
|
||||
break;
|
||||
|
||||
case 'nyc':
|
||||
case 'sfo':
|
||||
emoji = '🇺🇸';
|
||||
break;
|
||||
}
|
||||
|
||||
return emoji;
|
||||
}
|
||||
|
||||
String dnsProviderToInfectName(final DnsProviderType dnsProvider) {
|
||||
String dnsProviderType;
|
||||
switch (dnsProvider) {
|
||||
case DnsProviderType.digitalOcean:
|
||||
dnsProviderType = 'DIGITALOCEAN';
|
||||
break;
|
||||
case DnsProviderType.cloudflare:
|
||||
default:
|
||||
dnsProviderType = 'CLOUDFLARE';
|
||||
break;
|
||||
}
|
||||
return dnsProviderType;
|
||||
return GenericResult(success: true, data: servers);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -148,8 +102,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
rootUser: installationData.rootUser,
|
||||
domainName: installationData.serverDomain.domainName,
|
||||
serverType: installationData.serverTypeId,
|
||||
dnsProviderType:
|
||||
dnsProviderToInfectName(installationData.dnsProviderType),
|
||||
dnsProviderType: installationData.dnsProviderType.toInfectName(),
|
||||
hostName: hostname,
|
||||
base64Password: base64.encode(
|
||||
utf8.encode(installationData.rootUser.password ?? 'PASS'),
|
||||
|
@ -244,6 +197,139 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
return GenericResult(success: true, data: null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<CallbackDialogueBranching?>> deleteServer(
|
||||
final String hostname,
|
||||
) async {
|
||||
final String deletionName = getHostnameFromDomain(hostname);
|
||||
final serversResult = await getServers();
|
||||
try {
|
||||
final servers = serversResult.data;
|
||||
ServerBasicInfo? foundServer;
|
||||
for (final server in servers) {
|
||||
if (server.name == deletionName) {
|
||||
foundServer = server;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final volumes = await getVolumes();
|
||||
final ServerVolume volumeToRemove;
|
||||
volumeToRemove = volumes.data.firstWhere(
|
||||
(final el) => el.serverId == foundServer!.id,
|
||||
);
|
||||
|
||||
await _adapter.api().detachVolume(
|
||||
volumeToRemove.name,
|
||||
volumeToRemove.serverId!,
|
||||
);
|
||||
|
||||
await Future.delayed(const Duration(seconds: 10));
|
||||
final List<Future> laterFutures = <Future>[];
|
||||
laterFutures.add(_adapter.api().deleteVolume(volumeToRemove.uuid!));
|
||||
laterFutures.add(_adapter.api().deleteServer(foundServer!.id));
|
||||
|
||||
await Future.wait(laterFutures);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: CallbackDialogueBranching(
|
||||
choices: [
|
||||
CallbackDialogueChoice(
|
||||
title: 'basis.cancel'.tr(),
|
||||
callback: null,
|
||||
),
|
||||
CallbackDialogueChoice(
|
||||
title: 'modals.try_again'.tr(),
|
||||
callback: () async {
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
return deleteServer(hostname);
|
||||
},
|
||||
),
|
||||
],
|
||||
description: 'modals.try_again'.tr(),
|
||||
title: 'modals.server_deletion_error'.tr(),
|
||||
),
|
||||
message: e.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
|
||||
final api = _adapter.api(getInitialized: false);
|
||||
final result = await api.isApiTokenValid(token);
|
||||
if (!result.data || !result.success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
_adapter = ApiAdapter(region: api.region, isWithToken: true);
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> trySetServerLocation(
|
||||
final String location,
|
||||
) async {
|
||||
final bool apiInitialized = _adapter.api().isWithToken;
|
||||
if (!apiInitialized) {
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: false,
|
||||
message: 'Not authorized!',
|
||||
);
|
||||
}
|
||||
|
||||
_adapter = ApiAdapter(
|
||||
isWithToken: true,
|
||||
region: location,
|
||||
);
|
||||
return success;
|
||||
}
|
||||
|
||||
String? getEmojiFlag(final String query) {
|
||||
String? emoji;
|
||||
|
||||
switch (query.toLowerCase().substring(0, 3)) {
|
||||
case 'fra':
|
||||
emoji = '🇩🇪';
|
||||
break;
|
||||
|
||||
case 'ams':
|
||||
emoji = '🇳🇱';
|
||||
break;
|
||||
|
||||
case 'sgp':
|
||||
emoji = '🇸🇬';
|
||||
break;
|
||||
|
||||
case 'lon':
|
||||
emoji = '🇬🇧';
|
||||
break;
|
||||
|
||||
case 'tor':
|
||||
emoji = '🇨🇦';
|
||||
break;
|
||||
|
||||
case 'blr':
|
||||
emoji = '🇮🇳';
|
||||
break;
|
||||
|
||||
case 'nyc':
|
||||
case 'sfo':
|
||||
emoji = '🇺🇸';
|
||||
break;
|
||||
}
|
||||
|
||||
return emoji;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<List<ServerProviderLocation>>>
|
||||
getAvailableLocations() async {
|
||||
|
@ -318,43 +404,214 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<List<ServerBasicInfo>>> getServers() async {
|
||||
List<ServerBasicInfo> servers = [];
|
||||
final result = await _adapter.api().getServers();
|
||||
if (result.data.isEmpty || !result.success) {
|
||||
Future<GenericResult<DateTime?>> powerOn(final int serverId) async {
|
||||
DateTime? timestamp;
|
||||
final result = await _adapter.api().powerOn(serverId);
|
||||
if (!result.success) {
|
||||
return GenericResult(
|
||||
success: result.success,
|
||||
data: servers,
|
||||
success: false,
|
||||
data: timestamp,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
final List rawServers = result.data;
|
||||
servers = rawServers.map<ServerBasicInfo>(
|
||||
(final server) {
|
||||
String ipv4 = '0.0.0.0';
|
||||
if (server['networks']['v4'].isNotEmpty) {
|
||||
for (final v4 in server['networks']['v4']) {
|
||||
if (v4['type'].toString() == 'public') {
|
||||
ipv4 = v4['ip_address'].toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
timestamp = DateTime.now();
|
||||
|
||||
return ServerBasicInfo(
|
||||
id: server['id'],
|
||||
reverseDns: server['name'],
|
||||
created: DateTime.now(),
|
||||
ip: ipv4,
|
||||
name: server['name'],
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
|
||||
return GenericResult(success: true, data: servers);
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: timestamp,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<DateTime?>> restart(final int serverId) async {
|
||||
DateTime? timestamp;
|
||||
final result = await _adapter.api().restart(serverId);
|
||||
if (!result.success) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: timestamp,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
timestamp = DateTime.now();
|
||||
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: timestamp,
|
||||
);
|
||||
}
|
||||
|
||||
/// Hardcoded on their documentation and there is no pricing API at all
|
||||
/// Probably we should scrap the doc page manually
|
||||
@override
|
||||
Future<GenericResult<Price?>> getPricePerGb() async => GenericResult(
|
||||
success: true,
|
||||
data: Price(
|
||||
value: 0.10,
|
||||
currency: currency,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Future<GenericResult<List<ServerVolume>>> getVolumes({
|
||||
final String? status,
|
||||
}) async {
|
||||
final List<ServerVolume> volumes = [];
|
||||
|
||||
final result = await _adapter.api().getVolumes();
|
||||
|
||||
if (!result.success || result.data.isEmpty) {
|
||||
return GenericResult(
|
||||
data: [],
|
||||
success: false,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
int id = 0;
|
||||
for (final rawVolume in result.data) {
|
||||
final String volumeName = rawVolume.name;
|
||||
final volume = ServerVolume(
|
||||
id: id++,
|
||||
name: volumeName,
|
||||
sizeByte: rawVolume.sizeGigabytes * 1024 * 1024 * 1024,
|
||||
serverId:
|
||||
(rawVolume.dropletIds != null && rawVolume.dropletIds!.isNotEmpty)
|
||||
? rawVolume.dropletIds![0]
|
||||
: null,
|
||||
linuxDevice: 'scsi-0DO_Volume_$volumeName',
|
||||
uuid: rawVolume.id,
|
||||
);
|
||||
volumes.add(volume);
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return GenericResult(
|
||||
data: [],
|
||||
success: false,
|
||||
message: e.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return GenericResult(
|
||||
data: volumes,
|
||||
success: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<ServerVolume?>> createVolume() async {
|
||||
ServerVolume? volume;
|
||||
|
||||
final result = await _adapter.api().createVolume();
|
||||
|
||||
if (!result.success || result.data == null) {
|
||||
return GenericResult(
|
||||
data: null,
|
||||
success: false,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
final getVolumesResult = await _adapter.api().getVolumes();
|
||||
|
||||
if (!getVolumesResult.success || getVolumesResult.data.isEmpty) {
|
||||
return GenericResult(
|
||||
data: null,
|
||||
success: false,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
final String volumeName = result.data!.name;
|
||||
volume = ServerVolume(
|
||||
id: getVolumesResult.data.length,
|
||||
name: volumeName,
|
||||
sizeByte: result.data!.sizeGigabytes,
|
||||
serverId: null,
|
||||
linuxDevice: '/dev/disk/by-id/scsi-0DO_Volume_$volumeName',
|
||||
uuid: result.data!.id,
|
||||
);
|
||||
|
||||
return GenericResult(
|
||||
data: volume,
|
||||
success: true,
|
||||
);
|
||||
}
|
||||
|
||||
Future<GenericResult<ServerVolume?>> getVolume(
|
||||
final String volumeUuid,
|
||||
) async {
|
||||
ServerVolume? requestedVolume;
|
||||
|
||||
final result = await getVolumes();
|
||||
|
||||
if (!result.success || result.data.isEmpty) {
|
||||
return GenericResult(
|
||||
data: null,
|
||||
success: false,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
for (final volume in result.data) {
|
||||
if (volume.uuid == volumeUuid) {
|
||||
requestedVolume = volume;
|
||||
}
|
||||
}
|
||||
|
||||
return GenericResult(
|
||||
data: requestedVolume,
|
||||
success: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> attachVolume(
|
||||
final ServerVolume volume,
|
||||
final int serverId,
|
||||
) async =>
|
||||
_adapter.api().attachVolume(
|
||||
volume.name,
|
||||
serverId,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> detachVolume(
|
||||
final ServerVolume volume,
|
||||
) async =>
|
||||
_adapter.api().detachVolume(
|
||||
volume.name,
|
||||
volume.serverId!,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<GenericResult<void>> deleteVolume(
|
||||
final ServerVolume volume,
|
||||
) async =>
|
||||
_adapter.api().deleteVolume(
|
||||
volume.uuid!,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> resizeVolume(
|
||||
final ServerVolume volume,
|
||||
final DiskSize size,
|
||||
) async =>
|
||||
_adapter.api().resizeVolume(
|
||||
volume.name,
|
||||
size,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<GenericResult<List<ServerMetadataEntity>>> getMetadata(
|
||||
final int serverId,
|
||||
|
@ -536,277 +793,4 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
|
||||
return GenericResult(success: true, data: metrics);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<DateTime?>> restart(final int serverId) async {
|
||||
DateTime? timestamp;
|
||||
final result = await _adapter.api().restart(serverId);
|
||||
if (!result.success) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: timestamp,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
timestamp = DateTime.now();
|
||||
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: timestamp,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<CallbackDialogueBranching?>> deleteServer(
|
||||
final String hostname,
|
||||
) async {
|
||||
final String deletionName = getHostnameFromDomain(hostname);
|
||||
final serversResult = await getServers();
|
||||
try {
|
||||
final servers = serversResult.data;
|
||||
ServerBasicInfo? foundServer;
|
||||
for (final server in servers) {
|
||||
if (server.name == deletionName) {
|
||||
foundServer = server;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final volumes = await getVolumes();
|
||||
final ServerVolume volumeToRemove;
|
||||
volumeToRemove = volumes.data.firstWhere(
|
||||
(final el) => el.serverId == foundServer!.id,
|
||||
);
|
||||
|
||||
await _adapter.api().detachVolume(
|
||||
volumeToRemove.name,
|
||||
volumeToRemove.serverId!,
|
||||
);
|
||||
|
||||
await Future.delayed(const Duration(seconds: 10));
|
||||
final List<Future> laterFutures = <Future>[];
|
||||
laterFutures.add(_adapter.api().deleteVolume(volumeToRemove.uuid!));
|
||||
laterFutures.add(_adapter.api().deleteServer(foundServer!.id));
|
||||
|
||||
await Future.wait(laterFutures);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: CallbackDialogueBranching(
|
||||
choices: [
|
||||
CallbackDialogueChoice(
|
||||
title: 'basis.cancel'.tr(),
|
||||
callback: null,
|
||||
),
|
||||
CallbackDialogueChoice(
|
||||
title: 'modals.try_again'.tr(),
|
||||
callback: () async {
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
return deleteServer(hostname);
|
||||
},
|
||||
),
|
||||
],
|
||||
description: 'modals.try_again'.tr(),
|
||||
title: 'modals.server_deletion_error'.tr(),
|
||||
),
|
||||
message: e.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<List<ServerVolume>>> getVolumes({
|
||||
final String? status,
|
||||
}) async {
|
||||
final List<ServerVolume> volumes = [];
|
||||
|
||||
final result = await _adapter.api().getVolumes();
|
||||
|
||||
if (!result.success || result.data.isEmpty) {
|
||||
return GenericResult(
|
||||
data: [],
|
||||
success: false,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
int id = 0;
|
||||
for (final rawVolume in result.data) {
|
||||
final String volumeName = rawVolume.name;
|
||||
final volume = ServerVolume(
|
||||
id: id++,
|
||||
name: volumeName,
|
||||
sizeByte: rawVolume.sizeGigabytes * 1024 * 1024 * 1024,
|
||||
serverId:
|
||||
(rawVolume.dropletIds != null && rawVolume.dropletIds!.isNotEmpty)
|
||||
? rawVolume.dropletIds![0]
|
||||
: null,
|
||||
linuxDevice: 'scsi-0DO_Volume_$volumeName',
|
||||
uuid: rawVolume.id,
|
||||
);
|
||||
volumes.add(volume);
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return GenericResult(
|
||||
data: [],
|
||||
success: false,
|
||||
message: e.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return GenericResult(
|
||||
data: volumes,
|
||||
success: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<ServerVolume?>> createVolume() async {
|
||||
ServerVolume? volume;
|
||||
|
||||
final result = await _adapter.api().createVolume();
|
||||
|
||||
if (!result.success || result.data == null) {
|
||||
return GenericResult(
|
||||
data: null,
|
||||
success: false,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
final getVolumesResult = await _adapter.api().getVolumes();
|
||||
|
||||
if (!getVolumesResult.success || getVolumesResult.data.isEmpty) {
|
||||
return GenericResult(
|
||||
data: null,
|
||||
success: false,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
final String volumeName = result.data!.name;
|
||||
volume = ServerVolume(
|
||||
id: getVolumesResult.data.length,
|
||||
name: volumeName,
|
||||
sizeByte: result.data!.sizeGigabytes,
|
||||
serverId: null,
|
||||
linuxDevice: '/dev/disk/by-id/scsi-0DO_Volume_$volumeName',
|
||||
uuid: result.data!.id,
|
||||
);
|
||||
|
||||
return GenericResult(
|
||||
data: volume,
|
||||
success: true,
|
||||
);
|
||||
}
|
||||
|
||||
Future<GenericResult<ServerVolume?>> getVolume(
|
||||
final String volumeUuid,
|
||||
) async {
|
||||
ServerVolume? requestedVolume;
|
||||
|
||||
final result = await getVolumes();
|
||||
|
||||
if (!result.success || result.data.isEmpty) {
|
||||
return GenericResult(
|
||||
data: null,
|
||||
success: false,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
for (final volume in result.data) {
|
||||
if (volume.uuid == volumeUuid) {
|
||||
requestedVolume = volume;
|
||||
}
|
||||
}
|
||||
|
||||
return GenericResult(
|
||||
data: requestedVolume,
|
||||
success: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<void>> deleteVolume(
|
||||
final ServerVolume volume,
|
||||
) async =>
|
||||
_adapter.api().deleteVolume(
|
||||
volume.uuid!,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> attachVolume(
|
||||
final ServerVolume volume,
|
||||
final int serverId,
|
||||
) async =>
|
||||
_adapter.api().attachVolume(
|
||||
volume.name,
|
||||
serverId,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> detachVolume(
|
||||
final ServerVolume volume,
|
||||
) async =>
|
||||
_adapter.api().detachVolume(
|
||||
volume.name,
|
||||
volume.serverId!,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> resizeVolume(
|
||||
final ServerVolume volume,
|
||||
final DiskSize size,
|
||||
) async =>
|
||||
_adapter.api().resizeVolume(
|
||||
volume.name,
|
||||
size,
|
||||
);
|
||||
|
||||
/// Hardcoded on their documentation and there is no pricing API at all
|
||||
/// Probably we should scrap the doc page manually
|
||||
@override
|
||||
Future<GenericResult<Price?>> getPricePerGb() async => GenericResult(
|
||||
success: true,
|
||||
data: Price(
|
||||
value: 0.10,
|
||||
currency: currency,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Future<GenericResult<DateTime?>> powerOn(final int serverId) async {
|
||||
DateTime? timestamp;
|
||||
final result = await _adapter.api().powerOn(serverId);
|
||||
if (!result.success) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: timestamp,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
timestamp = DateTime.now();
|
||||
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: timestamp,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/he
|
|||
import 'package:selfprivacy/logic/models/callback_dialogue_branching.dart';
|
||||
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||
import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart';
|
||||
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||
import 'package:selfprivacy/logic/models/price.dart';
|
||||
|
@ -51,131 +50,6 @@ class HetznerServerProvider extends ServerProvider {
|
|||
@override
|
||||
ServerProviderType get type => ServerProviderType.hetzner;
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> trySetServerLocation(
|
||||
final String location,
|
||||
) async {
|
||||
final bool apiInitialized = _adapter.api().isWithToken;
|
||||
if (!apiInitialized) {
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: false,
|
||||
message: 'Not authorized!',
|
||||
);
|
||||
}
|
||||
|
||||
_adapter = ApiAdapter(
|
||||
isWithToken: true,
|
||||
region: location,
|
||||
);
|
||||
return success;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
|
||||
final api = _adapter.api(getInitialized: false);
|
||||
final result = await api.isApiTokenValid(token);
|
||||
if (!result.data || !result.success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
_adapter = ApiAdapter(region: api.region, isWithToken: true);
|
||||
return result;
|
||||
}
|
||||
|
||||
String? getEmojiFlag(final String query) {
|
||||
String? emoji;
|
||||
|
||||
switch (query.toLowerCase()) {
|
||||
case 'de':
|
||||
emoji = '🇩🇪';
|
||||
break;
|
||||
|
||||
case 'fi':
|
||||
emoji = '🇫🇮';
|
||||
break;
|
||||
|
||||
case 'us':
|
||||
emoji = '🇺🇸';
|
||||
break;
|
||||
}
|
||||
|
||||
return emoji;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<List<ServerProviderLocation>>>
|
||||
getAvailableLocations() async {
|
||||
final List<ServerProviderLocation> locations = [];
|
||||
final result = await _adapter.api().getAvailableLocations();
|
||||
if (result.data.isEmpty || !result.success) {
|
||||
return GenericResult(
|
||||
success: result.success,
|
||||
data: locations,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
final List<HetznerLocation> rawLocations = result.data;
|
||||
for (final rawLocation in rawLocations) {
|
||||
ServerProviderLocation? location;
|
||||
try {
|
||||
location = ServerProviderLocation(
|
||||
title: rawLocation.city,
|
||||
description: rawLocation.description,
|
||||
flag: getEmojiFlag(rawLocation.country),
|
||||
identifier: rawLocation.name,
|
||||
);
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
locations.add(location);
|
||||
}
|
||||
|
||||
return GenericResult(success: true, data: locations);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<List<ServerType>>> getServerTypes({
|
||||
required final ServerProviderLocation location,
|
||||
}) async {
|
||||
final List<ServerType> types = [];
|
||||
final result = await _adapter.api().getAvailableServerTypes();
|
||||
if (result.data.isEmpty || !result.success) {
|
||||
return GenericResult(
|
||||
success: result.success,
|
||||
data: types,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
final rawTypes = result.data;
|
||||
for (final rawType in rawTypes) {
|
||||
for (final rawPrice in rawType.prices) {
|
||||
if (rawPrice.location == location.identifier) {
|
||||
types.add(
|
||||
ServerType(
|
||||
title: rawType.description,
|
||||
identifier: rawType.name,
|
||||
ram: rawType.memory.toDouble(),
|
||||
cores: rawType.cores,
|
||||
disk: DiskSize(byte: rawType.disk * 1024 * 1024 * 1024),
|
||||
price: Price(
|
||||
value: rawPrice.monthly,
|
||||
currency: currency,
|
||||
),
|
||||
location: location,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return GenericResult(success: true, data: types);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<List<ServerBasicInfo>>> getServers() async {
|
||||
final List<ServerBasicInfo> servers = [];
|
||||
|
@ -214,206 +88,6 @@ class HetznerServerProvider extends ServerProvider {
|
|||
return GenericResult(success: true, data: servers);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<List<ServerMetadataEntity>>> getMetadata(
|
||||
final int serverId,
|
||||
) async {
|
||||
List<ServerMetadataEntity> metadata = [];
|
||||
final result = await _adapter.api().getServers();
|
||||
if (result.data.isEmpty || !result.success) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: metadata,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
final List<HetznerServerInfo> servers = result.data;
|
||||
try {
|
||||
final HetznerServerInfo server = servers.firstWhere(
|
||||
(final server) => server.id == serverId,
|
||||
);
|
||||
|
||||
metadata = [
|
||||
ServerMetadataEntity(
|
||||
type: MetadataType.id,
|
||||
trId: 'server.server_id',
|
||||
value: server.id.toString(),
|
||||
),
|
||||
ServerMetadataEntity(
|
||||
type: MetadataType.status,
|
||||
trId: 'server.status',
|
||||
value: server.status.toString().split('.')[1].capitalize(),
|
||||
),
|
||||
ServerMetadataEntity(
|
||||
type: MetadataType.cpu,
|
||||
trId: 'server.cpu',
|
||||
value: server.serverType.cores.toString(),
|
||||
),
|
||||
ServerMetadataEntity(
|
||||
type: MetadataType.ram,
|
||||
trId: 'server.ram',
|
||||
value: '${server.serverType.memory.toString()} GB',
|
||||
),
|
||||
ServerMetadataEntity(
|
||||
type: MetadataType.cost,
|
||||
trId: 'server.monthly_cost',
|
||||
value:
|
||||
'${server.serverType.prices[1].monthly.toStringAsFixed(2)} ${currency.shortcode}',
|
||||
),
|
||||
ServerMetadataEntity(
|
||||
type: MetadataType.location,
|
||||
trId: 'server.location',
|
||||
value: '${server.location.city}, ${server.location.country}',
|
||||
),
|
||||
ServerMetadataEntity(
|
||||
type: MetadataType.other,
|
||||
trId: 'server.provider',
|
||||
value: _adapter.api().displayProviderName,
|
||||
),
|
||||
];
|
||||
} catch (e) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: [],
|
||||
message: e.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return GenericResult(success: true, data: metadata);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<ServerMetrics?>> getMetrics(
|
||||
final int serverId,
|
||||
final DateTime start,
|
||||
final DateTime end,
|
||||
) async {
|
||||
ServerMetrics? metrics;
|
||||
|
||||
List<TimeSeriesData> serializeTimeSeries(
|
||||
final Map<String, dynamic> json,
|
||||
final String type,
|
||||
) {
|
||||
final List list = json['time_series'][type]['values'];
|
||||
return list
|
||||
.map((final el) => TimeSeriesData(el[0], double.parse(el[1])))
|
||||
.toList();
|
||||
}
|
||||
|
||||
final cpuResult = await _adapter.api().getMetrics(
|
||||
serverId,
|
||||
start,
|
||||
end,
|
||||
'cpu',
|
||||
);
|
||||
|
||||
if (cpuResult.data.isEmpty || !cpuResult.success) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: metrics,
|
||||
code: cpuResult.code,
|
||||
message: cpuResult.message,
|
||||
);
|
||||
}
|
||||
|
||||
final netResult = await _adapter.api().getMetrics(
|
||||
serverId,
|
||||
start,
|
||||
end,
|
||||
'network',
|
||||
);
|
||||
|
||||
if (cpuResult.data.isEmpty || !netResult.success) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: metrics,
|
||||
code: netResult.code,
|
||||
message: netResult.message,
|
||||
);
|
||||
}
|
||||
|
||||
metrics = ServerMetrics(
|
||||
cpu: serializeTimeSeries(
|
||||
cpuResult.data,
|
||||
'cpu',
|
||||
),
|
||||
bandwidthIn: serializeTimeSeries(
|
||||
netResult.data,
|
||||
'network.0.bandwidth.in',
|
||||
),
|
||||
bandwidthOut: serializeTimeSeries(
|
||||
netResult.data,
|
||||
'network.0.bandwidth.out',
|
||||
),
|
||||
end: end,
|
||||
start: start,
|
||||
stepsInSecond: cpuResult.data['step'],
|
||||
);
|
||||
|
||||
return GenericResult(data: metrics, success: true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<DateTime?>> restart(final int serverId) async {
|
||||
DateTime? timestamp;
|
||||
final result = await _adapter.api().restart(serverId);
|
||||
if (!result.success) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: timestamp,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
timestamp = DateTime.now();
|
||||
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: timestamp,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<DateTime?>> powerOn(final int serverId) async {
|
||||
DateTime? timestamp;
|
||||
final result = await _adapter.api().powerOn(serverId);
|
||||
if (!result.success) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: timestamp,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
timestamp = DateTime.now();
|
||||
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: timestamp,
|
||||
);
|
||||
}
|
||||
|
||||
String dnsProviderToInfectName(final DnsProviderType dnsProvider) {
|
||||
String dnsProviderType;
|
||||
switch (dnsProvider) {
|
||||
case DnsProviderType.digitalOcean:
|
||||
dnsProviderType = 'DIGITALOCEAN';
|
||||
break;
|
||||
case DnsProviderType.desec:
|
||||
dnsProviderType = 'DESEC';
|
||||
break;
|
||||
case DnsProviderType.cloudflare:
|
||||
default:
|
||||
dnsProviderType = 'CLOUDFLARE';
|
||||
break;
|
||||
}
|
||||
return dnsProviderType;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<CallbackDialogueBranching?>> launchInstallation(
|
||||
final LaunchInstallationData installationData,
|
||||
|
@ -454,8 +128,7 @@ class HetznerServerProvider extends ServerProvider {
|
|||
rootUser: installationData.rootUser,
|
||||
domainName: installationData.serverDomain.domainName,
|
||||
serverType: installationData.serverTypeId,
|
||||
dnsProviderType:
|
||||
dnsProviderToInfectName(installationData.dnsProviderType),
|
||||
dnsProviderType: installationData.dnsProviderType.toInfectName(),
|
||||
hostName: hostname,
|
||||
volumeId: volume.id,
|
||||
base64Password: base64.encode(
|
||||
|
@ -651,10 +324,175 @@ class HetznerServerProvider extends ServerProvider {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<ServerVolume?>> createVolume() async {
|
||||
ServerVolume? volume;
|
||||
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
|
||||
final api = _adapter.api(getInitialized: false);
|
||||
final result = await api.isApiTokenValid(token);
|
||||
if (!result.data || !result.success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
final result = await _adapter.api().createVolume();
|
||||
_adapter = ApiAdapter(region: api.region, isWithToken: true);
|
||||
return result;
|
||||
}
|
||||
|
||||
String? getEmojiFlag(final String query) {
|
||||
String? emoji;
|
||||
|
||||
switch (query.toLowerCase()) {
|
||||
case 'de':
|
||||
emoji = '🇩🇪';
|
||||
break;
|
||||
|
||||
case 'fi':
|
||||
emoji = '🇫🇮';
|
||||
break;
|
||||
|
||||
case 'us':
|
||||
emoji = '🇺🇸';
|
||||
break;
|
||||
}
|
||||
|
||||
return emoji;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> trySetServerLocation(
|
||||
final String location,
|
||||
) async {
|
||||
final bool apiInitialized = _adapter.api().isWithToken;
|
||||
if (!apiInitialized) {
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: false,
|
||||
message: 'Not authorized!',
|
||||
);
|
||||
}
|
||||
|
||||
_adapter = ApiAdapter(
|
||||
isWithToken: true,
|
||||
region: location,
|
||||
);
|
||||
return success;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<List<ServerProviderLocation>>>
|
||||
getAvailableLocations() async {
|
||||
final List<ServerProviderLocation> locations = [];
|
||||
final result = await _adapter.api().getAvailableLocations();
|
||||
if (result.data.isEmpty || !result.success) {
|
||||
return GenericResult(
|
||||
success: result.success,
|
||||
data: locations,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
final List<HetznerLocation> rawLocations = result.data;
|
||||
for (final rawLocation in rawLocations) {
|
||||
ServerProviderLocation? location;
|
||||
try {
|
||||
location = ServerProviderLocation(
|
||||
title: rawLocation.city,
|
||||
description: rawLocation.description,
|
||||
flag: getEmojiFlag(rawLocation.country),
|
||||
identifier: rawLocation.name,
|
||||
);
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
locations.add(location);
|
||||
}
|
||||
|
||||
return GenericResult(success: true, data: locations);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<List<ServerType>>> getServerTypes({
|
||||
required final ServerProviderLocation location,
|
||||
}) async {
|
||||
final List<ServerType> types = [];
|
||||
final result = await _adapter.api().getAvailableServerTypes();
|
||||
if (result.data.isEmpty || !result.success) {
|
||||
return GenericResult(
|
||||
success: result.success,
|
||||
data: types,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
final rawTypes = result.data;
|
||||
for (final rawType in rawTypes) {
|
||||
for (final rawPrice in rawType.prices) {
|
||||
if (rawPrice.location == location.identifier) {
|
||||
types.add(
|
||||
ServerType(
|
||||
title: rawType.description,
|
||||
identifier: rawType.name,
|
||||
ram: rawType.memory.toDouble(),
|
||||
cores: rawType.cores,
|
||||
disk: DiskSize(byte: rawType.disk * 1024 * 1024 * 1024),
|
||||
price: Price(
|
||||
value: rawPrice.monthly,
|
||||
currency: currency,
|
||||
),
|
||||
location: location,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return GenericResult(success: true, data: types);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<DateTime?>> powerOn(final int serverId) async {
|
||||
DateTime? timestamp;
|
||||
final result = await _adapter.api().powerOn(serverId);
|
||||
if (!result.success) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: timestamp,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
timestamp = DateTime.now();
|
||||
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: timestamp,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<DateTime?>> restart(final int serverId) async {
|
||||
DateTime? timestamp;
|
||||
final result = await _adapter.api().restart(serverId);
|
||||
if (!result.success) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: timestamp,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
timestamp = DateTime.now();
|
||||
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: timestamp,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<Price?>> getPricePerGb() async {
|
||||
final result = await _adapter.api().getPricePerGb();
|
||||
|
||||
if (!result.success || result.data == null) {
|
||||
return GenericResult(
|
||||
|
@ -665,28 +503,12 @@ class HetznerServerProvider extends ServerProvider {
|
|||
);
|
||||
}
|
||||
|
||||
try {
|
||||
volume = ServerVolume(
|
||||
id: result.data!.id,
|
||||
name: result.data!.name,
|
||||
sizeByte: result.data!.size * 1024 * 1024 * 1024,
|
||||
serverId: result.data!.serverId,
|
||||
linuxDevice: result.data!.linuxDevice,
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return GenericResult(
|
||||
data: null,
|
||||
success: false,
|
||||
message: e.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return GenericResult(
|
||||
data: volume,
|
||||
success: true,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
data: Price(
|
||||
value: result.data!,
|
||||
currency: currency,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -739,10 +561,66 @@ class HetznerServerProvider extends ServerProvider {
|
|||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<ServerVolume?>> createVolume() async {
|
||||
ServerVolume? volume;
|
||||
|
||||
final result = await _adapter.api().createVolume();
|
||||
|
||||
if (!result.success || result.data == null) {
|
||||
return GenericResult(
|
||||
data: null,
|
||||
success: false,
|
||||
message: result.message,
|
||||
code: result.code,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
volume = ServerVolume(
|
||||
id: result.data!.id,
|
||||
name: result.data!.name,
|
||||
sizeByte: result.data!.size * 1024 * 1024 * 1024,
|
||||
serverId: result.data!.serverId,
|
||||
linuxDevice: result.data!.linuxDevice,
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return GenericResult(
|
||||
data: null,
|
||||
success: false,
|
||||
message: e.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return GenericResult(
|
||||
data: volume,
|
||||
success: true,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<void>> deleteVolume(final ServerVolume volume) async =>
|
||||
_adapter.api().deleteVolume(volume.id);
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> resizeVolume(
|
||||
final ServerVolume volume,
|
||||
final DiskSize size,
|
||||
) async =>
|
||||
_adapter.api().resizeVolume(
|
||||
HetznerVolume(
|
||||
volume.id,
|
||||
volume.sizeByte,
|
||||
volume.serverId,
|
||||
volume.name,
|
||||
volume.linuxDevice,
|
||||
),
|
||||
size,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> attachVolume(
|
||||
final ServerVolume volume,
|
||||
|
@ -768,40 +646,143 @@ class HetznerServerProvider extends ServerProvider {
|
|||
);
|
||||
|
||||
@override
|
||||
Future<GenericResult<bool>> resizeVolume(
|
||||
final ServerVolume volume,
|
||||
final DiskSize size,
|
||||
) async =>
|
||||
_adapter.api().resizeVolume(
|
||||
HetznerVolume(
|
||||
volume.id,
|
||||
volume.sizeByte,
|
||||
volume.serverId,
|
||||
volume.name,
|
||||
volume.linuxDevice,
|
||||
),
|
||||
size,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<GenericResult<Price?>> getPricePerGb() async {
|
||||
final result = await _adapter.api().getPricePerGb();
|
||||
|
||||
if (!result.success || result.data == null) {
|
||||
Future<GenericResult<List<ServerMetadataEntity>>> getMetadata(
|
||||
final int serverId,
|
||||
) async {
|
||||
List<ServerMetadataEntity> metadata = [];
|
||||
final result = await _adapter.api().getServers();
|
||||
if (result.data.isEmpty || !result.success) {
|
||||
return GenericResult(
|
||||
data: null,
|
||||
success: false,
|
||||
message: result.message,
|
||||
data: metadata,
|
||||
code: result.code,
|
||||
message: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: Price(
|
||||
value: result.data!,
|
||||
currency: currency,
|
||||
final List<HetznerServerInfo> servers = result.data;
|
||||
try {
|
||||
final HetznerServerInfo server = servers.firstWhere(
|
||||
(final server) => server.id == serverId,
|
||||
);
|
||||
|
||||
metadata = [
|
||||
ServerMetadataEntity(
|
||||
type: MetadataType.id,
|
||||
trId: 'server.server_id',
|
||||
value: server.id.toString(),
|
||||
),
|
||||
ServerMetadataEntity(
|
||||
type: MetadataType.status,
|
||||
trId: 'server.status',
|
||||
value: server.status.toString().split('.')[1].capitalize(),
|
||||
),
|
||||
ServerMetadataEntity(
|
||||
type: MetadataType.cpu,
|
||||
trId: 'server.cpu',
|
||||
value: server.serverType.cores.toString(),
|
||||
),
|
||||
ServerMetadataEntity(
|
||||
type: MetadataType.ram,
|
||||
trId: 'server.ram',
|
||||
value: '${server.serverType.memory.toString()} GB',
|
||||
),
|
||||
ServerMetadataEntity(
|
||||
type: MetadataType.cost,
|
||||
trId: 'server.monthly_cost',
|
||||
value:
|
||||
'${server.serverType.prices[1].monthly.toStringAsFixed(2)} ${currency.shortcode}',
|
||||
),
|
||||
ServerMetadataEntity(
|
||||
type: MetadataType.location,
|
||||
trId: 'server.location',
|
||||
value: '${server.location.city}, ${server.location.country}',
|
||||
),
|
||||
ServerMetadataEntity(
|
||||
type: MetadataType.other,
|
||||
trId: 'server.provider',
|
||||
value: _adapter.api().displayProviderName,
|
||||
),
|
||||
];
|
||||
} catch (e) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: [],
|
||||
message: e.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return GenericResult(success: true, data: metadata);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GenericResult<ServerMetrics?>> getMetrics(
|
||||
final int serverId,
|
||||
final DateTime start,
|
||||
final DateTime end,
|
||||
) async {
|
||||
ServerMetrics? metrics;
|
||||
|
||||
List<TimeSeriesData> serializeTimeSeries(
|
||||
final Map<String, dynamic> json,
|
||||
final String type,
|
||||
) {
|
||||
final List list = json['time_series'][type]['values'];
|
||||
return list
|
||||
.map((final el) => TimeSeriesData(el[0], double.parse(el[1])))
|
||||
.toList();
|
||||
}
|
||||
|
||||
final cpuResult = await _adapter.api().getMetrics(
|
||||
serverId,
|
||||
start,
|
||||
end,
|
||||
'cpu',
|
||||
);
|
||||
|
||||
if (cpuResult.data.isEmpty || !cpuResult.success) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: metrics,
|
||||
code: cpuResult.code,
|
||||
message: cpuResult.message,
|
||||
);
|
||||
}
|
||||
|
||||
final netResult = await _adapter.api().getMetrics(
|
||||
serverId,
|
||||
start,
|
||||
end,
|
||||
'network',
|
||||
);
|
||||
|
||||
if (cpuResult.data.isEmpty || !netResult.success) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: metrics,
|
||||
code: netResult.code,
|
||||
message: netResult.message,
|
||||
);
|
||||
}
|
||||
|
||||
metrics = ServerMetrics(
|
||||
cpu: serializeTimeSeries(
|
||||
cpuResult.data,
|
||||
'cpu',
|
||||
),
|
||||
bandwidthIn: serializeTimeSeries(
|
||||
netResult.data,
|
||||
'network.0.bandwidth.in',
|
||||
),
|
||||
bandwidthOut: serializeTimeSeries(
|
||||
netResult.data,
|
||||
'network.0.bandwidth.out',
|
||||
),
|
||||
end: end,
|
||||
start: start,
|
||||
stepsInSecond: cpuResult.data['step'],
|
||||
);
|
||||
|
||||
return GenericResult(data: metrics, success: true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,44 +14,124 @@ export 'package:selfprivacy/logic/api_maps/generic_result.dart';
|
|||
export 'package:selfprivacy/logic/models/launch_installation_data.dart';
|
||||
|
||||
abstract class ServerProvider {
|
||||
/// Returns an assigned enum value, respectively to which
|
||||
/// provider implements [ServerProvider] interface.
|
||||
ServerProviderType get type;
|
||||
|
||||
/// Returns [ServerBasicInfo] of all available machines
|
||||
/// assigned to the authorized user.
|
||||
///
|
||||
/// Only with public IPv4 addresses.
|
||||
Future<GenericResult<List<ServerBasicInfo>>> getServers();
|
||||
Future<GenericResult<bool>> trySetServerLocation(final String location);
|
||||
|
||||
/// Tries to launch installation of SelfPrivacy on
|
||||
/// the requested server entry for the authorized account.
|
||||
/// Depending on a server provider, the algorithm
|
||||
/// and instructions vary drastically.
|
||||
///
|
||||
/// If successly launched, stores new server information.
|
||||
///
|
||||
/// If failure, returns error dialogue information to
|
||||
/// write in ServerInstallationState.
|
||||
Future<GenericResult<CallbackDialogueBranching?>> launchInstallation(
|
||||
final LaunchInstallationData installationData,
|
||||
);
|
||||
|
||||
/// Tries to delete the requested server entry
|
||||
/// from the authorized account, including all assigned
|
||||
/// storage extensions.
|
||||
///
|
||||
/// If failure, returns error dialogue information to
|
||||
/// write in ServerInstallationState.
|
||||
Future<GenericResult<CallbackDialogueBranching?>> deleteServer(
|
||||
final String hostname,
|
||||
);
|
||||
|
||||
/// Tries to access an account linked to the provided token.
|
||||
///
|
||||
/// To generate a token for your account follow instructions of your
|
||||
/// server provider respectfully.
|
||||
///
|
||||
/// If success, saves it for future usage.
|
||||
Future<GenericResult<bool>> tryInitApiByToken(final String token);
|
||||
|
||||
/// Tries to assign the location shortcode for future usage.
|
||||
///
|
||||
/// If API wasn't initialized with token by [tryInitApiByToken] beforehand,
|
||||
/// returns 'Not authorized!' error.
|
||||
Future<GenericResult<bool>> trySetServerLocation(final String location);
|
||||
|
||||
/// Returns all available server locations
|
||||
/// of the authorized user's server provider.
|
||||
Future<GenericResult<List<ServerProviderLocation>>> getAvailableLocations();
|
||||
|
||||
/// Returns [ServerType] of all available
|
||||
/// machine configurations available to the authorized account
|
||||
/// within the requested provider location,
|
||||
/// accessed from [getAvailableLocations].
|
||||
Future<GenericResult<List<ServerType>>> getServerTypes({
|
||||
required final ServerProviderLocation location,
|
||||
});
|
||||
|
||||
Future<GenericResult<CallbackDialogueBranching?>> deleteServer(
|
||||
final String hostname,
|
||||
);
|
||||
Future<GenericResult<CallbackDialogueBranching?>> launchInstallation(
|
||||
final LaunchInstallationData installationData,
|
||||
);
|
||||
/// Tries to power on the requested accessible machine.
|
||||
///
|
||||
/// If success, returns [DateTime] of when the server
|
||||
/// answered the request.
|
||||
Future<GenericResult<DateTime?>> powerOn(final int serverId);
|
||||
|
||||
/// Tries to restart the requested accessible machine.
|
||||
///
|
||||
/// If success, returns [DateTime] of when the server
|
||||
/// answered the request.
|
||||
Future<GenericResult<DateTime?>> restart(final int serverId);
|
||||
|
||||
/// Returns [Price] information per one gigabyte of storage extension for
|
||||
/// the requested accessible machine.
|
||||
Future<GenericResult<Price?>> getPricePerGb();
|
||||
|
||||
/// Returns [ServerVolume] of all available volumes
|
||||
/// assigned to the authorized user and attached to active machine.
|
||||
Future<GenericResult<List<ServerVolume>>> getVolumes({final String? status});
|
||||
|
||||
/// Tries to create an empty unattached [ServerVolume].
|
||||
///
|
||||
/// If success, returns this volume information.
|
||||
Future<GenericResult<ServerVolume?>> createVolume();
|
||||
|
||||
/// Tries to delete the requested accessible [ServerVolume].
|
||||
Future<GenericResult<void>> deleteVolume(final ServerVolume volume);
|
||||
|
||||
/// Tries to resize the requested accessible [ServerVolume]
|
||||
/// to the provided size **(not by!)**, must be greater than current size.
|
||||
Future<GenericResult<bool>> resizeVolume(
|
||||
final ServerVolume volume,
|
||||
final DiskSize size,
|
||||
);
|
||||
|
||||
/// Tries to attach the requested accessible [ServerVolume]
|
||||
/// to an accessible machine by the provided identificator.
|
||||
Future<GenericResult<bool>> attachVolume(
|
||||
final ServerVolume volume,
|
||||
final int serverId,
|
||||
);
|
||||
|
||||
/// Tries to attach the requested accessible [ServerVolume]
|
||||
/// from any machine.
|
||||
Future<GenericResult<bool>> detachVolume(final ServerVolume volume);
|
||||
|
||||
/// Returns metedata of an accessible machine by the provided identificator
|
||||
/// to show on ServerDetailsScreen.
|
||||
Future<GenericResult<List<ServerMetadataEntity>>> getMetadata(
|
||||
final int serverId,
|
||||
);
|
||||
|
||||
/// Returns information about cpu and bandwidth load within the provided
|
||||
/// time period of the requested accessible machine.
|
||||
Future<GenericResult<ServerMetrics?>> getMetrics(
|
||||
final int serverId,
|
||||
final DateTime start,
|
||||
final DateTime end,
|
||||
);
|
||||
|
||||
Future<GenericResult<Price?>> getPricePerGb();
|
||||
Future<GenericResult<List<ServerVolume>>> getVolumes({final String? status});
|
||||
Future<GenericResult<ServerVolume?>> createVolume();
|
||||
Future<GenericResult<void>> deleteVolume(final ServerVolume volume);
|
||||
Future<GenericResult<bool>> resizeVolume(
|
||||
final ServerVolume volume,
|
||||
final DiskSize size,
|
||||
);
|
||||
Future<GenericResult<bool>> attachVolume(
|
||||
final ServerVolume volume,
|
||||
final int serverId,
|
||||
);
|
||||
Future<GenericResult<bool>> detachVolume(final ServerVolume volume);
|
||||
Future<GenericResult<List<ServerMetadataEntity>>> getMetadata(
|
||||
final int serverId,
|
||||
);
|
||||
GenericResult<bool> get success => GenericResult(success: true, data: true);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue