Compare commits

..

5 Commits

22 changed files with 236 additions and 966 deletions

View File

@ -61,11 +61,6 @@
"1": "It's a virtual computer, where all your services live.",
"2": "General information",
"3": "Location"
},
"chart": {
"month": "Month",
"day": "Day",
"hour": "Hour"
}
},
"domain": {

View File

@ -61,11 +61,6 @@
"1": "Это виртульный компьютер на котором работают все ваши сервисы.",
"2": "Общая информация",
"3": "Размещение"
},
"chart": {
"month": "Месяц",
"day": "День",
"hour": "Час"
}
},
"domain": {

View File

@ -1,5 +1,5 @@
SelfPrivacy - is a platform on your cloud hosting, that allows to deploy your own private services and control them using mobile application.
To use this application, you'll be required to create accounts of different service providers. Please reffer to this manual: https://hugo.selfprivacy.org/posts/getting_started
To use this application, you'll be required to create accounts of different service providers. Please reffer to this manual: https://selfprivacy.org/en/second.html
Application will do the following things for you:
1. Create your personal server
2. Setup NixOS

View File

@ -94,7 +94,7 @@ class HetznerApi extends ApiMap {
);
var dbId = dbCreateResponse.data['volume']['id'];
var data = jsonDecode(
'''{"name":"$domainName","server_type":"cx11","start_after_create":false,"image":"ubuntu-20.04", "volumes":[$dbId], "networks":[],"user_data":"#cloud-config\\nruncmd:\\n- curl https://git.selfprivacy.org/ilchub/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-20.09 DOMAIN=$domainName LUSER=${rootUser.login} PASSWORD=${rootUser.password} HASHED_PASSWORD=${rootUser.hashPassword} CF_TOKEN=$cloudFlareKey DB_PASSWORD=$dbPassword bash 2>&1 | tee /tmp/infect.log","labels":{},"automount":true, "location": "fsn1"}''',
'''{"name":"$domainName","server_type":"cx11","start_after_create":false,"image":"ubuntu-20.04", "volumes":[$dbId], "networks":[],"user_data":"#cloud-config\\nruncmd:\\n- curl https://git.selfprivacy.org/ilchub/selfprivacy-nixos-infect/raw/branch/preproduction/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-20.09 DOMAIN=$domainName LUSER=${rootUser.login} PASSWORD=${rootUser.password} HASHED_PASSWORD=${rootUser.hashPassword} CF_TOKEN=$cloudFlareKey DB_PASSWORD=$dbPassword bash 2>&1 | tee /tmp/infect.log","labels":{},"automount":true, "location": "fsn1"}''',
);
Response serverCreateResponse = await client.post(
@ -164,21 +164,11 @@ class HetznerApi extends ApiMap {
return server.copyWith(startTime: DateTime.now());
}
Future<Map<String, dynamic>> getMetrics(DateTime start, DateTime end, String type) async {
metrics() async {
var hetznerServer = getIt<ApiConfigModel>().hetznerServer;
var client = await getClient();
Map<String, dynamic> queryParameters = {
"start": start.toUtc().toIso8601String(),
"end": end.toUtc().toIso8601String(),
"type": type
};
var res = await client.get(
'/servers/${hetznerServer!.id}/metrics',
queryParameters: queryParameters,
);
await client.post('/servers/${hetznerServer!.id}/metrics');
close(client);
return res.data;
}
Future<HetznerServerInfo> getInfo() async {

View File

@ -8,5 +8,3 @@ enum InitializingSteps {
startServer,
checkSystemDnsAndDkimSet,
}
enum Period { hour, day, month }

View File

@ -1,49 +0,0 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
import 'hetzner_metrics_repository.dart';
part 'hetzner_metrics_state.dart';
class HetznerMetricsCubit extends Cubit<HetznerMetricsState> {
HetznerMetricsCubit() : super(HetznerMetricsLoading(Period.day));
final repository = HetznerMetricsRepository();
Timer? timer;
close() {
closeTimer();
return super.close();
}
void closeTimer() {
if (timer != null && timer!.isActive) {
timer!.cancel();
}
}
void changePeriod(Period period) async {
closeTimer();
emit(HetznerMetricsLoading(period));
load(period);
}
void restart() async {
load(state.period);
}
void load(Period period) async {
var newState = await repository.getMetrics(period);
timer = Timer(
Duration(seconds: newState.stepInSeconds.toInt()),
() => load(newState.period),
);
emit(newState);
}
}

View File

@ -1,57 +0,0 @@
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
import 'hetzner_metrics_cubit.dart';
class HetznerMetricsRepository {
Future<HetznerMetricsLoaded> getMetrics(Period period) async {
print(period);
var end = DateTime.now();
DateTime start;
switch (period) {
case Period.hour:
start = end.subtract(Duration(hours: 1));
break;
case Period.day:
start = end.subtract(Duration(days: 1));
break;
case Period.month:
start = end.subtract(Duration(days: 15));
break;
}
var api = HetznerApi(hasLoger: true);
var results = await Future.wait([
api.getMetrics(start, end, 'cpu'),
api.getMetrics(start, end, 'network'),
]);
var cpuMetricsData = results[0]["metrics"];
var networkMetricsData = results[1]["metrics"];
return HetznerMetricsLoaded(
period: period,
start: start,
end: end,
stepInSeconds: cpuMetricsData["step"],
cpu: timeSeriesSerializer(cpuMetricsData, 'cpu'),
ppsIn: timeSeriesSerializer(networkMetricsData, 'network.0.pps.in'),
ppsOut: timeSeriesSerializer(networkMetricsData, 'network.0.pps.out'),
bandwidthIn:
timeSeriesSerializer(networkMetricsData, 'network.0.bandwidth.in'),
bandwidthOut: timeSeriesSerializer(
networkMetricsData,
'network.0.bandwidth.out',
),
);
}
}
List<TimeSeriesData> timeSeriesSerializer(
Map<String, dynamic> json, String type) {
List list = json["time_series"][type]["values"];
return list.map((el) => TimeSeriesData(el[0], double.parse(el[1]))).toList();
}

View File

@ -1,43 +0,0 @@
part of 'hetzner_metrics_cubit.dart';
abstract class HetznerMetricsState extends Equatable {
const HetznerMetricsState();
abstract final Period period;
}
class HetznerMetricsLoading extends HetznerMetricsState {
HetznerMetricsLoading(this.period);
final Period period;
@override
List<Object?> get props => [period];
}
class HetznerMetricsLoaded extends HetznerMetricsState {
HetznerMetricsLoaded({
required this.period,
required this.start,
required this.end,
required this.stepInSeconds,
required this.cpu,
required this.ppsIn,
required this.ppsOut,
required this.bandwidthIn,
required this.bandwidthOut,
});
final Period period;
final DateTime start;
final DateTime end;
final num stepInSeconds;
final List<TimeSeriesData> cpu;
final List<TimeSeriesData> ppsIn;
final List<TimeSeriesData> ppsOut;
final List<TimeSeriesData> bandwidthIn;
final List<TimeSeriesData> bandwidthOut;
@override
List<Object?> get props => [period, start, end];
}

View File

@ -1,11 +0,0 @@
class TimeSeriesData {
TimeSeriesData(
this.secondsSinceEpoch,
this.value,
);
final int secondsSinceEpoch;
DateTime get time =>
DateTime.fromMillisecondsSinceEpoch(secondsSinceEpoch * 1000);
final double value;
}

View File

@ -1,42 +0,0 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart';
class BrandRadio extends StatelessWidget {
BrandRadio({
Key? key,
required this.isChecked,
}) : super(key: key);
final bool isChecked;
@override
Widget build(BuildContext context) {
return Container(
height: 20,
width: 20,
alignment: Alignment.center,
padding: EdgeInsets.all(2),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: _getBorder(),
),
child: isChecked
? Container(
height: 10,
width: 10,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: BrandColors.primary,
),
)
: null,
);
}
BoxBorder? _getBorder() {
return Border.all(
color: isChecked ? BrandColors.primary : BrandColors.gray1,
width: 2,
);
}
}

View File

@ -1,37 +0,0 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/ui/components/brand_radio/brand_radio.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
class BrandRadioTile extends StatelessWidget {
const BrandRadioTile({
Key? key,
required this.isChecked,
required this.text,
required this.onPress,
}) : super(key: key);
final bool isChecked;
final String text;
final VoidCallback onPress;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPress,
behavior: HitTestBehavior.translucent,
child: Padding(
padding: EdgeInsets.all(2),
child: Row(
children: [
BrandRadio(
isChecked: isChecked,
),
SizedBox(width: 9),
BrandText.h5(text)
],
),
),
);
}
}

