feat: Allow skipping the server provider token when recovering

This commit is contained in:
Inex Code 2024-07-30 04:15:17 +03:00
parent 894d23bb7c
commit 8fe0de0c9e
25 changed files with 341 additions and 176 deletions

View file

@ -138,7 +138,8 @@
"cpu_title": "CPU Usage", "cpu_title": "CPU Usage",
"network_title": "Network Usage", "network_title": "Network Usage",
"in": "In", "in": "In",
"out": "Out" "out": "Out",
"unsupported": "You can't view resource usage charts without the server provider token."
}, },
"server": { "server": {
"card_title": "Server", "card_title": "Server",
@ -511,6 +512,9 @@
"provider_connected": "Connect to {}", "provider_connected": "Connect to {}",
"provider_connected_description": "Enter your token with access to {}:", "provider_connected_description": "Enter your token with access to {}:",
"provider_connected_placeholder": "{} token", "provider_connected_placeholder": "{} token",
"login_later": "Log in later",
"server_provider_unknown": "Unknown server provider",
"server_provider_unknown_description": "Your server provider is not recognized. You can continue without entering its API token.",
"confirm_server": "Confirm server", "confirm_server": "Confirm server",
"confirm_server_description": "Found your server! Confirm it is the right one:", "confirm_server_description": "Found your server! Confirm it is the right one:",
"confirm_server_accept": "Yes! That's it", "confirm_server_accept": "Yes! That's it",

View file

@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/disk_size.dart';
import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/logic/models/disk_status.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart';
@ -74,7 +75,7 @@ class VolumesBloc extends Bloc<VolumesEvent, VolumesState> {
bool isLoaded = false; bool isLoaded = false;
Future<Price?> getPricePerGb() async { Future<Price?> getPricePerGb() async {
if (ProvidersController.currentServerProvider == null) { if (!(ProvidersController.currentServerProvider?.isAuthorized ?? false)) {
return null; return null;
} }
Price? price; Price? price;
@ -92,33 +93,36 @@ class VolumesBloc extends Bloc<VolumesEvent, VolumesState> {
final VolumesServerLoaded event, final VolumesServerLoaded event,
final Emitter<VolumesState> emit, final Emitter<VolumesState> emit,
) async { ) async {
if (ProvidersController.currentServerProvider == null) { if (getIt<ApiConnectionRepository>().currentConnectionStatus ==
ConnectionStatus.nonexistent) {
return; return;
} }
emit(VolumesLoading()); emit(VolumesLoading());
final volumesResult = late final GenericResult<List<ServerProviderVolume>>? volumesResult;
await ProvidersController.currentServerProvider!.getVolumes();
if (!volumesResult.success || volumesResult.data.isEmpty) { if (ProvidersController.currentServerProvider?.isAuthorized ?? false) {
emit(VolumesInitial()); volumesResult =
return; await ProvidersController.currentServerProvider?.getVolumes();
} else {
volumesResult = null;
} }
final serverVolumes = getIt<ApiConnectionRepository>().apiData.volumes.data; final serverVolumes = getIt<ApiConnectionRepository>().apiData.volumes.data;
if (serverVolumes == null) { if (serverVolumes == null &&
volumesResult != null &&
volumesResult.data.isNotEmpty) {
emit(VolumesLoading(providerVolumes: volumesResult.data)); emit(VolumesLoading(providerVolumes: volumesResult.data));
return; return;
} else { } else if (serverVolumes != null) {
emit( emit(
VolumesLoaded( VolumesLoaded(
diskStatus: DiskStatus.fromVolumes( diskStatus: DiskStatus.fromVolumes(
serverVolumes, serverVolumes,
volumesResult.data, volumesResult?.data ?? [],
), ),
providerVolumes: volumesResult.data, providerVolumes: volumesResult?.data ?? [],
serverVolumesHashCode: Object.hashAll(serverVolumes), serverVolumesHashCode: Object.hashAll(serverVolumes),
), ),
); );
@ -186,6 +190,9 @@ class VolumesBloc extends Bloc<VolumesEvent, VolumesState> {
if (state is! VolumesLoaded) { if (state is! VolumesLoaded) {
return; return;
} }
if (ProvidersController.currentServerProvider?.isAuthorized ?? false) {
return;
}
getIt<NavigationService>().showSnackBar( getIt<NavigationService>().showSnackBar(
'storage.extending_volume_started'.tr(), 'storage.extending_volume_started'.tr(),
); );

View file

@ -52,6 +52,8 @@ class MetricsCubit extends Cubit<MetricsState> {
Duration(seconds: state.period.stepPeriodInSeconds), Duration(seconds: state.period.stepPeriodInSeconds),
() => load(state.period), () => load(state.period),
); );
} on MetricsUnsupportedException {
emit(MetricsUnsupported(period));
} }
} }
} }

View file

@ -10,10 +10,15 @@ class MetricsLoadException implements Exception {
final String message; final String message;
} }
class MetricsUnsupportedException implements Exception {
MetricsUnsupportedException(this.message);
final String message;
}
class MetricsRepository { class MetricsRepository {
Future<MetricsLoaded> getMetrics(final Period period) async { Future<MetricsLoaded> getMetrics(final Period period) async {
if (ProvidersController.currentServerProvider == null) { if (!(ProvidersController.currentServerProvider?.isAuthorized ?? false)) {
throw MetricsLoadException('Server Provider data is null'); throw MetricsUnsupportedException('Server Provider data is null');
} }
final DateTime end = DateTime.now(); final DateTime end = DateTime.now();

View file

@ -29,3 +29,12 @@ class MetricsLoaded extends MetricsState {
@override @override
List<Object?> get props => [period, metrics]; List<Object?> get props => [period, metrics];
} }
class MetricsUnsupported extends MetricsState {
const MetricsUnsupported(this.period);
@override
final Period period;
@override
List<Object?> get props => [period];
}

View file

@ -4,6 +4,7 @@ import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart'; import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
import 'package:selfprivacy/logic/models/hive/server.dart';
import 'package:selfprivacy/logic/models/server_metadata.dart'; import 'package:selfprivacy/logic/models/server_metadata.dart';
import 'package:selfprivacy/logic/models/ssh_settings.dart'; import 'package:selfprivacy/logic/models/ssh_settings.dart';
import 'package:selfprivacy/logic/models/system_settings.dart'; import 'package:selfprivacy/logic/models/system_settings.dart';
@ -36,47 +37,63 @@ class ServerDetailsCubit
sshSettings: settings.sshSettings, sshSettings: settings.sshSettings,
), ),
); );
if (state.metadata.isEmpty) {
check();
}
} }
Future<List<ServerMetadataEntity>> get _metadata async { Future<List<ServerMetadataEntity>> get _metadata async {
List<ServerMetadataEntity> data = []; final List<ServerMetadataEntity> data = [];
final serverProviderApi = ProvidersController.currentServerProvider; final serverProviderApi = ProvidersController.currentServerProvider;
final dnsProviderApi = ProvidersController.currentDnsProvider; final dnsProviderApi = ProvidersController.currentDnsProvider;
if (serverProviderApi != null && dnsProviderApi != null) { if (serverProviderApi?.isAuthorized ?? false) {
final serverId = getIt<ResourcesModel>().serverDetails?.id ?? 0; final serverId = getIt<ResourcesModel>().serverDetails?.id ?? 0;
final metadataResult = await serverProviderApi.getMetadata(serverId); final metadataResult = await serverProviderApi?.getMetadata(serverId);
metadataResult.data.add(
data.addAll(metadataResult?.data ?? []);
}
if (serverProviderApi == null || !serverProviderApi.isAuthorized) {
final Server server = getIt<ResourcesModel>().servers.first;
data.add(
ServerMetadataEntity(
type: MetadataType.other,
trId: 'server.server_provider',
value: server.hostingDetails.provider.displayName,
),
);
}
if (dnsProviderApi != null && dnsProviderApi.isAuthorized) {
data.add(
ServerMetadataEntity( ServerMetadataEntity(
trId: 'server.dns_provider', trId: 'server.dns_provider',
value: dnsProviderApi.type.displayName, value: dnsProviderApi.type.displayName,
type: MetadataType.other, type: MetadataType.other,
), ),
); );
} else {
data = metadataResult.data; final Server server = getIt<ResourcesModel>().servers.first;
data.add(
ServerMetadataEntity(
trId: 'server.dns_provider',
value: server.domain.provider.displayName,
type: MetadataType.other,
),
);
} }
return data; return data;
} }
void check() async { void check() async {
final bool isReadyToCheck = getIt<ResourcesModel>().serverDetails != null;
try {
if (isReadyToCheck) {
emit(const ServerDetailsLoading());
final List<ServerMetadataEntity> metadata = await _metadata; final List<ServerMetadataEntity> metadata = await _metadata;
emit( emit(
state.copyWith( state.copyWith(
metadata: metadata, metadata: metadata,
), ),
); );
} else {
emit(const ServerDetailsNotReady());
}
} on StateError {
print('Tried to emit server info state when cubit is closed');
}
} }
@override @override
@ -85,9 +102,7 @@ class ServerDetailsCubit
} }
@override @override
void load() async { void load() async {}
check();
}
@override @override
Future<void> close() { Future<void> close() {

View file

@ -552,6 +552,34 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
); );
} }
void selectRecoveryMethod(final ServerRecoveryMethods method) {
final ServerInstallationRecovery dataState =
state as ServerInstallationRecovery;
switch (method) {
case ServerRecoveryMethods.newDeviceKey:
emit(
dataState.copyWith(
currentStep: RecoveryStep.newDeviceKey,
),
);
break;
case ServerRecoveryMethods.recoveryKey:
emit(
dataState.copyWith(
currentStep: RecoveryStep.recoveryKey,
),
);
break;
case ServerRecoveryMethods.oldToken:
emit(
dataState.copyWith(
currentStep: RecoveryStep.oldToken,
),
);
break;
}
}
void tryToRecover( void tryToRecover(
final String token, final String token,
final ServerRecoveryMethods method, final ServerRecoveryMethods method,
@ -596,8 +624,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
isWithToken: true, isWithToken: true,
overrideDomain: serverDomain.domainName, overrideDomain: serverDomain.domainName,
).getDnsProviderType(); ).getDnsProviderType();
if (serverProvider == ServerProviderType.unknown || if (dnsProvider == DnsProviderType.unknown) {
dnsProvider == DnsProviderType.unknown) {
getIt<NavigationService>() getIt<NavigationService>()
.showSnackBar('recovering.generic_error'.tr()); .showSnackBar('recovering.generic_error'.tr());
return; return;
@ -672,32 +699,13 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
} }
} }
void selectRecoveryMethod(final ServerRecoveryMethods method) { Future<void> skipSettingServerProviderKey() async {
final ServerInstallationRecovery dataState =
state as ServerInstallationRecovery;
switch (method) {
case ServerRecoveryMethods.newDeviceKey:
emit( emit(
dataState.copyWith( (state as ServerInstallationRecovery).copyWith(
currentStep: RecoveryStep.newDeviceKey, providerApiToken: null,
currentStep: RecoveryStep.dnsProviderToken,
), ),
); );
break;
case ServerRecoveryMethods.recoveryKey:
emit(
dataState.copyWith(
currentStep: RecoveryStep.recoveryKey,
),
);
break;
case ServerRecoveryMethods.oldToken:
emit(
dataState.copyWith(
currentStep: RecoveryStep.oldToken,
),
);
break;
}
} }
Future<List<ServerBasicInfoWithValidators>> getAvailableServers() async { Future<List<ServerBasicInfoWithValidators>> getAvailableServers() async {
@ -747,7 +755,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
linuxDevice: '', linuxDevice: '',
), ),
apiToken: dataState.serverDetails!.apiToken, apiToken: dataState.serverDetails!.apiToken,
provider: ServerProviderType.hetzner, provider: dataState.serverDetails!.provider,
); );
await repository.saveDomain(serverDomain); await repository.saveDomain(serverDomain);
await repository.saveServerDetails(serverDetails); await repository.saveServerDetails(serverDetails);
@ -804,16 +812,23 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
await repository.saveIsServerRebootedFirstTime(true); await repository.saveIsServerRebootedFirstTime(true);
await repository.saveIsServerRebootedSecondTime(true); await repository.saveIsServerRebootedSecondTime(true);
await repository.saveIsRecoveringServer(false); await repository.saveIsRecoveringServer(false);
final serverType = await ProvidersController.currentServerProvider! late final GenericResult<ServerType?>? serverType;
.getServerType(state.serverDetails!.id); if (ProvidersController.currentServerProvider?.isAuthorized ?? false) {
serverType = await ProvidersController.currentServerProvider
?.getServerType(state.serverDetails!.id);
if (serverType != null) {
await repository.saveServerType(serverType.data!); await repository.saveServerType(serverType.data!);
await ProvidersController.currentServerProvider! await ProvidersController.currentServerProvider!
.trySetServerLocation(serverType.data!.location.identifier); .trySetServerLocation(serverType.data!.location.identifier);
}
} else {
serverType = null;
}
await repository.saveHasFinalChecked(true); await repository.saveHasFinalChecked(true);
final ServerInstallationRecovery updatedState = final ServerInstallationRecovery updatedState =
(state as ServerInstallationRecovery).copyWith( (state as ServerInstallationRecovery).copyWith(
backblazeCredential: backblazeCredential, backblazeCredential: backblazeCredential,
serverTypeIdentificator: serverType.data!.identifier, serverTypeIdentificator: serverType?.data?.identifier,
); );
emit(updatedState.finish()); emit(updatedState.finish());
getIt<ApiConnectionRepository>().init(); getIt<ApiConnectionRepository>().init();

