forked from kherel/selfprivacy.org.app
parent
bc6c55b528
commit
804147b8d6
@ -0,0 +1,7 @@ |
||||
targets: |
||||
$default: |
||||
builders: |
||||
json_serializable: |
||||
options: |
||||
create_factory: true |
||||
create_to_json: false |
@ -0,0 +1,24 @@ |
||||
import 'package:bloc/bloc.dart'; |
||||
import 'package:equatable/equatable.dart'; |
||||
import 'package:selfprivacy/config/get_it_config.dart'; |
||||
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_repository.dart'; |
||||
import 'package:selfprivacy/logic/models/hetzner_server_info.dart'; |
||||
|
||||
part 'server_detailed_info_state.dart'; |
||||
|
||||
class ServerDetailsCubit extends Cubit<ServerDetailsState> { |
||||
ServerDetailsCubit() : super(ServerDetailsInitial()); |
||||
|
||||
ServerDetailsRepository repository = ServerDetailsRepository(); |
||||
|
||||
void check() async { |
||||
var isReadyToCheck = getIt<ApiConfigModel>().hetznerServer != null; |
||||
if (isReadyToCheck) { |
||||
emit(ServerDetailsLoading()); |
||||
var data = await repository.load(); |
||||
emit(Loaded(serverInfo: data, checkTime: DateTime.now())); |
||||
} else { |
||||
emit(ServerDetailsNotReady()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
import 'package:selfprivacy/logic/api_maps/hetzner.dart'; |
||||
import 'package:selfprivacy/logic/models/hetzner_server_info.dart'; |
||||
|
||||
class ServerDetailsRepository { |
||||
Future<HetznerServerInfo> load() async { |
||||
var client = HetznerApi(); |
||||
return await client.getInfo(); |
||||
} |
||||
} |
@ -0,0 +1,29 @@ |
||||
part of 'server_detailed_info_cubit.dart'; |
||||
|
||||
abstract class ServerDetailsState extends Equatable { |
||||
const ServerDetailsState(); |
||||
|
||||
@override |
||||
List<Object> get props => []; |
||||
} |
||||
|
||||
class ServerDetailsInitial extends ServerDetailsState {} |
||||
|
||||
class ServerDetailsLoading extends ServerDetailsState {} |
||||
|
||||
class ServerDetailsNotReady extends ServerDetailsState {} |
||||
|
||||
class Loading extends ServerDetailsState {} |
||||
|
||||
class Loaded extends ServerDetailsState { |
||||
final HetznerServerInfo serverInfo; |
||||
final DateTime checkTime; |
||||
|
||||
Loaded({ |
||||
required this.serverInfo, |
||||
required this.checkTime, |
||||
}); |
||||
|
||||
@override |
||||
List<Object> get props => [serverInfo, checkTime]; |
||||
} |
@ -0,0 +1,89 @@ |
||||
import 'package:json_annotation/json_annotation.dart'; |
||||
|
||||
part 'hetzner_server_info.g.dart'; |
||||
|
||||
@JsonSerializable() |
||||
class HetznerServerInfo { |
||||
final int id; |
||||
final String name; |
||||
final ServerStatus status; |
||||
final DateTime created; |
||||
|
||||
@JsonKey(name: 'server_type') |
||||
final HetznerServerTypeInfo serverType; |
||||
|
||||
@JsonKey(name: 'datacenter', fromJson: HetznerServerInfo.locationFromJson) |
||||
final HetznerLocation location; |
||||
|
||||
static HetznerLocation locationFromJson(Map json) => |
||||
HetznerLocation.fromJson(json['location']); |
||||
|
||||
static HetznerServerInfo fromJson(Map<String, dynamic> json) => |
||||
_$HetznerServerInfoFromJson(json); |
||||
|
||||
HetznerServerInfo( |
||||
this.id, |
||||
this.name, |
||||
this.status, |
||||
this.created, |
||||
this.serverType, |
||||
this.location, |
||||
); |
||||
} |
||||
|
||||
enum ServerStatus { |
||||
running, |
||||
initializing, |
||||
starting, |
||||
stopping, |
||||
off, |
||||
deleting, |
||||
migrating, |
||||
rebuilding, |
||||
unknown, |
||||
} |
||||
|
||||
@JsonSerializable() |
||||
class HetznerServerTypeInfo { |
||||
final int cores; |
||||
final num memory; |
||||
final int disk; |
||||
|
||||
final List<HetznerPriceInfo> prices; |
||||
|
||||
HetznerServerTypeInfo(this.cores, this.memory, this.disk, this.prices); |
||||
|
||||
static HetznerServerTypeInfo fromJson(Map<String, dynamic> json) => |
||||
_$HetznerServerTypeInfoFromJson(json); |
||||
} |
||||
|
||||
@JsonSerializable() |
||||
class HetznerPriceInfo { |
||||
HetznerPriceInfo(this.hourly, this.monthly); |
||||
|
||||
@JsonKey(name: 'price_hourly', fromJson: HetznerPriceInfo.getPrice) |
||||
final double hourly; |
||||
|
||||
@JsonKey(name: 'price_monthly', fromJson: HetznerPriceInfo.getPrice) |
||||
final double monthly; |
||||
|
||||
static HetznerPriceInfo fromJson(Map<String, dynamic> json) => |
||||
_$HetznerPriceInfoFromJson(json); |
||||
|
||||
static double getPrice(Map json) => double.parse(json['gross'] as String); |
||||
} |
||||
|
||||
@JsonSerializable() |
||||
class HetznerLocation { |
||||
final String country; |
||||
final String city; |
||||
final String description; |
||||
|
||||
@JsonKey(name: 'network_zone') |
||||
final String zone; |
||||
|
||||
HetznerLocation(this.country, this.city, this.description, this.zone); |
||||
|
||||
static HetznerLocation fromJson(Map<String, dynamic> json) => |
||||
_$HetznerLocationFromJson(json); |
||||
} |
@ -0,0 +1,84 @@ |
||||
// GENERATED CODE - DO NOT MODIFY BY HAND |
||||
|
||||
part of 'hetzner_server_info.dart'; |
||||
|
||||
// ************************************************************************** |
||||
// JsonSerializableGenerator |
||||
// ************************************************************************** |
||||
|
||||
HetznerServerInfo _$HetznerServerInfoFromJson(Map<String, dynamic> json) { |
||||
return HetznerServerInfo( |
||||
json['id'] as int, |
||||
json['name'] as String, |
||||
_$enumDecode(_$ServerStatusEnumMap, json['status']), |
||||
DateTime.parse(json['created'] as String), |
||||
HetznerServerTypeInfo.fromJson(json['server_type'] as Map<String, dynamic>), |
||||
HetznerServerInfo.locationFromJson(json['datacenter'] as Map), |
||||
); |
||||
} |
||||
|
||||
K _$enumDecode<K, V>( |
||||
Map<K, V> enumValues, |
||||
Object? source, { |
||||
K? unknownValue, |
||||
}) { |
||||
if (source == null) { |
||||
throw ArgumentError( |
||||
'A value must be provided. Supported values: ' |
||||
'${enumValues.values.join(', ')}', |
||||
); |
||||
} |
||||
|
||||
return enumValues.entries.singleWhere( |
||||
(e) => e.value == source, |
||||
orElse: () { |
||||
if (unknownValue == null) { |
||||
throw ArgumentError( |
||||
'`$source` is not one of the supported values: ' |
||||
'${enumValues.values.join(', ')}', |
||||
); |
||||
} |
||||
return MapEntry(unknownValue, enumValues.values.first); |
||||
}, |
||||
).key; |
||||
} |
||||
|
||||
const _$ServerStatusEnumMap = { |
||||
ServerStatus.running: 'running', |
||||
ServerStatus.initializing: 'initializing', |
||||
ServerStatus.starting: 'starting', |
||||
ServerStatus.stopping: 'stopping', |
||||
ServerStatus.off: 'off', |
||||
ServerStatus.deleting: 'deleting', |
||||
ServerStatus.migrating: 'migrating', |
||||
ServerStatus.rebuilding: 'rebuilding', |
||||
ServerStatus.unknown: 'unknown', |
||||
}; |
||||
|
||||
HetznerServerTypeInfo _$HetznerServerTypeInfoFromJson( |
||||
Map<String, dynamic> json) { |
||||
return HetznerServerTypeInfo( |
||||
json['cores'] as int, |
||||
json['memory'] as num, |
||||
json['disk'] as int, |
||||
(json['prices'] as List<dynamic>) |
||||
.map((e) => HetznerPriceInfo.fromJson(e as Map<String, dynamic>)) |
||||
.toList(), |
||||
); |
||||
} |
||||
|
||||
HetznerPriceInfo _$HetznerPriceInfoFromJson(Map<String, dynamic> json) { |
||||
return HetznerPriceInfo( |
||||
HetznerPriceInfo.getPrice(json['price_hourly'] as Map), |
||||
HetznerPriceInfo.getPrice(json['price_monthly'] as Map), |
||||
); |
||||
} |
||||
|
||||
HetznerLocation _$HetznerLocationFromJson(Map<String, dynamic> json) { |
||||
return HetznerLocation( |
||||
json['country'] as String, |
||||
json['city'] as String, |
||||
json['description'] as String, |
||||
json['network_zone'] as String, |
||||
); |
||||
} |
@ -1,23 +0,0 @@ |
||||
import 'package:json_annotation/json_annotation.dart'; |
||||
|
||||
@JsonSerializable(createFactory: false) |
||||
class ServerInfo { |
||||
final String id; |
||||
final String name; |
||||
final ServerStatus status; |
||||
final DateTime created; |
||||
|
||||
ServerInfo(this.id, this.name, this.status, this.created); |
||||
} |
||||
|
||||
enum ServerStatus { |
||||
running, |
||||
initializing, |
||||
starting, |
||||
stopping, |
||||
off, |
||||
deleting, |
||||
migrating, |
||||
rebuilding, |
||||
unknown, |
||||
} |
@ -0,0 +1,51 @@ |
||||
import 'package:flutter/material.dart'; |
||||
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart'; |
||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; |
||||
import 'package:easy_localization/easy_localization.dart'; |
||||
import 'package:selfprivacy/ui/components/pre_styled_buttons.dart'; |
||||
|
||||
class OnePage extends StatelessWidget { |
||||
const OnePage({ |
||||
Key? key, |
||||
required this.title, |
||||
required this.child, |
||||
}) : super(key: key); |
||||
|
||||
final String title; |
||||
final Widget child; |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return SafeArea( |
||||
child: Scaffold( |
||||
appBar: PreferredSize( |
||||
child: Column( |
||||
children: [ |
||||
Container( |
||||
height: 51, |
||||
alignment: Alignment.center, |
||||
padding: EdgeInsets.symmetric(horizontal: 15), |
||||
child: BrandText.h4('basis.details'.tr()), |
||||
), |
||||
BrandDivider(), |
||||
], |
||||
), |
||||
preferredSize: Size.fromHeight(52), |
||||
), |
||||
body: child, |
||||
bottomNavigationBar: SafeArea( |
||||
child: Container( |
||||
decoration: BoxDecoration(boxShadow: kElevationToShadow[3]), |
||||
height: kBottomNavigationBarHeight, |
||||
child: Container( |
||||
color: Theme.of(context).scaffoldBackgroundColor, |
||||
alignment: Alignment.center, |
||||
child: PreStyledButtons.close( |
||||
onPress: () => Navigator.of(context).pop()), |
||||
), |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,30 @@ |
||||
import 'package:flutter/material.dart'; |
||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; |
||||
import 'package:easy_localization/easy_localization.dart'; |
||||
|
||||
class PreStyledButtons { |
||||
static Widget close({ |
||||
required VoidCallback onPress, |
||||
}) => |
||||
_CloseButton(onPress: onPress); |
||||
} |
||||
|
||||
class _CloseButton extends StatelessWidget { |
||||
const _CloseButton({Key? key, required this.onPress}) : super(key: key); |
||||
|
||||
final VoidCallback onPress; |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return OutlinedButton( |
||||
onPressed: () => Navigator.of(context).pop(), |
||||
child: Row( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: [ |
||||
BrandText.h4('basis.close'.tr()), |
||||
Icon(Icons.close), |
||||
], |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,290 @@ |
||||
import 'package:cubit_form/cubit_form.dart'; |
||||
import 'package:flutter/material.dart'; |
||||
import 'package:selfprivacy/config/brand_colors.dart'; |
||||
import 'package:selfprivacy/config/brand_theme.dart'; |
||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; |
||||
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; |
||||
import 'package:selfprivacy/logic/models/state_types.dart'; |
||||
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart'; |
||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; |
||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; |
||||
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart'; |
||||
import 'package:easy_localization/easy_localization.dart'; |
||||
import 'package:selfprivacy/ui/components/switch_block/switch_bloc.dart'; |
||||
import 'package:selfprivacy/utils/named_font_weight.dart'; |
||||
|
||||
part 'server_settings.dart'; |
||||
|
||||
var navigatorKey = GlobalKey<NavigatorState>(); |
||||
|
||||
class ServerDetails extends StatefulWidget { |
||||
const ServerDetails({Key? key}) : super(key: key); |
||||
|
||||
@override |
||||
_ServerDetailsState createState() => _ServerDetailsState(); |
||||
} |
||||
|
||||
class _ServerDetailsState extends State<ServerDetails> |
||||
with SingleTickerProviderStateMixin { |
||||
late TabController tabController; |
||||
|
||||
@override |
||||
void dispose() { |
||||
tabController.dispose(); |
||||
super.dispose(); |
||||
} |
||||
|
||||
@override |
||||
void initState() { |
||||
tabController = TabController(length: 2, vsync: this); |
||||
tabController.addListener(() { |
||||
setState(() {}); |
||||
}); |
||||
super.initState(); |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized; |
||||
var providerState = isReady ? StateType.stable : StateType.uninitialized; |
||||
|
||||
late String title = 'providers.server.card_title'.tr(); |
||||
|
||||
return TabBarView( |
||||
physics: NeverScrollableScrollPhysics(), |
||||
controller: tabController, |
||||
children: [ |
||||
BlocProvider( |
||||
create: (context) => ServerDetailsCubit()..check(), |
||||
child: Builder(builder: (context) { |
||||
var details = context.watch<ServerDetailsCubit>().state; |
||||
if (details is ServerDetailsLoading || |
||||
details is ServerDetailsInitial) { |
||||
return _TempMessage(message: 'basis.loading'.tr()); |
||||
} else if (details is ServerDetailsNotReady) { |
||||
return _TempMessage(message: 'basis.no_data'.tr()); |
||||
} else if (details is Loaded) { |
||||
var data = details.serverInfo; |
||||
var checkTime = details.checkTime; |
||||
|
||||
return Column( |
||||
crossAxisAlignment: CrossAxisAlignment.start, |
||||
children: [ |
||||
Padding( |
||||
padding: brandPagePadding2, |
||||
child: Column( |
||||
crossAxisAlignment: CrossAxisAlignment.start, |
||||
children: [ |
||||
Row( |
||||
children: [ |
||||
IconStatusMask( |
||||
status: providerState, |
||||
child: Icon( |
||||
BrandIcons.server, |
||||
size: 40, |
||||
color: Colors.white, |
||||
), |
||||
), |
||||
SizedBox(width: 10), |
||||
BrandText.h2(title), |
||||
Spacer(), |
||||
Padding( |
||||
padding: EdgeInsets.symmetric( |
||||
vertical: 4, |
||||
horizontal: 2, |
||||
), |
||||
child: PopupMenuButton<_PopupMenuItemType>( |
||||
shape: RoundedRectangleBorder( |
||||
borderRadius: BorderRadius.circular(10.0), |
||||
), |
||||
onSelected: (_PopupMenuItemType result) { |
||||
switch (result) { |
||||
case _PopupMenuItemType.setting: |
||||
tabController.animateTo(1); |
||||
break; |
||||
} |
||||
}, |
||||
icon: Icon(Icons.more_vert), |
||||
itemBuilder: (BuildContext context) => [ |
||||
PopupMenuItem<_PopupMenuItemType>( |
||||
value: _PopupMenuItemType.setting, |
||||
child: Container( |
||||
padding: EdgeInsets.only(left: 5), |
||||
child: Text('basis.settings'.tr()), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
], |
||||
), |
||||
SizedBox(height: 10), |
||||
BrandText.body1('providers.server.bottom_sheet.1'.tr()), |
||||
SizedBox(height: 30), |
||||
Center(child: BrandText.h2('General information')), |
||||
SizedBox(height: 10), |
||||
Table( |
||||
columnWidths: { |
||||
0: FractionColumnWidth(.5), |
||||
1: FractionColumnWidth(.5), |
||||
}, |
||||
defaultVerticalAlignment: |
||||
TableCellVerticalAlignment.middle, |
||||
children: [ |
||||
TableRow( |
||||
children: [ |
||||
getRowTitle('Last check'), |
||||
getRowValue(formater.format(checkTime)), |
||||
], |
||||
), |
||||
TableRow( |
||||
children: [ |
||||
getRowTitle('Server Id'), |
||||
getRowValue(data.id.toString()), |
||||
], |
||||
), |
||||
TableRow( |
||||
children: [ |
||||
getRowTitle('Status:'), |
||||
getRowValue( |
||||
'${data.status.toString().split('.')[1].toUpperCase()}', |
||||
isBold: true, |
||||
), |
||||
], |
||||
), |
||||
TableRow( |
||||
children: [ |
||||
getRowTitle('CPU'), |
||||
getRowValue( |
||||
data.serverType.cores.toString(), |
||||
), |
||||
], |
||||
), |
||||
TableRow( |
||||
children: [ |
||||
getRowTitle('Memory'), |
||||
getRowValue( |
||||
'${data.serverType.memory.toString()} GB', |
||||
), |
||||
], |
||||
), |
||||
TableRow( |
||||
children: [ |
||||
getRowTitle('Disk Local'), |
||||
getRowValue( |
||||
'${data.serverType.disk.toString()} GB', |
||||
), |
||||
], |
||||
), |
||||
TableRow( |
||||
children: [ |
||||
getRowTitle('Price monthly:'), |
||||
getRowValue( |
||||
'${data.serverType.prices[1].monthly.toString()}', |
||||
), |
||||
], |
||||
), |
||||
TableRow( |
||||
children: [ |
||||
getRowTitle('Price hourly:'), |
||||
getRowValue( |
||||
'${data.serverType.prices[1].hourly.toString()}', |
||||
), |
||||
], |
||||
), |
||||
], |
||||
), |
||||
SizedBox(height: 30), |
||||
Center(child: BrandText.h2('Location')), |
||||
SizedBox(height: 10), |
||||
Table( |
||||
columnWidths: { |
||||
0: FractionColumnWidth(.5), |
||||
1: FractionColumnWidth(.5), |
||||
}, |
||||
defaultVerticalAlignment: |
||||
TableCellVerticalAlignment.middle, |
||||
children: [ |
||||
TableRow( |
||||
children: [ |
||||
getRowTitle('Country'), |
||||
getRowValue( |
||||
'${data.location.country}', |