forked from SelfPrivacy/selfprivacy.org.app
Added charts. Added possibility to remove server. Fixed translations. Fixed black theme
Reviewed-on: kherel/selfprivacy.org.app#22fdroid
commit
f30b54f564
|
@ -41,7 +41,9 @@
|
||||||
"1": "Dark Theme",
|
"1": "Dark Theme",
|
||||||
"2": "Change your the app theme",
|
"2": "Change your the app theme",
|
||||||
"3": "Reset app config",
|
"3": "Reset app config",
|
||||||
"4": "Reset api keys and root user"
|
"4": "Reset api keys and root user",
|
||||||
|
"5": "Delete Server",
|
||||||
|
"6": "This removes the Server. It will be no longer accessible"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"onboarding": {
|
"onboarding": {
|
||||||
|
@ -61,6 +63,11 @@
|
||||||
"1": "It's a virtual computer, where all your services live.",
|
"1": "It's a virtual computer, where all your services live.",
|
||||||
"2": "General information",
|
"2": "General information",
|
||||||
"3": "Location"
|
"3": "Location"
|
||||||
|
},
|
||||||
|
"chart": {
|
||||||
|
"month": "Month",
|
||||||
|
"day": "Day",
|
||||||
|
"hour": "Hour"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain": {
|
"domain": {
|
||||||
|
@ -157,7 +164,7 @@
|
||||||
},
|
},
|
||||||
"initializing": {
|
"initializing": {
|
||||||
"_comment": "initializing page",
|
"_comment": "initializing page",
|
||||||
"1": "Connect Hetzner server",
|
"1": "Connect a server",
|
||||||
"2": "Here, your data and SelfPrivacy services wiil reside",
|
"2": "Here, your data and SelfPrivacy services wiil reside",
|
||||||
"how": "How to obtain API token",
|
"how": "How to obtain API token",
|
||||||
"3": "Connect CloudFlare",
|
"3": "Connect CloudFlare",
|
||||||
|
@ -179,7 +186,10 @@
|
||||||
"18": "How to obtain Hetzner API Token",
|
"18": "How to obtain Hetzner API Token",
|
||||||
"19": "1 Go via this link ",
|
"19": "1 Go via this link ",
|
||||||
"20": "\n",
|
"20": "\n",
|
||||||
"21": "One more restart to apply your security certificates."
|
"21": "One more restart to apply your security certificates.",
|
||||||
|
"22": "Create master account",
|
||||||
|
"23": "Enter a nickname and strong password"
|
||||||
|
|
||||||
},
|
},
|
||||||
"modals": {
|
"modals": {
|
||||||
"_comment": "messages in modals",
|
"_comment": "messages in modals",
|
||||||
|
@ -187,7 +197,9 @@
|
||||||
"2": "Destroy server and create a new one?",
|
"2": "Destroy server and create a new one?",
|
||||||
"3": "Are you sure?",
|
"3": "Are you sure?",
|
||||||
"4": "Purge all authentication keys?",
|
"4": "Purge all authentication keys?",
|
||||||
"5": "Yes, purge all my tokens"
|
"5": "Yes, purge all my tokens",
|
||||||
|
"6": "Delete the server and volume?",
|
||||||
|
"7": "Yes"
|
||||||
},
|
},
|
||||||
"timer": {
|
"timer": {
|
||||||
"sec": "{} sec"
|
"sec": "{} sec"
|
||||||
|
|
|
@ -41,7 +41,9 @@
|
||||||
"1": "Темная тема",
|
"1": "Темная тема",
|
||||||
"2": "Сменить цветовую тему",
|
"2": "Сменить цветовую тему",
|
||||||
"3": "Сброс настроек",
|
"3": "Сброс настроек",
|
||||||
"4": "Сбросить API ключи а так же root пользвателя"
|
"4": "Сбросить API ключи а так же root пользвателя",
|
||||||
|
"5": "Удалить сервер",
|
||||||
|
"6": "Действие приведет к удалению сервера. После чего он не будет доступен."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"onboarding": {
|
"onboarding": {
|
||||||
|
@ -61,6 +63,11 @@
|
||||||
"1": "Это виртульный компьютер на котором работают все ваши сервисы.",
|
"1": "Это виртульный компьютер на котором работают все ваши сервисы.",
|
||||||
"2": "Общая информация",
|
"2": "Общая информация",
|
||||||
"3": "Размещение"
|
"3": "Размещение"
|
||||||
|
},
|
||||||
|
"chart": {
|
||||||
|
"month": "Месяц",
|
||||||
|
"day": "День",
|
||||||
|
"hour": "Час"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain": {
|
"domain": {
|
||||||
|
@ -157,10 +164,10 @@
|
||||||
},
|
},
|
||||||
"initializing": {
|
"initializing": {
|
||||||
"_comment": "initializing page",
|
"_comment": "initializing page",
|
||||||
"1": "Подключите сервер Hetzner",
|
"1": "Подключите сервер",
|
||||||
"2": "Здесь будут жить наши данные и SelfPrivacy-сервисы",
|
"2": "Здесь будут жить наши данные и SelfPrivacy-сервисы",
|
||||||
"how": "Как получить API Token",
|
"how": "Как получить API Token",
|
||||||
"3": "'Подключите CloudFlare'",
|
"3": "Подключите CloudFlare",
|
||||||
"4": "Для управления DNS вашего домена",
|
"4": "Для управления DNS вашего домена",
|
||||||
"5": "CloudFlare API Token",
|
"5": "CloudFlare API Token",
|
||||||
"6": "Подключите облачное хранилище Backblaze",
|
"6": "Подключите облачное хранилище Backblaze",
|
||||||
|
@ -179,7 +186,9 @@
|
||||||
"18": "Как получить Hetzner API Token'",
|
"18": "Как получить Hetzner API Token'",
|
||||||
"19": "1 Переходим по ссылке ",
|
"19": "1 Переходим по ссылке ",
|
||||||
"20": "\n2 Заходим в созданный нами проект. Если такового - нет, значит создаём.\n3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).\n4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.\n5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку.\n6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет.",
|
"20": "\n2 Заходим в созданный нами проект. Если такового - нет, значит создаём.\n3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).\n4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.\n5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку.\n6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет.",
|
||||||
"21": "Сейчас будет дополнительная перезагрузка для активации сертификатов безопастности"
|
"21": "Сейчас будет дополнительная перезагрузка для активации сертификатов безопастности",
|
||||||
|
"22": "Создайте главную учетную запись",
|
||||||
|
"23": "Введите никнейм и сложный пароль"
|
||||||
},
|
},
|
||||||
"modals": {
|
"modals": {
|
||||||
"_comment": "messages in modals",
|
"_comment": "messages in modals",
|
||||||
|
@ -187,7 +196,9 @@
|
||||||
"2": "Уничтожить сервер и создать новый?",
|
"2": "Уничтожить сервер и создать новый?",
|
||||||
"3": "Вы уверенны",
|
"3": "Вы уверенны",
|
||||||
"4": "Сбросить все ключи?",
|
"4": "Сбросить все ключи?",
|
||||||
"5": "Да, сбросить"
|
"5": "Да, сбросить",
|
||||||
|
"6": "Удалить сервер и диск?",
|
||||||
|
"7": "Да, удалить"
|
||||||
},
|
},
|
||||||
"timer": {
|
"timer": {
|
||||||
"sec": "{} сек"
|
"sec": "{} сек"
|
||||||
|
|
|
@ -93,14 +93,16 @@ class HetznerApi extends ApiMap {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
var dbId = dbCreateResponse.data['volume']['id'];
|
var dbId = dbCreateResponse.data['volume']['id'];
|
||||||
|
|
||||||
var data = jsonDecode(
|
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/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"}''',
|
'''{"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.hash} SALT=${rootUser.hashPassword.salt} CF_TOKEN=$cloudFlareKey DB_PASSWORD=$dbPassword bash 2>&1 | tee /tmp/infect.log","labels":{},"automount":true, "location": "fsn1"}''',
|
||||||
);
|
);
|
||||||
|
|
||||||
Response serverCreateResponse = await client.post(
|
Response serverCreateResponse = await client.post(
|
||||||
'/servers',
|
'/servers',
|
||||||
data: data,
|
data: data,
|
||||||
);
|
);
|
||||||
|
|
||||||
client.close();
|
client.close();
|
||||||
return HetznerServerDetails(
|
return HetznerServerDetails(
|
||||||
id: serverCreateResponse.data['server']['id'],
|
id: serverCreateResponse.data['server']['id'],
|
||||||
|
@ -164,11 +166,22 @@ class HetznerApi extends ApiMap {
|
||||||
return server.copyWith(startTime: DateTime.now());
|
return server.copyWith(startTime: DateTime.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics() async {
|
Future<Map<String, dynamic>> getMetrics(
|
||||||
|
DateTime start, DateTime end, String type) async {
|
||||||
var hetznerServer = getIt<ApiConfigModel>().hetznerServer;
|
var hetznerServer = getIt<ApiConfigModel>().hetznerServer;
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
await client.post('/servers/${hetznerServer!.id}/metrics');
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
close(client);
|
close(client);
|
||||||
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<HetznerServerInfo> getInfo() async {
|
Future<HetznerServerInfo> getInfo() async {
|
||||||
|
|
|
@ -8,3 +8,5 @@ enum InitializingSteps {
|
||||||
startServer,
|
startServer,
|
||||||
checkSystemDnsAndDkimSet,
|
checkSystemDnsAndDkimSet,
|
||||||
}
|
}
|
||||||
|
enum Period { hour, day, month }
|
||||||
|
|
||||||
|
|
|
@ -263,6 +263,28 @@ class AppConfigCubit extends Cubit<AppConfigState> {
|
||||||
emit(InitialAppConfigState());
|
emit(InitialAppConfigState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> serverDelete() async {
|
||||||
|
closeTimer();
|
||||||
|
if (state.hetznerServer != null) {
|
||||||
|
await repository.deleteServer(state.cloudFlareDomain!);
|
||||||
|
}
|
||||||
|
await repository.deleteRecords();
|
||||||
|
emit(AppConfigState(
|
||||||
|
hetznerKey: state.hetznerKey,
|
||||||
|
cloudFlareKey: state.cloudFlareKey,
|
||||||
|
backblazeCredential: state.backblazeCredential,
|
||||||
|
cloudFlareDomain: state.cloudFlareDomain,
|
||||||
|
rootUser: state.rootUser,
|
||||||
|
hetznerServer: null,
|
||||||
|
isServerStarted: false,
|
||||||
|
isServerResetedFirstTime: false,
|
||||||
|
isServerResetedSecondTime: false,
|
||||||
|
hasFinalChecked: false,
|
||||||
|
isLoading: false,
|
||||||
|
error: null,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
void setHetznerKey(String hetznerKey) async {
|
void setHetznerKey(String hetznerKey) async {
|
||||||
await repository.saveHetznerKey(hetznerKey);
|
await repository.saveHetznerKey(hetznerKey);
|
||||||
emit(state.copyWith(hetznerKey: hetznerKey));
|
emit(state.copyWith(hetznerKey: hetznerKey));
|
||||||
|
|
|
@ -220,4 +220,26 @@ class AppConfigRepository {
|
||||||
Future<void> saveHasFinalChecked(bool value) async {
|
Future<void> saveHasFinalChecked(bool value) async {
|
||||||
await box.put(BNames.hasFinalChecked, value);
|
await box.put(BNames.hasFinalChecked, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> deleteServer(CloudFlareDomain cloudFlareDomain) async {
|
||||||
|
var hetznerApi = HetznerApi();
|
||||||
|
var cloudFlare = CloudflareApi();
|
||||||
|
|
||||||
|
hetznerApi.deleteSelfprivacyServerAndAllVolumes(
|
||||||
|
domainName: cloudFlareDomain.domainName,
|
||||||
|
);
|
||||||
|
cloudFlare.removeSimilarRecords(cloudFlareDomain: cloudFlareDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteRecords() async {
|
||||||
|
await box.deleteAll([
|
||||||
|
BNames.hetznerServer,
|
||||||
|
BNames.isServerStarted,
|
||||||
|
BNames.isServerResetedFirstTime,
|
||||||
|
BNames.isServerResetedSecondTime,
|
||||||
|
BNames.hasFinalChecked,
|
||||||
|
BNames.isLoading,
|
||||||
|
]);
|
||||||
|
getIt<ApiConfigModel>().init();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ class AppConfigState extends Equatable {
|
||||||
|
|
||||||
final bool hasFinalChecked;
|
final bool hasFinalChecked;
|
||||||
|
|
||||||
final bool? isLoading;
|
final bool isLoading;
|
||||||
final Exception? error;
|
final Exception? error;
|
||||||
|
|
||||||
AppConfigState copyWith({
|
AppConfigState copyWith({
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
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();
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
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];
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
class TimeSeriesData {
|
||||||
|
TimeSeriesData(
|
||||||
|
this.secondsSinceEpoch,
|
||||||
|
this.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
final int secondsSinceEpoch;
|
||||||
|
DateTime get time =>
|
||||||
|
DateTime.fromMillisecondsSinceEpoch(secondsSinceEpoch * 1000);
|
||||||
|
final double value;
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:crypt/crypt.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:selfprivacy/utils/color_utils.dart';
|
import 'package:selfprivacy/utils/color_utils.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:selfprivacy/utils/crypto.dart';
|
|
||||||
|
|
||||||
part 'user.g.dart';
|
part 'user.g.dart';
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ class User extends Equatable {
|
||||||
|
|
||||||
Color get color => stringToColor(login);
|
Color get color => stringToColor(login);
|
||||||
|
|
||||||
String get hashPassword => convertToSha512Hash(password);
|
Crypt get hashPassword => Crypt.sha512(password);
|
||||||
|
|
||||||
String toString() {
|
String toString() {
|
||||||
return login;
|
return login;
|
||||||
|
|
|
@ -39,7 +39,9 @@ class _BrandMarkdownState extends State<BrandMarkdown> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var isDark = Theme.of(context).brightness == Brightness.dark;
|
var isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
var markdown = MarkdownStyleSheet(
|
var markdown = MarkdownStyleSheet(
|
||||||
p: defaultTextStyle,
|
p: defaultTextStyle.copyWith(
|
||||||
|
color: isDark ? BrandColors.white : null,
|
||||||
|
),
|
||||||
h1: headline1Style.copyWith(
|
h1: headline1Style.copyWith(
|
||||||
color: isDark ? BrandColors.white : null,
|
color: isDark ? BrandColors.white : null,
|
||||||
),
|
),
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
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)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -70,9 +70,9 @@ class _BrandTimerState extends State<BrandTimer> {
|
||||||
_durationToString(DateTime.now().difference(widget.startDateTime));
|
_durationToString(DateTime.now().difference(widget.startDateTime));
|
||||||
|
|
||||||
String _durationToString(Duration duration) {
|
String _durationToString(Duration duration) {
|
||||||
|
var timeLeft = widget.duration - duration;
|
||||||
String twoDigits(int n) => n.toString().padLeft(2, "0");
|
String twoDigits(int n) => n.toString().padLeft(2, "0");
|
||||||
String twoDigitSeconds =
|
String twoDigitSeconds = twoDigits(timeLeft.inSeconds);
|
||||||
twoDigits(widget.duration.inSeconds - duration.inSeconds.remainder(60));
|
|
||||||
|
|
||||||
return "timer.sec".tr(args: [twoDigitSeconds]);
|
return "timer.sec".tr(args: [twoDigitSeconds]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,7 @@ class NotReadyCard extends StatelessWidget {
|
||||||
child: Text(
|
child: Text(
|
||||||
'not_ready_card.2'.tr(),
|
'not_ready_card.2'.tr(),
|
||||||
style: body1Style.copyWith(
|
style: body1Style.copyWith(
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
color: Colors.white,
|
||||||
? Colors.black
|
|
||||||
: BrandColors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
// height: 1.1,
|
// height: 1.1,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/initializing/backblaze_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/initializing/backblaze_form_cubit.dart';
|
||||||
|
@ -45,43 +46,56 @@ class InitializingPage extends StatelessWidget {
|
||||||
},
|
},
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: ListView(
|
body: SingleChildScrollView(
|
||||||
children: [
|
child: Column(
|
||||||
Padding(
|
children: [
|
||||||
padding: brandPagePadding2.copyWith(top: 10, bottom: 10),
|
Padding(
|
||||||
child: ProgressBar(
|
padding: brandPagePadding2.copyWith(top: 10, bottom: 10),
|
||||||
steps: [
|
child: ProgressBar(
|
||||||
'Hetzner',
|
steps: [
|
||||||
'CloudFlare',
|
'Hetzner',
|
||||||
'Backblaze',
|
'CloudFlare',
|
||||||
'Domain',
|
'Backblaze',
|
||||||
'User',
|
'Domain',
|
||||||
'Server',
|
'User',
|
||||||
' ✅',
|
'Server',
|
||||||
' ✅',
|
' ✅',
|
||||||
' ✅',
|
' ✅',
|
||||||
' ✅',
|
' ✅',
|
||||||
],
|
' ✅',
|
||||||
activeIndex: cubit.state.progress,
|
],
|
||||||
|
activeIndex: cubit.state.progress,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
_addCard(
|
||||||
_addCard(
|
AnimatedSwitcher(
|
||||||
AnimatedSwitcher(
|
duration: Duration(milliseconds: 300),
|
||||||
duration: Duration(milliseconds: 300),
|
child: actualPage,
|
||||||
child: actualPage,
|
),
|
||||||
),
|
),
|
||||||
),
|
ConstrainedBox(
|
||||||
BrandButton.text(
|
constraints: BoxConstraints(
|
||||||
title: cubit.state.isFullyInitilized
|
minHeight: MediaQuery.of(context).size.height -
|
||||||
? 'basis.close'.tr()
|
MediaQuery.of(context).padding.top -
|
||||||
: 'basis.later'.tr(),
|
MediaQuery.of(context).padding.bottom -
|
||||||
onPressed: () {
|
566,
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
),
|
||||||
materialRoute(RootPage()),
|
child: Container(
|
||||||
(predicate) => false,
|
alignment: Alignment.center,
|
||||||
);
|
child: BrandButton.text(
|
||||||
}),
|
title: cubit.state.isFullyInitilized
|
||||||
],
|
? 'basis.close'.tr()
|
||||||
|
: 'basis.later'.tr(),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
|
materialRoute(RootPage()),
|
||||||
|
(predicate) => false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -341,8 +355,10 @@ class InitializingPage extends StatelessWidget {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Spacer(),
|
BrandText.h2('initializing.22'.tr()),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
|
BrandText.body2('initializing.23'.tr()),
|
||||||
|
Spacer(),
|
||||||
CubitFormTextField(
|
CubitFormTextField(
|
||||||
formFieldCubit: context.read<RootUserFormCubit>().userName,
|
formFieldCubit: context.read<RootUserFormCubit>().userName,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
@ -409,7 +425,7 @@ class InitializingPage extends StatelessWidget {
|
||||||
BrandText.body2('initializing.11'.tr()),
|
BrandText.body2('initializing.11'.tr()),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
BrandButton.rised(
|
BrandButton.rised(
|
||||||
onPressed: isLoading!
|
onPressed: isLoading
|
||||||
? null
|
? null
|
||||||
: () => appConfigCubit.createServerAndSetDnsRecords(),
|
: () => appConfigCubit.createServerAndSetDnsRecords(),
|
||||||
title: isLoading ? 'basis.loading'.tr() : 'initializing.11'.tr(),
|
title: isLoading ? 'basis.loading'.tr() : 'initializing.11'.tr(),
|
||||||
|
@ -446,7 +462,7 @@ class InitializingPage extends StatelessWidget {
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
BrandText.body2(text),
|
BrandText.body2(text),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
if (!state.isLoading!)
|
if (!state.isLoading)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
BrandText.body2('initializing.16'.tr()),
|
BrandText.body2('initializing.16'.tr()),
|
||||||
|
@ -456,7 +472,7 @@ class InitializingPage extends StatelessWidget {
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (state.isLoading!) BrandText.body2('initializing.17'.tr()),
|
if (state.isLoading) BrandText.body2('initializing.17'.tr()),
|
||||||
Spacer(
|
Spacer(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
),
|
),
|
||||||
|
|
|
@ -27,8 +27,8 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
|
||||||
child: Builder(builder: (context) {
|
child: Builder(builder: (context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
child:
|
child: BrandHeader(
|
||||||
BrandHeader(title: 'more.settings.title'.tr(), hasBackButton: true),
|
title: 'more.settings.title'.tr(), hasBackButton: true),
|
||||||
preferredSize: Size.fromHeight(52),
|
preferredSize: Size.fromHeight(52),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
|
@ -116,7 +116,64 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.only(top: 20, bottom: 5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(width: 1, color: BrandColors.dividerColor),
|
||||||
|
)),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: _TextColumn(
|
||||||
|
title: 'more.settings.5'.tr(),
|
||||||
|
value: 'more.settings.6'.tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
primary: BrandColors.red1,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'basis.delete'.tr(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: BrandColors.white,
|
||||||
|
fontWeight: NamedFontWeight.demiBold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) {
|
||||||
|
return BrandAlert(
|
||||||
|
title: 'modals.3'.tr(),
|
||||||
|
contentText: 'modals.6'.tr(),
|
||||||
|
acitons: [
|
||||||
|
ActionButton(
|
||||||
|
text: 'modals.7'.tr(),
|
||||||
|
isRed: true,
|
||||||
|
onPressed: () async {
|
||||||
|
await context
|
||||||
|
.read<AppConfigCubit>()
|
||||||
|
.serverDelete();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}),
|
||||||
|
ActionButton(
|
||||||
|
text: 'basis.cancel'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -82,8 +82,6 @@ class _Card extends StatelessWidget {
|
||||||
switch (provider.type) {
|
switch (provider.type) {
|
||||||
case ProviderType.server:
|
case ProviderType.server:
|
||||||
title = 'providers.server.card_title'.tr();
|
title = 'providers.server.card_title'.tr();
|
||||||
stableText = 'providers.domain.status'.tr();
|
|
||||||
|
|
||||||
stableText = 'providers.server.status'.tr();
|
stableText = 'providers.server.status'.tr();
|
||||||
onTap = () => Navigator.of(context).push(
|
onTap = () => Navigator.of(context).push(
|
||||||
SlideBottomRoute(
|
SlideBottomRoute(
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
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,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
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 }
|
|
@ -0,0 +1,141 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,18 +2,26 @@ import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.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/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/cubit/server_detailed_info/server_detailed_info_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/state_types.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_divider/brand_divider.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.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/brand_text/brand_text.dart';
|
||||||
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/ui/components/switch_block/switch_bloc.dart';
|
import 'package:selfprivacy/ui/components/switch_block/switch_bloc.dart';
|
||||||
import 'package:selfprivacy/utils/named_font_weight.dart';
|
import 'package:selfprivacy/utils/named_font_weight.dart';
|
||||||
|
import 'cpu_chart.dart';
|
||||||
|
import 'network_charts.dart';
|
||||||
|
|
||||||
part 'server_settings.dart';
|
part 'server_settings.dart';
|
||||||
|
part 'text_details.dart';
|
||||||
|
part 'chart.dart';
|
||||||
|
part 'header.dart';
|
||||||
|
|
||||||
var navigatorKey = GlobalKey<NavigatorState>();
|
var navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
@ -48,240 +56,41 @@ class _ServerDetailsState extends State<ServerDetails>
|
||||||
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
|
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
|
||||||
var providerState = isReady ? StateType.stable : StateType.uninitialized;
|
var providerState = isReady ? StateType.stable : StateType.uninitialized;
|
||||||
|
|
||||||
late String title = 'providers.server.card_title'.tr();
|
|
||||||
|
|
||||||
return TabBarView(
|
return TabBarView(
|
||||||
physics: NeverScrollableScrollPhysics(),
|
physics: NeverScrollableScrollPhysics(),
|
||||||
controller: tabController,
|
controller: tabController,
|
||||||
children: [
|
children: [
|
||||||
BlocProvider(
|
SingleChildScrollView(
|
||||||
create: (context) => ServerDetailsCubit()..check(),
|
child: Column(
|
||||||
child: Builder(builder: (context) {
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
var details = context.watch<ServerDetailsCubit>().state;
|
children: [
|
||||||
if (details is ServerDetailsLoading ||
|
Padding(
|
||||||
details is ServerDetailsInitial) {
|
padding: brandPagePadding2,
|
||||||
return _TempMessage(message: 'basis.loading'.tr());
|
child: Column(
|
||||||
} else if (details is ServerDetailsNotReady) {
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
return _TempMessage(message: 'basis.no_data'.tr());
|
children: [
|
||||||
} else if (details is Loaded) {
|
_Header(
|
||||||
var data = details.serverInfo;
|
providerState: providerState,
|
||||||
var checkTime = details.checkTime;
|
tabController: tabController),
|
||||||
|
BrandText.body1('providers.server.bottom_sheet.1'.tr()),
|
||||||
return Column(
|
SizedBox(height: 10),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
BlocProvider(
|
||||||
children: [
|
create: (context) => HetznerMetricsCubit()..restart(),
|
||||||
Padding(
|
child: _Chart(),
|
||||||
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(),
|
||||||
} else {
|
child: _TextDetails(),
|
||||||
throw Exception('wrong state');
|
),
|
||||||
}
|
],
|
||||||
}),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
_ServerSettings(tabController: tabController),
|
_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');
|
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
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');
|
|
@ -397,7 +397,6 @@ class _ServiceDetails extends StatelessWidget {
|
||||||
try {
|
try {
|
||||||
await launch(
|
await launch(
|
||||||
url,
|
url,
|
||||||
forceSafariVC: true,
|
|
||||||
enableJavaScript: true,
|
enableJavaScript: true,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:crypto/crypto.dart';
|
|
||||||
|
|
||||||
String convertToSha512Hash(String text) {
|
|
||||||
var bytes = utf8.encode(text);
|
|
||||||
|
|
||||||
var hash = sha512.convert(bytes);
|
|
||||||
return hash.toString();
|
|
||||||
}
|
|
14
pubspec.lock
14
pubspec.lock
|
@ -176,6 +176,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
crypt:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: crypt
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.1"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -274,6 +281,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
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:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
|
@ -10,6 +10,7 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
crypt: ^4.0.1
|
||||||
crypto: ^3.0.0
|
crypto: ^3.0.0
|
||||||
cubit_form: ^1.0.0-nullsafety.0
|
cubit_form: ^1.0.0-nullsafety.0
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
|
@ -17,6 +18,7 @@ dependencies:
|
||||||
easy_localization: ^3.0.0
|
easy_localization: ^3.0.0
|
||||||
either_option: ^2.0.1-dev.1
|
either_option: ^2.0.1-dev.1
|
||||||
equatable: ^2.0.0
|
equatable: ^2.0.0
|
||||||
|
fl_chart: ^0.35.0
|
||||||
flutter_bloc: ^7.0.0
|
flutter_bloc: ^7.0.0
|
||||||
flutter_markdown: ^0.6.0
|
flutter_markdown: ^0.6.0
|
||||||
flutter_secure_storage: ^4.1.0
|
flutter_secure_storage: ^4.1.0
|
||||||
|
|
Loading…
Reference in New Issue