View File

@ -70,9 +70,9 @@ class _BrandTimerState extends State<BrandTimer> {
_durationToString(DateTime.now().difference(widget.startDateTime));
String _durationToString(Duration duration) {
var timeLeft = widget.duration - duration;
String twoDigits(int n) => n.toString().padLeft(2, "0");
String twoDigitSeconds = twoDigits(timeLeft.inSeconds);
String twoDigitSeconds =
twoDigits(widget.duration.inSeconds - duration.inSeconds.remainder(60));
return "timer.sec".tr(args: [twoDigitSeconds]);
}

View File

@ -82,6 +82,8 @@ class _Card extends StatelessWidget {
switch (provider.type) {
case ProviderType.server:
title = 'providers.server.card_title'.tr();
stableText = 'providers.domain.status'.tr();
stableText = 'providers.server.status'.tr();
onTap = () => Navigator.of(context).push(
SlideBottomRoute(

View File

@ -1,166 +0,0 @@
part of 'server_details.dart';
class _Chart extends StatelessWidget {
const _Chart({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
var cubit = context.watch<HetznerMetricsCubit>();
var period = cubit.state.period;
var state = cubit.state;
List<Widget> charts;
if (state is HetznerMetricsLoading) {
charts = [
Container(
height: 200,
alignment: Alignment.center,
child: Text('basis.loading'.tr()),
)
];
} else if (state is HetznerMetricsLoaded) {
charts = [
Legend(color: Colors.red, text: 'CPU %'),
getCpuChart(state),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
BrandText.small('Public Network interface packets per sec'),
SizedBox(width: 10),
Legend(color: Colors.red, text: 'IN'),
SizedBox(width: 5),
Legend(color: Colors.green, text: 'OUT'),
],
),
getPpsChart(state),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
BrandText.small('Public Network interface bytes per sec'),
SizedBox(width: 10),
Legend(color: Colors.red, text: 'IN'),
SizedBox(width: 5),
Legend(color: Colors.green, text: 'OUT'),
],
),
getBandwidthChart(state),
];
} else {
throw 'wrong state';
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: Column(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BrandRadioTile(
isChecked: period == Period.month,
text: 'providers.server.chart.month'.tr(),
onPress: () => cubit.changePeriod(Period.month),
),
BrandRadioTile(
isChecked: period == Period.day,
text: 'providers.server.chart.day'.tr(),
onPress: () => cubit.changePeriod(Period.day),
),
BrandRadioTile(
isChecked: period == Period.hour,
text: 'providers.server.chart.hour'.tr(),
onPress: () => cubit.changePeriod(Period.hour),
),
],
),
),
...charts,
],
),
);
}
Widget getCpuChart(HetznerMetricsLoaded state) {
var data = state.cpu;
return Container(
height: 200,
child: CpuChart(data, state.period, state.start),
);
}
Widget getPpsChart(HetznerMetricsLoaded state) {
var ppsIn = state.ppsIn;
var ppsOut = state.ppsOut;
return Container(
height: 200,
child: NetworkChart(
[ppsIn, ppsOut],
state.period,
state.start,
),
);
}
Widget getBandwidthChart(HetznerMetricsLoaded state) {
var ppsIn = state.bandwidthIn;
var ppsOut = state.bandwidthOut;
return Container(
height: 200,
child: NetworkChart(
[ppsIn, ppsOut],
state.period,
state.start,
),
);
}
}
class Legend extends StatelessWidget {
const Legend({
Key? key,
required this.color,
required this.text,
}) : super(key: key);
final String text;
final Color color;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
_ColoredBox(color: color),
SizedBox(width: 5),
BrandText.small(text),
],
);
}
}
class _ColoredBox extends StatelessWidget {
const _ColoredBox({
Key? key,
required this.color,
}) : super(key: key);
final Color color;
@override
Widget build(BuildContext context) {
return Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: color.withOpacity(0.3),
border: Border.all(
color: color,
)),
);
}
}