View file

@ -95,8 +95,8 @@ class ServerInstallationRepository {
// We have a server set up, so we load it // We have a server set up, so we load it
TlsOptions.verifyCertificate = true; TlsOptions.verifyCertificate = true;
return ServerInstallationFinished( return ServerInstallationFinished(
providerApiToken: providerApiToken!, providerApiToken: providerApiToken,
serverTypeIdentificator: serverTypeIdentificator!, serverTypeIdentificator: serverTypeIdentificator,
dnsApiToken: dnsApiToken!, dnsApiToken: dnsApiToken!,
serverDomain: serverDomain!, serverDomain: serverDomain!,
backblazeCredential: backblazeCredential!, backblazeCredential: backblazeCredential!,
@ -105,6 +105,30 @@ class ServerInstallationRepository {
} }
} }
// If wizard data has info on the providers, init them
if (wizardData.serverProviderType != null &&
wizardData.serverProviderType != ServerProviderType.unknown) {
ProvidersController.initServerProvider(
ServerProviderSettings(
provider: wizardData.serverProviderType!,
isAuthorized: wizardData.serverProviderKey != null,
location: wizardData.serverLocation,
token: wizardData.serverProviderKey,
),
);
}
if (wizardData.dnsProviderType != null &&
wizardData.dnsProviderType != DnsProviderType.unknown) {
ProvidersController.initDnsProvider(
DnsProviderSettings(
provider: wizardData.dnsProviderType!,
isAuthorized: wizardData.dnsProviderKey != null,
token: wizardData.dnsProviderKey,
),
);
}
if (wizardData.isRecoveringServer && wizardData.serverDomain != null) { if (wizardData.isRecoveringServer && wizardData.serverDomain != null) {
return ServerInstallationRecovery( return ServerInstallationRecovery(
providerApiToken: wizardData.serverProviderKey, providerApiToken: wizardData.serverProviderKey,
@ -548,17 +572,23 @@ class ServerInstallationRepository {
domain: wizardData.serverDomain!, domain: wizardData.serverDomain!,
), ),
); );
if (wizardData.serverProviderKey != null) {
await getIt<ResourcesModel>().associateServerWithToken( await getIt<ResourcesModel>().associateServerWithToken(
wizardData.serverDetails!.id, wizardData.serverDetails!.id,
wizardData.serverProviderKey!, wizardData.serverProviderKey!,
); );
}
if (wizardData.dnsProviderKey != null) {
await getIt<ResourcesModel>().associateDomainWithToken( await getIt<ResourcesModel>().associateDomainWithToken(
wizardData.serverDomain!.domainName, wizardData.serverDomain!.domainName,
wizardData.dnsProviderKey!, wizardData.dnsProviderKey!,
); );
}
if (wizardData.backupsCredential != null) {
await getIt<ResourcesModel>().addBackupsCredential( await getIt<ResourcesModel>().addBackupsCredential(
wizardData.backupsCredential!, wizardData.backupsCredential!,
); );
}
await getIt<WizardDataModel>().clearServerInstallation(); await getIt<WizardDataModel>().clearServerInstallation();
} }
} }

