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",
"network_title": "Network Usage",
"in": "In",
"out": "Out"
"out": "Out",
"unsupported": "You can't view resource usage charts without the server provider token."
},
"server": {
"card_title": "Server",
@ -511,6 +512,9 @@
"provider_connected": "Connect to {}",
"provider_connected_description": "Enter your token with access to {}:",
"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_description": "Found your server! Confirm it is the right one:",
"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:flutter_bloc/flutter_bloc.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_status.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
@ -74,7 +75,7 @@ class VolumesBloc extends Bloc<VolumesEvent, VolumesState> {
bool isLoaded = false;
Future<Price?> getPricePerGb() async {
if (ProvidersController.currentServerProvider == null) {
if (!(ProvidersController.currentServerProvider?.isAuthorized ?? false)) {
return null;
}
Price? price;
@ -92,33 +93,36 @@ class VolumesBloc extends Bloc<VolumesEvent, VolumesState> {
final VolumesServerLoaded event,
final Emitter<VolumesState> emit,
) async {
if (ProvidersController.currentServerProvider == null) {
if (getIt<ApiConnectionRepository>().currentConnectionStatus ==
ConnectionStatus.nonexistent) {
return;
}
emit(VolumesLoading());
final volumesResult =
await ProvidersController.currentServerProvider!.getVolumes();
late final GenericResult<List<ServerProviderVolume>>? volumesResult;
if (!volumesResult.success || volumesResult.data.isEmpty) {
emit(VolumesInitial());
return;
if (ProvidersController.currentServerProvider?.isAuthorized ?? false) {
volumesResult =
await ProvidersController.currentServerProvider?.getVolumes();
} else {
volumesResult = null;
}
final serverVolumes = getIt<ApiConnectionRepository>().apiData.volumes.data;
if (serverVolumes == null) {
if (serverVolumes == null &&
volumesResult != null &&
volumesResult.data.isNotEmpty) {
emit(VolumesLoading(providerVolumes: volumesResult.data));
return;
} else {
} else if (serverVolumes != null) {
emit(
VolumesLoaded(
diskStatus: DiskStatus.fromVolumes(
serverVolumes,
volumesResult.data,
volumesResult?.data ?? [],
),
providerVolumes: volumesResult.data,
providerVolumes: volumesResult?.data ?? [],
serverVolumesHashCode: Object.hashAll(serverVolumes),
),
);
@ -186,6 +190,9 @@ class VolumesBloc extends Bloc<VolumesEvent, VolumesState> {
if (state is! VolumesLoaded) {
return;
}
if (ProvidersController.currentServerProvider?.isAuthorized ?? false) {
return;
}
getIt<NavigationService>().showSnackBar(
'storage.extending_volume_started'.tr(),
);

View file

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

View file

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

View file

@ -29,3 +29,12 @@ class MetricsLoaded extends MetricsState {
@override
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/get_it/resources_model.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/ssh_settings.dart';
import 'package:selfprivacy/logic/models/system_settings.dart';
@ -36,47 +37,63 @@ class ServerDetailsCubit
sshSettings: settings.sshSettings,
),
);
if (state.metadata.isEmpty) {
check();
}
}
Future<List<ServerMetadataEntity>> get _metadata async {
List<ServerMetadataEntity> data = [];
final List<ServerMetadataEntity> data = [];
final serverProviderApi = ProvidersController.currentServerProvider;
final dnsProviderApi = ProvidersController.currentDnsProvider;
if (serverProviderApi != null && dnsProviderApi != null) {
if (serverProviderApi?.isAuthorized ?? false) {
final serverId = getIt<ResourcesModel>().serverDetails?.id ?? 0;
final metadataResult = await serverProviderApi.getMetadata(serverId);
metadataResult.data.add(
final metadataResult = await serverProviderApi?.getMetadata(serverId);
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(
trId: 'server.dns_provider',
value: dnsProviderApi.type.displayName,
type: MetadataType.other,
),
);
data = metadataResult.data;
} else {
final Server server = getIt<ResourcesModel>().servers.first;
data.add(
ServerMetadataEntity(
trId: 'server.dns_provider',
value: server.domain.provider.displayName,
type: MetadataType.other,
),
);
}
return data;
}
void check() async {
final bool isReadyToCheck = getIt<ResourcesModel>().serverDetails != null;
try {
if (isReadyToCheck) {
emit(const ServerDetailsLoading());
final List<ServerMetadataEntity> metadata = await _metadata;
emit(
state.copyWith(
metadata: metadata,
),
);
} else {
emit(const ServerDetailsNotReady());
}
} on StateError {
print('Tried to emit server info state when cubit is closed');
}
final List<ServerMetadataEntity> metadata = await _metadata;
emit(
state.copyWith(
metadata: metadata,
),
);
}
@override
@ -85,9 +102,7 @@ class ServerDetailsCubit
}
@override
void load() async {
check();
}
void load() async {}
@override
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(
final String token,
final ServerRecoveryMethods method,
@ -596,8 +624,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
isWithToken: true,
overrideDomain: serverDomain.domainName,
).getDnsProviderType();
if (serverProvider == ServerProviderType.unknown ||
dnsProvider == DnsProviderType.unknown) {
if (dnsProvider == DnsProviderType.unknown) {
getIt<NavigationService>()
.showSnackBar('recovering.generic_error'.tr());
return;
@ -672,32 +699,13 @@ 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;
}
Future<void> skipSettingServerProviderKey() async {
emit(
(state as ServerInstallationRecovery).copyWith(
providerApiToken: null,
currentStep: RecoveryStep.dnsProviderToken,
),
);
}
Future<List<ServerBasicInfoWithValidators>> getAvailableServers() async {
@ -747,7 +755,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
linuxDevice: '',
),
apiToken: dataState.serverDetails!.apiToken,
provider: ServerProviderType.hetzner,
provider: dataState.serverDetails!.provider,
);
await repository.saveDomain(serverDomain);
await repository.saveServerDetails(serverDetails);
@ -804,16 +812,23 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
await repository.saveIsServerRebootedFirstTime(true);
await repository.saveIsServerRebootedSecondTime(true);
await repository.saveIsRecoveringServer(false);
final serverType = await ProvidersController.currentServerProvider!
.getServerType(state.serverDetails!.id);
await repository.saveServerType(serverType.data!);
await ProvidersController.currentServerProvider!
.trySetServerLocation(serverType.data!.location.identifier);
late final GenericResult<ServerType?>? serverType;
if (ProvidersController.currentServerProvider?.isAuthorized ?? false) {
serverType = await ProvidersController.currentServerProvider
?.getServerType(state.serverDetails!.id);
if (serverType != null) {
await repository.saveServerType(serverType.data!);
await ProvidersController.currentServerProvider!
.trySetServerLocation(serverType.data!.location.identifier);
}
} else {
serverType = null;
}
await repository.saveHasFinalChecked(true);
final ServerInstallationRecovery updatedState =
(state as ServerInstallationRecovery).copyWith(
backblazeCredential: backblazeCredential,
serverTypeIdentificator: serverType.data!.identifier,
serverTypeIdentificator: serverType?.data?.identifier,
);
emit(updatedState.finish());
getIt<ApiConnectionRepository>().init();

View file

@ -95,8 +95,8 @@ class ServerInstallationRepository {
// We have a server set up, so we load it
TlsOptions.verifyCertificate = true;
return ServerInstallationFinished(
providerApiToken: providerApiToken!,
serverTypeIdentificator: serverTypeIdentificator!,
providerApiToken: providerApiToken,
serverTypeIdentificator: serverTypeIdentificator,
dnsApiToken: dnsApiToken!,
serverDomain: serverDomain!,
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) {
return ServerInstallationRecovery(
providerApiToken: wizardData.serverProviderKey,
@ -548,17 +572,23 @@ class ServerInstallationRepository {
domain: wizardData.serverDomain!,
),
);
await getIt<ResourcesModel>().associateServerWithToken(
wizardData.serverDetails!.id,
wizardData.serverProviderKey!,
);
await getIt<ResourcesModel>().associateDomainWithToken(
wizardData.serverDomain!.domainName,
wizardData.dnsProviderKey!,
);
await getIt<ResourcesModel>().addBackupsCredential(
wizardData.backupsCredential!,
);
if (wizardData.serverProviderKey != null) {
await getIt<ResourcesModel>().associateServerWithToken(
wizardData.serverDetails!.id,
wizardData.serverProviderKey!,
);
}
if (wizardData.dnsProviderKey != null) {
await getIt<ResourcesModel>().associateDomainWithToken(
wizardData.serverDomain!.domainName,
wizardData.dnsProviderKey!,
);
}
if (wizardData.backupsCredential != null) {
await getIt<ResourcesModel>().addBackupsCredential(
wizardData.backupsCredential!,
);
}
await getIt<WizardDataModel>().clearServerInstallation();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -100,37 +100,58 @@ 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 {
throw 'wrong state';
}
return Column(
children: [
SegmentedButtons(
isSelected: [
period == Period.month,
period == Period.day,
period == Period.hour,
],
onPressed: (final index) {
switch (index) {
case 0:
cubit.changePeriod(Period.month);
break;
case 1:
cubit.changePeriod(Period.day);
break;
case 2:
cubit.changePeriod(Period.hour);
break;
}
},
titles: [
'resource_chart.month'.tr(),
'resource_chart.day'.tr(),
'resource_chart.hour'.tr(),
],
),
if (state is! MetricsUnsupported)
SegmentedButtons(
isSelected: [
period == Period.month,
period == Period.day,
period == Period.hour,
],
onPressed: (final index) {
switch (index) {
case 0:
cubit.changePeriod(Period.month);
break;
case 1:
cubit.changePeriod(Period.day);
break;
case 2:
cubit.changePeriod(Period.hour);
break;
}
},
titles: [
'resource_chart.month'.tr(),
'resource_chart.day'.tr(),
'resource_chart.hour'.tr(),
],
),
const SizedBox(height: 8),
...charts,
],

View file

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

View file

@ -63,42 +63,39 @@ class _ServerDetailsScreenState extends State<ServerDetailsScreen>
);
}
return BlocProvider(
create: (final context) => context.read<ServerDetailsCubit>()..check(),
child: BrandHeroScreen(
hasFlashButton: true,
heroIcon: BrandIcons.server,
heroTitle: 'server.card_title'.tr(),
heroSubtitle: 'server.description'.tr(),
children: [
StorageCard(
diskStatus: context.watch<VolumesBloc>().state.diskStatus,
),
const SizedBox(height: 16),
ListTile(
title: Text('server.settings'.tr()),
leading: const Icon(BrandIcons.settings),
onTap: () => context.pushRoute(const ServerSettingsRoute()),
),
ListTile(
title: Text('server.logs'.tr()),
leading: const Icon(Icons.manage_search_outlined),
onTap: () => context.pushRoute(ServerLogsRoute()),
),
const Divider(height: 32),
Text(
'server.resource_usage'.tr(),
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
BlocProvider(
create: (final context) => MetricsCubit()..restart(),
child: _Chart(),
),
const SizedBox(height: 8),
_TextDetails(),
],
),
return BrandHeroScreen(
hasFlashButton: true,
heroIcon: BrandIcons.server,
heroTitle: 'server.card_title'.tr(),
heroSubtitle: 'server.description'.tr(),
children: [
StorageCard(
diskStatus: context.watch<VolumesBloc>().state.diskStatus,
),
const SizedBox(height: 16),
ListTile(
title: Text('server.settings'.tr()),
leading: const Icon(BrandIcons.settings),
onTap: () => context.pushRoute(const ServerSettingsRoute()),
),
ListTile(
title: Text('server.logs'.tr()),
leading: const Icon(Icons.manage_search_outlined),
onTap: () => context.pushRoute(ServerLogsRoute()),
),
const Divider(height: 32),
Text(
'server.resource_usage'.tr(),
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
BlocProvider(
create: (final context) => MetricsCubit()..restart(),
child: _Chart(),
),
const SizedBox(height: 8),
_TextDetails(),
],
);
}
}

View file

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

View file

@ -1,9 +1,11 @@
import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.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/server_installation/server_installation_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/layouts/brand_hero_screen.dart';
@ -15,6 +17,27 @@ class RecoveryServerProviderConnected extends StatelessWidget {
final ServerInstallationCubit appConfig =
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(
create: (final BuildContext context) =>
ServerProviderFormCubit(appConfig),
@ -50,13 +73,13 @@ class RecoveryServerProviderConnected extends StatelessWidget {
),
),
),
const SizedBox(height: 16),
const Gap(16),
BrandButton.filled(
onPressed: () =>
context.read<ServerProviderFormCubit>().trySubmit(),
child: Text('basis.continue'.tr()),
),
const SizedBox(height: 16),
const Gap(16),
Builder(
builder: (final context) => BrandButton.text(
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(),
),
],
),
),