View File

@ -1,116 +0,0 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/text_themes.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
import 'package:intl/intl.dart';
class CpuChart extends StatelessWidget {
CpuChart(this.data, this.period, this.start);
final List<TimeSeriesData> data;
final Period period;
final DateTime start;
List<FlSpot> getSpots() {
var i = 0;
List<FlSpot> res = [];
for (var d in data) {
res.add(FlSpot(i.toDouble(), d.value));
i++;
}
return res;
}
@override
Widget build(BuildContext context) {
return LineChart(
LineChartData(
lineTouchData: LineTouchData(enabled: false),
lineBarsData: [
LineChartBarData(
spots: getSpots(),
isCurved: true,
barWidth: 1,
colors: [
Colors.red,
],
dotData: FlDotData(
show: false,
),
),
],
minY: 0,
maxY: 100,
minX: data.length - 200,
titlesData: FlTitlesData(
bottomTitles: SideTitles(
interval: 20,
rotateAngle: 90.0,
showTitles: true,
getTextStyles: (value) => const TextStyle(
fontSize: 10,
color: Colors.purple,
fontWeight: FontWeight.bold,
),
getTitles: (value) {
return bottomTitle(value.toInt());
}),
leftTitles: SideTitles(
getTextStyles: (value) => progressTextStyleLight.copyWith(
color: Theme.of(context).brightness == Brightness.dark
? BrandColors.gray4
: null,
),
margin: 15,
interval: 25,
showTitles: true,
),
),
gridData: FlGridData(show: true),
),
);
}
bool checkToShowTitle(
double minValue,
double maxValue,
SideTitles sideTitles,
double appliedInterval,
double value,
) {
print(value);
if (value < 0) {
return false;
} else if (value == 0) {
return true;
}
var _value = value - minValue;
var v = _value / 20;
return v - v.floor() == 0;
}
String bottomTitle(int value) {
final hhmm = DateFormat('HH:mm');
var day = DateFormat('MMMd');
String res;
if (value <= 0) {
return '';
}
var time = data[value].time;
switch (period) {
case Period.hour:
case Period.day:
res = hhmm.format(time);
break;
case Period.month:
res = day.format(time);
}
return res;
}
}