View file

@ -226,8 +226,8 @@ class ServerInstallationNotFinished extends ServerInstallationState {
); );
ServerInstallationFinished finish() => ServerInstallationFinished( ServerInstallationFinished finish() => ServerInstallationFinished(
providerApiToken: providerApiToken!, providerApiToken: providerApiToken,
serverTypeIdentificator: serverTypeIdentificator!, serverTypeIdentificator: serverTypeIdentificator,
dnsApiToken: dnsApiToken!, dnsApiToken: dnsApiToken!,
backblazeCredential: backblazeCredential!, backblazeCredential: backblazeCredential!,
serverDomain: serverDomain!, serverDomain: serverDomain!,
@ -257,12 +257,12 @@ class ServerInstallationEmpty extends ServerInstallationNotFinished {
class ServerInstallationFinished extends ServerInstallationState { class ServerInstallationFinished extends ServerInstallationState {
const ServerInstallationFinished({ const ServerInstallationFinished({
required String super.providerApiToken,
required String super.serverTypeIdentificator,
required String super.dnsApiToken, required String super.dnsApiToken,
required BackupsCredential super.backblazeCredential, required BackupsCredential super.backblazeCredential,
required ServerDomain super.serverDomain, required ServerDomain super.serverDomain,
required ServerHostingDetails super.serverDetails, required ServerHostingDetails super.serverDetails,
super.providerApiToken,
super.serverTypeIdentificator,
}) : super( }) : super(
rootUser: null, rootUser: null,
isServerStarted: true, isServerStarted: true,
@ -367,8 +367,8 @@ class ServerInstallationRecovery extends ServerInstallationState {
); );
ServerInstallationFinished finish() => ServerInstallationFinished( ServerInstallationFinished finish() => ServerInstallationFinished(
providerApiToken: providerApiToken!, providerApiToken: providerApiToken,
serverTypeIdentificator: serverTypeIdentificator!, serverTypeIdentificator: serverTypeIdentificator,
dnsApiToken: dnsApiToken!, dnsApiToken: dnsApiToken!,
backblazeCredential: backblazeCredential!, backblazeCredential: backblazeCredential!,
serverDomain: serverDomain!, serverDomain: serverDomain!,

View file

@ -320,20 +320,20 @@ class ApiConnectionRepository {
await _apiData.serverJobs await _apiData.serverJobs
.refetchData(version, () => _dataStream.add(_apiData)); .refetchData(version, () => _dataStream.add(_apiData));
} }
await _apiData.backups
.refetchData(version, () => _dataStream.add(_apiData));
await _apiData.backupConfig
.refetchData(version, () => _dataStream.add(_apiData));
await _apiData.services await _apiData.services
.refetchData(version, () => _dataStream.add(_apiData)); .refetchData(version, () => _dataStream.add(_apiData));
await _apiData.users.refetchData(version, () => _dataStream.add(_apiData));
await _apiData.volumes await _apiData.volumes
.refetchData(version, () => _dataStream.add(_apiData)); .refetchData(version, () => _dataStream.add(_apiData));
await _apiData.settings
.refetchData(version, () => _dataStream.add(_apiData));
await _apiData.recoveryKeyStatus await _apiData.recoveryKeyStatus
.refetchData(version, () => _dataStream.add(_apiData)); .refetchData(version, () => _dataStream.add(_apiData));
await _apiData.devices await _apiData.devices
.refetchData(version, () => _dataStream.add(_apiData)); .refetchData(version, () => _dataStream.add(_apiData));
await _apiData.users.refetchData(version, () => _dataStream.add(_apiData)); await _apiData.backupConfig
await _apiData.settings .refetchData(version, () => _dataStream.add(_apiData));
await _apiData.backups
.refetchData(version, () => _dataStream.add(_apiData)); .refetchData(version, () => _dataStream.add(_apiData));
} }

View file

@ -12,7 +12,7 @@ class DiskVolume {
this.providerVolume, this.providerVolume,
}); });
DiskVolume.fromServerDiscVolume( DiskVolume.fromServerAndDiskVolume(
final ServerDiskVolume volume, final ServerDiskVolume volume,
final ServerProviderVolume? providerVolume, final ServerProviderVolume? providerVolume,
) : this( ) : this(
@ -104,7 +104,7 @@ class DiskStatus {
} }
final DiskVolume diskVolume = final DiskVolume diskVolume =
DiskVolume.fromServerDiscVolume(volume, providerVolume); DiskVolume.fromServerAndDiskVolume(volume, providerVolume);
return diskVolume; return diskVolume;
}).toList(); }).toList();