View File

@ -1,61 +0,0 @@
part of 'server_details.dart';
class _Header extends StatelessWidget {
const _Header({
Key? key,
required this.providerState,
required this.tabController,
}) : super(key: key);
final StateType providerState;
final TabController tabController;
@override
Widget build(BuildContext context) {
return Row(
children: [
IconStatusMask(
status: providerState,
child: Icon(
BrandIcons.server,
size: 40,
color: Colors.white,
),
),
SizedBox(width: 10),
BrandText.h2('providers.server.card_title'.tr()),
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()),
),
),
],
),
),
],
);
}
}
enum _PopupMenuItemType { setting }

View File

@ -1,141 +0,0 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/text_themes.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
import 'package:intl/intl.dart';
class NetworkChart extends StatelessWidget {
NetworkChart(
this.listData,
this.period,
this.start,
);
final List<List<TimeSeriesData>> listData;
final Period period;
final DateTime start;
List<FlSpot> getSpots(data) {
var i = 0;
List<FlSpot> res = [];
for (var d in data) {
res.add(FlSpot(i.toDouble(), d.value));
i++;
}
return res;
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 150,
width: MediaQuery.of(context).size.width * 0.90,
child: LineChart(
LineChartData(
lineTouchData: LineTouchData(enabled: false),
lineBarsData: [
LineChartBarData(
spots: getSpots(listData[0]),
isCurved: true,
barWidth: 1,
colors: [Colors.red],
dotData: FlDotData(
show: false,
),
),
LineChartBarData(
spots: getSpots(listData[1]),
isCurved: true,
barWidth: 1,
colors: [Colors.green],
dotData: FlDotData(
show: false,
),
),
],
minY: 0,
maxY: [
...listData[0].map((e) => e.value),
...listData[1].map((e) => e.value)
].reduce(max) *
1.2,
minX: listData[0].length - 200,
titlesData: FlTitlesData(
bottomTitles: SideTitles(
interval: 20,
rotateAngle: 90.0,
showTitles: true,
getTextStyles: (value) => const TextStyle(
fontSize: 10,
color: Colors.purple,
fontWeight: FontWeight.bold,
),
getTitles: (value) {
return bottomTitle(value.toInt());
}),
leftTitles: SideTitles(
margin: 15,
interval: [
...listData[0].map((e) => e.value),
...listData[1].map((e) => e.value)
].reduce(max) *
1.2 /
10,
getTextStyles: (value) => progressTextStyleLight.copyWith(
color: Theme.of(context).brightness == Brightness.dark
? BrandColors.gray4
: null,
),
showTitles: true,
),
),
gridData: FlGridData(show: true),
),
),
);
}
bool checkToShowTitle(
double minValue,
double maxValue,
SideTitles sideTitles,
double appliedInterval,
double value,
) {
if (value < 0) {
return false;
} else if (value == 0) {
return true;
}
var _value = value - minValue;
var v = _value / 20;
return v - v.floor() == 0;
}
String bottomTitle(int value) {
final hhmm = DateFormat('HH:mm');
var day = DateFormat('MMMd');
String res;
if (value <= 0) {
return '';
}
var time = listData[0][value].time;
switch (period) {
case Period.hour:
case Period.day:
res = hhmm.format(time);
break;
case Period.month:
res = day.format(time);
}
return res;
}
}

View File

@ -2,26 +2,18 @@ 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/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/hetzner_metrics/hetzner_metrics_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_radio_tile/brand_radio_tile.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';
import 'cpu_chart.dart';
import 'network_charts.dart';
part 'server_settings.dart';
part 'text_details.dart';
part 'chart.dart';
part 'header.dart';
var navigatorKey = GlobalKey<NavigatorState>();
@ -56,41 +48,240 @@ class _ServerDetailsState extends State<ServerDetails>
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: [
SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: brandPagePadding2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_Header(
providerState: providerState,
tabController: tabController),
BrandText.body1('providers.server.bottom_sheet.1'.tr()),
SizedBox(height: 10),
BlocProvider(
create: (context) => HetznerMetricsCubit()..restart(),
child: _Chart(),
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('providers.server.2'.tr())),
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('providers.server.3'.tr())),
SizedBox(height: 10),
Table(
columnWidths: {
0: FractionColumnWidth(.5),
1: FractionColumnWidth(.5),
},
defaultVerticalAlignment:
TableCellVerticalAlignment.middle,
children: [
TableRow(
children: [
getRowTitle('Country'),
getRowValue(
'${data.location.country}',
),
],
),
TableRow(
children: [
getRowTitle('City'),
getRowValue(data.location.city),
],
),
TableRow(
children: [
getRowTitle('Description'),
getRowValue(data.location.description),
],
),
],
),
],
),
SizedBox(height: 20),
BlocProvider(
create: (context) => ServerDetailsCubit()..check(),
child: _TextDetails(),
),
],
),
),
],
),
),
],
);
} else {
throw Exception('wrong state');
}
}),
),
_ServerSettings(tabController: tabController),
],
);
}
Widget getRowTitle(String title) {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: BrandText.h5(
title,
textAlign: TextAlign.right,
),
);
}
Widget getRowValue(String title, {bool isBold = false}) {
return BrandText.body1(
title,
style: isBold
? TextStyle(
fontWeight: NamedFontWeight.demiBold,
)
: null,
);
}
}
enum _PopupMenuItemType { setting }
class _TempMessage extends StatelessWidget {
const _TempMessage({
Key? key,
required this.message,
}) : super(key: key);
final String message;
@override
Widget build(BuildContext context) {
return SizedBox(
height: MediaQuery.of(context).size.height - 100,
child: Center(
child: BrandText.body2(message),
),
);
}
}
final DateFormat formater = DateFormat('HH:mm:ss');