View file

@ -36,6 +36,9 @@ class BackblazeBackupsProvider extends BackupsProvider {
final ApiAdapter _adapter; final ApiAdapter _adapter;
@override
bool get isAuthorized => _adapter.api().isWithToken;
@override @override
BackupsProviderType get type => BackupsProviderType.backblaze; BackupsProviderType get type => BackupsProviderType.backblaze;

View file

@ -19,6 +19,8 @@ abstract class BackupsProvider {
/// provider implements [BackupsProvider] interface. /// provider implements [BackupsProvider] interface.
BackupsProviderType get type; BackupsProviderType get type;
bool get isAuthorized;
/// Returns a full url to a guide on how to setup /// Returns a full url to a guide on how to setup
/// backups provider /// backups provider
String get howToRegister; String get howToRegister;

View file

@ -27,7 +27,7 @@ class ApiAdapter {
} }
class CloudflareDnsProvider extends DnsProvider { class CloudflareDnsProvider extends DnsProvider {
CloudflareDnsProvider() : _adapter = ApiAdapter(); CloudflareDnsProvider() : _adapter = ApiAdapter(isWithToken: false);
CloudflareDnsProvider.load( CloudflareDnsProvider.load(
final bool isAuthorized, final bool isAuthorized,
final String? token, final String? token,
@ -38,6 +38,9 @@ class CloudflareDnsProvider extends DnsProvider {
ApiAdapter _adapter; ApiAdapter _adapter;
@override
bool get isAuthorized => _adapter.api().isWithToken;
@override @override
DnsProviderType get type => DnsProviderType.cloudflare; DnsProviderType get type => DnsProviderType.cloudflare;

View file

@ -21,7 +21,7 @@ class ApiAdapter {
} }
class DesecDnsProvider extends DnsProvider { class DesecDnsProvider extends DnsProvider {
DesecDnsProvider() : _adapter = ApiAdapter(); DesecDnsProvider() : _adapter = ApiAdapter(isWithToken: false);
DesecDnsProvider.load( DesecDnsProvider.load(
final bool isAuthorized, final bool isAuthorized,
final String? token, final String? token,
@ -32,6 +32,9 @@ class DesecDnsProvider extends DnsProvider {
final ApiAdapter _adapter; final ApiAdapter _adapter;
@override
bool get isAuthorized => _adapter.api().isWithToken;
@override @override
DnsProviderType get type => DnsProviderType.desec; DnsProviderType get type => DnsProviderType.desec;

View file

@ -21,7 +21,7 @@ class ApiAdapter {
} }
class DigitalOceanDnsProvider extends DnsProvider { class DigitalOceanDnsProvider extends DnsProvider {
DigitalOceanDnsProvider() : _adapter = ApiAdapter(); DigitalOceanDnsProvider() : _adapter = ApiAdapter(isWithToken: false);
DigitalOceanDnsProvider.load( DigitalOceanDnsProvider.load(
final bool isAuthorized, final bool isAuthorized,
final String? token, final String? token,
@ -32,6 +32,9 @@ class DigitalOceanDnsProvider extends DnsProvider {
final ApiAdapter _adapter; final ApiAdapter _adapter;
@override
bool get isAuthorized => _adapter.api().isWithToken;
@override @override
DnsProviderType get type => DnsProviderType.digitalOcean; DnsProviderType get type => DnsProviderType.digitalOcean;

View file

@ -8,6 +8,8 @@ abstract class DnsProvider {
/// provider implements [DnsProvider] interface. /// provider implements [DnsProvider] interface.
DnsProviderType get type; DnsProviderType get type;
bool get isAuthorized;
/// Returns a full url to a guide on how to setup /// Returns a full url to a guide on how to setup
/// DNS provider nameservers /// DNS provider nameservers
String get howToRegister; String get howToRegister;

View file

@ -39,7 +39,7 @@ class ApiAdapter {
} }
class DigitalOceanServerProvider extends ServerProvider { class DigitalOceanServerProvider extends ServerProvider {
DigitalOceanServerProvider() : _adapter = ApiAdapter(); DigitalOceanServerProvider() : _adapter = ApiAdapter(isWithToken: false);
DigitalOceanServerProvider.load( DigitalOceanServerProvider.load(
final String? location, final String? location,
final bool isAuthorized, final bool isAuthorized,
@ -53,6 +53,9 @@ class DigitalOceanServerProvider extends ServerProvider {
ApiAdapter _adapter; ApiAdapter _adapter;
final Currency currency = Currency.fromType(CurrencyType.usd); final Currency currency = Currency.fromType(CurrencyType.usd);
@override
bool get isAuthorized => _adapter.api().isWithToken;
@override @override
ServerProviderType get type => ServerProviderType.digitalOcean; ServerProviderType get type => ServerProviderType.digitalOcean;

View file

@ -39,7 +39,7 @@ class ApiAdapter {
} }
class HetznerServerProvider extends ServerProvider { class HetznerServerProvider extends ServerProvider {
HetznerServerProvider() : _adapter = ApiAdapter(); HetznerServerProvider() : _adapter = ApiAdapter(isWithToken: false);
HetznerServerProvider.load( HetznerServerProvider.load(
final String? location, final String? location,
final bool isAuthorized, final bool isAuthorized,
@ -54,6 +54,9 @@ class HetznerServerProvider extends ServerProvider {
final Currency currency = Currency.fromType(CurrencyType.eur); final Currency currency = Currency.fromType(CurrencyType.eur);
int? cachedCoreAmount; int? cachedCoreAmount;
@override
bool get isAuthorized => _adapter.api().isWithToken;
@override @override
ServerProviderType get type => ServerProviderType.hetzner; ServerProviderType get type => ServerProviderType.hetzner;

View file

@ -18,6 +18,8 @@ abstract class ServerProvider {
/// provider implements [ServerProvider] interface. /// provider implements [ServerProvider] interface.
ServerProviderType get type; ServerProviderType get type;
bool get isAuthorized;
/// Returns [ServerBasicInfo] of all available machines /// Returns [ServerBasicInfo] of all available machines
/// assigned to the authorized user. /// assigned to the authorized user.
/// ///

View file

@ -100,12 +100,33 @@ class _Chart extends StatelessWidget {
), ),
), ),
]; ];
} else if (state is MetricsUnsupported) {
charts = [
FilledCard(
clipped: false,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'resource_chart.unsupported'.tr(),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
),
),
];
} else { } else {
throw 'wrong state'; throw 'wrong state';
} }
return Column( return Column(
children: [ children: [
if (state is! MetricsUnsupported)
SegmentedButtons( SegmentedButtons(
isSelected: [ isSelected: [
period == Period.month, period == Period.month,

View file

@ -84,7 +84,11 @@ class _ServerLogsScreenState extends State<ServerLogsScreen> {
const Key centerKey = ValueKey<String>('server-logs-center-key'); const Key centerKey = ValueKey<String>('server-logs-center-key');
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(widget.serviceId == null ? 'server.logs'.tr() : 'service_page.logs'.tr()), title: Text(
widget.serviceId == null
? 'server.logs'.tr()
: 'service_page.logs'.tr(),
),
), ),
endDrawer: BlocBuilder<ServerLogsBloc, ServerLogsState>( endDrawer: BlocBuilder<ServerLogsBloc, ServerLogsState>(
builder: (final context, final state) { builder: (final context, final state) {
@ -160,10 +164,12 @@ class _ServerLogsScreenState extends State<ServerLogsScreen> {
], ],
); );
} else if (state is ServerLogsError) { } else if (state is ServerLogsError) {
return EmptyPagePlaceholder( return Center(
child: EmptyPagePlaceholder(
title: 'basis.error'.tr(), title: 'basis.error'.tr(),
iconData: Icons.error_outline, iconData: Icons.error_outline,
description: state.error.toString(), description: state.error.toString(),
),
); );
} }
return Center(child: Text('server.no_logs'.tr())); return Center(child: Text('server.no_logs'.tr()));

View file

@ -63,9 +63,7 @@ class _ServerDetailsScreenState extends State<ServerDetailsScreen>
); );
} }
return BlocProvider( return BrandHeroScreen(
create: (final context) => context.read<ServerDetailsCubit>()..check(),
child: BrandHeroScreen(
hasFlashButton: true, hasFlashButton: true,
heroIcon: BrandIcons.server, heroIcon: BrandIcons.server,
heroTitle: 'server.card_title'.tr(), heroTitle: 'server.card_title'.tr(),
@ -98,7 +96,6 @@ class _ServerDetailsScreenState extends State<ServerDetailsScreen>
const SizedBox(height: 8), const SizedBox(height: 8),
_TextDetails(), _TextDetails(),
], ],
),
); );
} }
} }

View file

@ -47,7 +47,7 @@ class _TempMessage extends StatelessWidget {
final String message; final String message;
@override @override
Widget build(final BuildContext context) => SizedBox( Widget build(final BuildContext context) => SizedBox(
height: MediaQuery.of(context).size.height - 100, height: 200,
child: Center( child: Center(
child: Text( child: Text(
message, message,

View file

@ -1,9 +1,11 @@
import 'package:cubit_form/cubit_form.dart'; import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/server_provider_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/server_provider_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
@ -15,6 +17,27 @@ class RecoveryServerProviderConnected extends StatelessWidget {
final ServerInstallationCubit appConfig = final ServerInstallationCubit appConfig =
context.watch<ServerInstallationCubit>(); context.watch<ServerInstallationCubit>();
if (appConfig.state.serverDetails?.provider == ServerProviderType.unknown) {
return BrandHeroScreen(
heroTitle: 'recovering.server_provider_unknown'.tr(),
heroSubtitle: 'recovering.server_provider_unknown_description'.tr(),
hasBackButton: true,
hasFlashButton: false,
ignoreBreakpoints: true,
onBackButtonPressed: () {
Navigator.of(context).popUntil((final route) => route.isFirst);
},
children: [
BrandButton.filled(
text: 'basis.continue'.tr(),
onPressed: () => context
.read<ServerInstallationCubit>()
.skipSettingServerProviderKey(),
),
],
);
}
return BlocProvider( return BlocProvider(
create: (final BuildContext context) => create: (final BuildContext context) =>
ServerProviderFormCubit(appConfig), ServerProviderFormCubit(appConfig),
@ -50,13 +73,13 @@ class RecoveryServerProviderConnected extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(height: 16), const Gap(16),
BrandButton.filled( BrandButton.filled(
onPressed: () => onPressed: () =>
context.read<ServerProviderFormCubit>().trySubmit(), context.read<ServerProviderFormCubit>().trySubmit(),
child: Text('basis.continue'.tr()), child: Text('basis.continue'.tr()),
), ),
const SizedBox(height: 16), const Gap(16),
Builder( Builder(
builder: (final context) => BrandButton.text( builder: (final context) => BrandButton.text(
title: 'initializing.how'.tr(), title: 'initializing.how'.tr(),
@ -66,6 +89,13 @@ class RecoveryServerProviderConnected extends StatelessWidget {
), ),
), ),
), ),
const Gap(16),
BrandButton.text(
title: 'recovering.login_later'.tr(),
onPressed: () => context
.read<ServerInstallationCubit>()
.skipSettingServerProviderKey(),
),
], ],
), ),
), ),