View File

@ -1,171 +0,0 @@
part of 'server_details.dart';
class _TextDetails extends StatelessWidget {
const _TextDetails({Key? key}) : super(key: key);
@override
Widget build(BuildContext 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(
children: [
Center(child: BrandText.h3('providers.server.bottom_sheet.2'.tr())),
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.h3('providers.server.bottom_sheet.3'.tr())),
SizedBox(height: 10),
Table(
columnWidths: {
0: FractionColumnWidth(.5),
1: FractionColumnWidth(.5),
},
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: [
TableRow(
children: [
getRowTitle('Country'),
getRowValue(
'${data.location.country}',
),
],
),
TableRow(
children: [
getRowTitle('City'),
getRowValue(data.location.city),
],
),
TableRow(
children: [
getRowTitle('Description'),
getRowValue(data.location.description),
],
),
],
),
SizedBox(height: 20),
],
);
} else {
throw Exception('wrong state');
}
}
Widget getRowTitle(String title) {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: BrandText.h5(
title,
textAlign: TextAlign.right,
),
);
}
Widget getRowValue(String title, {bool isBold = false}) {
return BrandText.body1(
title,
style: isBold
? TextStyle(
fontWeight: NamedFontWeight.demiBold,
)
: null,
);
}
}
class _TempMessage extends StatelessWidget {
const _TempMessage({
Key? key,
required this.message,
}) : super(key: key);
final String message;
@override
Widget build(BuildContext context) {
return SizedBox(
height: MediaQuery.of(context).size.height - 100,
child: Center(
child: BrandText.body2(message),
),
);
}
}
final DateFormat formater = DateFormat('HH:mm:ss');

View File

@ -397,6 +397,7 @@ class _ServiceDetails extends StatelessWidget {
try {
await launch(
url,
forceSafariVC: true,
enableJavaScript: true,
);
} catch (e) {

View File

@ -274,13 +274,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
fl_chart:
dependency: "direct main"
description:
name: fl_chart
url: "https://pub.dartlang.org"
source: hosted
version: "0.35.0"
flutter:
dependency: "direct main"
description: flutter

View File

@ -1,7 +1,7 @@
name: selfprivacy
description: selfprivacy.org
publish_to: 'none'
version: 0.1.1+1
version: 0.1.1+3
environment:
sdk: '>=2.12.0 <3.0.0'
@ -17,7 +17,6 @@ dependencies:
easy_localization: ^3.0.0
either_option: ^2.0.1-dev.1
equatable: ^2.0.0
fl_chart: ^0.35.0
flutter_bloc: ^7.0.0
flutter_markdown: ^0.6.0
flutter_secure_storage: ^4.1.0