forked from SelfPrivacy/selfprivacy.org.app
DNS records cubit and screen
parent
83a2d19e37
commit
914d56ff87
|
@ -91,6 +91,28 @@
|
|||
"status": "Status — Good",
|
||||
"bottom_sheet": {
|
||||
"1": "It's your personal internet address that will point to the server and other services of yours."
|
||||
},
|
||||
"screen_title": "Domain and DNS",
|
||||
"states": {
|
||||
"ok": "Records are OK",
|
||||
"error": "Problems found",
|
||||
"error_subtitle": "Tap here to fix them",
|
||||
"refreshing": "Refreshing status...",
|
||||
"uninitialized": "Data is not retrieved yet"
|
||||
},
|
||||
"record_description": {
|
||||
"root": "Root domain",
|
||||
"api": "SelfPrivacy API",
|
||||
"cloud": "File cloud",
|
||||
"git": "Git server",
|
||||
"meet": "Video conference",
|
||||
"social": "Social network",
|
||||
"password": "Password manager",
|
||||
"vpn": "VPN",
|
||||
"mx": "MX record",
|
||||
"dmarc": "DMARC record",
|
||||
"spf": "SPF record",
|
||||
"dkim": "DKIM key"
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
|
@ -120,7 +142,8 @@
|
|||
"_comment": "Card shown when user skips initial setup",
|
||||
"1": "Please finish application setup using ",
|
||||
"2": "@:more.configuration_wizard",
|
||||
"3": " for further work"
|
||||
"3": " for further work",
|
||||
"in_menu": "Server is not set up yet. Please finish setup using setup wizard for further work."
|
||||
},
|
||||
"services": {
|
||||
"_comment": "Вкладка сервисы",
|
||||
|
|
|
@ -91,6 +91,38 @@
|
|||
"status": "Статус — в норме",
|
||||
"bottom_sheet": {
|
||||
"1": "Это ваш личный адрес в интернете, который будет указывать на сервер и другие ваши сервисы."
|
||||
},
|
||||
"screen_title": "Домен и DNS",
|
||||
"states": {
|
||||
"ok": "Записи в норме",
|
||||
"error": "Обнаружены проблемы",
|
||||
"error_subtitle": "Нажмите здесь, чтобы исправить",
|
||||
"refreshing": "Обновление данных...",
|
||||
"uninitialized": "Данные ещё не получены"
|
||||
},
|
||||
"record_description": {
|
||||
"root": "Корневой домен",
|
||||
"api": "SelfPrivacy API",
|
||||
"cloud": "Файловое облако",
|
||||
"git": "Git сервер",
|
||||
"meet": "Видеоконференции",
|
||||
"social": "Социальная сеть",
|
||||
"password": "Менеджер паролей",
|
||||
"vpn": "VPN",
|
||||
"mx": "MX запись",
|
||||
"dmarc": "DMARC запись",
|
||||
"spf": "SPF запись",
|
||||
"dkim": "DKIM ключ"
|
||||
},
|
||||
"cards": {
|
||||
"services": {
|
||||
"title": "Сервисы",
|
||||
"subtitle": "Записи типа “A” необходимые для работы сервисов."
|
||||
},
|
||||
"email": {
|
||||
"title": "Электронная почта",
|
||||
"subtitle": "Записи необходимые для безопасного обмена электронной почтой."
|
||||
}
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
|
@ -98,7 +130,7 @@
|
|||
"status": "Статус — в норме",
|
||||
"bottom_sheet": {
|
||||
"1": "Выручит Вас в любой ситуации: хакерская атака, удаление сервера и т.д.",
|
||||
"2": "Использовано 3Gb из бесплатых 10Gb. Последнее копирование была сделано вчера в {}."
|
||||
"2": "Использовано 3Gb из бесплатых 10Gb. Последнее копирование была сделано вчера в {}."
|
||||
},
|
||||
"reuploadKey": "Принудительно обновить ключ",
|
||||
"reuploadedKey": "Ключ на сервере обновлён",
|
||||
|
@ -121,7 +153,8 @@
|
|||
"_comment": "Карточка показывающая когда человек скипнул настройку, на карте текст из 3 блоков, средний содержит ссыку на мастер подключения",
|
||||
"1": "Завершите настройку приложения используя ",
|
||||
"2": "@:more.configuration_wizard",
|
||||
"3": " для продолжения работы"
|
||||
"3": " для продолжения работы",
|
||||
"in_menu": "Сервер ещё не настроен, воспользуйтесь мастером подключения."
|
||||
},
|
||||
"services": {
|
||||
"_comment": "Вкладка сервисы",
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||
|
@ -20,6 +21,7 @@ class BlocAndProviderConfig extends StatelessWidget {
|
|||
var appConfigCubit = AppConfigCubit()..load();
|
||||
var servicesCubit = ServicesCubit(appConfigCubit);
|
||||
var backupsCubit = BackupsCubit(appConfigCubit);
|
||||
var dnsRecordsCubit = DnsRecordsCubit(appConfigCubit);
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
|
@ -33,6 +35,7 @@ class BlocAndProviderConfig extends StatelessWidget {
|
|||
BlocProvider(create: (_) => usersCubit..load(), lazy: false),
|
||||
BlocProvider(create: (_) => servicesCubit..load(), lazy: false),
|
||||
BlocProvider(create: (_) => backupsCubit..load(), lazy: false),
|
||||
BlocProvider(create: (_) => dnsRecordsCubit..load()),
|
||||
BlocProvider(
|
||||
create: (_) =>
|
||||
JobsCubit(usersCubit: usersCubit, servicesCubit: servicesCubit),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/api_map.dart';
|
||||
|
@ -87,6 +88,36 @@ class CloudflareApi extends ApiMap {
|
|||
close(client);
|
||||
}
|
||||
|
||||
Future<List<DnsRecord>> getDnsRecords({
|
||||
required CloudFlareDomain cloudFlareDomain,
|
||||
}) async {
|
||||
var domainName = cloudFlareDomain.domainName;
|
||||
var domainZoneId = cloudFlareDomain.zoneId;
|
||||
|
||||
var url = '/zones/$domainZoneId/dns_records';
|
||||
|
||||
var client = await getClient();
|
||||
Response response = await client.get(url);
|
||||
|
||||
List records = response.data['result'] ?? [];
|
||||
var allRecords = <DnsRecord>[];
|
||||
|
||||
for (var record in records) {
|
||||
if (record['zone_name'] == domainName) {
|
||||
allRecords.add(DnsRecord(
|
||||
name: record['name'],
|
||||
type: record['type'],
|
||||
content: record['content'],
|
||||
ttl: record['ttl'],
|
||||
proxied: record['proxied'],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
close(client);
|
||||
return allRecords;
|
||||
}
|
||||
|
||||
Future<void> createMultipleDnsRecords({
|
||||
String? ip4,
|
||||
required CloudFlareDomain cloudFlareDomain,
|
||||
|
@ -113,33 +144,33 @@ class CloudflareApi extends ApiMap {
|
|||
close(client);
|
||||
}
|
||||
|
||||
List<DnsRecords> projectDnsRecords(String? domainName, String? ip4) {
|
||||
var domainA = DnsRecords(type: 'A', name: domainName, content: ip4);
|
||||
List<DnsRecord> projectDnsRecords(String? domainName, String? ip4) {
|
||||
var domainA = DnsRecord(type: 'A', name: domainName, content: ip4);
|
||||
|
||||
var mx = DnsRecords(type: 'MX', name: '@', content: domainName);
|
||||
var apiA = DnsRecords(type: 'A', name: 'api', content: ip4);
|
||||
var cloudA = DnsRecords(type: 'A', name: 'cloud', content: ip4);
|
||||
var gitA = DnsRecords(type: 'A', name: 'git', content: ip4);
|
||||
var meetA = DnsRecords(type: 'A', name: 'meet', content: ip4);
|
||||
var passwordA = DnsRecords(type: 'A', name: 'password', content: ip4);
|
||||
var socialA = DnsRecords(type: 'A', name: 'social', content: ip4);
|
||||
var vpn = DnsRecords(type: 'A', name: 'vpn', content: ip4);
|
||||
var mx = DnsRecord(type: 'MX', name: '@', content: domainName);
|
||||
var apiA = DnsRecord(type: 'A', name: 'api', content: ip4);
|
||||
var cloudA = DnsRecord(type: 'A', name: 'cloud', content: ip4);
|
||||
var gitA = DnsRecord(type: 'A', name: 'git', content: ip4);
|
||||
var meetA = DnsRecord(type: 'A', name: 'meet', content: ip4);
|
||||
var passwordA = DnsRecord(type: 'A', name: 'password', content: ip4);
|
||||
var socialA = DnsRecord(type: 'A', name: 'social', content: ip4);
|
||||
var vpn = DnsRecord(type: 'A', name: 'vpn', content: ip4);
|
||||
|
||||
var txt1 = DnsRecords(
|
||||
var txt1 = DnsRecord(
|
||||
type: 'TXT',
|
||||
name: '_dmarc',
|
||||
content: 'v=DMARC1; p=none',
|
||||
ttl: 18000,
|
||||
);
|
||||
|
||||
var txt2 = DnsRecords(
|
||||
var txt2 = DnsRecord(
|
||||
type: 'TXT',
|
||||
name: domainName,
|
||||
content: 'v=spf1 a mx ip4:$ip4 -all',
|
||||
ttl: 18000,
|
||||
);
|
||||
|
||||
return <DnsRecords>[
|
||||
return <DnsRecord>[
|
||||
domainA,
|
||||
apiA,
|
||||
cloudA,
|
||||
|
@ -159,7 +190,7 @@ class CloudflareApi extends ApiMap {
|
|||
final domainZoneId = cloudFlareDomain.zoneId;
|
||||
final url = '$rootAddress/zones/$domainZoneId/dns_records';
|
||||
|
||||
final dkimRecord = DnsRecords(
|
||||
final dkimRecord = DnsRecord(
|
||||
type: 'TXT',
|
||||
name: 'selector._domainkey',
|
||||
content: dkimRecordString,
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
|
||||
import 'package:selfprivacy/logic/models/dns_records.dart';
|
||||
|
||||
import '../../api_maps/cloudflare.dart';
|
||||
import '../../api_maps/server.dart';
|
||||
|
||||
part 'dns_records_state.dart';
|
||||
|
||||
class DnsRecordsCubit extends AppConfigDependendCubit<DnsRecordsState> {
|
||||
DnsRecordsCubit(AppConfigCubit appConfigCubit)
|
||||
: super(appConfigCubit,
|
||||
DnsRecordsState(dnsState: DnsRecordsStatus.refreshing));
|
||||
|
||||
final api = ServerApi();
|
||||
final cloudflare = CloudflareApi();
|
||||
|
||||
Future<void> load() async {
|
||||
emit(DnsRecordsState(
|
||||
dnsState: DnsRecordsStatus.refreshing,
|
||||
dnsRecords: _getDesiredDnsRecords(
|
||||
appConfigCubit.state.cloudFlareDomain?.domainName, "", "")));
|
||||
print('Loading DNS status');
|
||||
if (appConfigCubit.state is AppConfigFinished) {
|
||||
final CloudFlareDomain? domain = appConfigCubit.state.cloudFlareDomain;
|
||||
final String? ipAddress = appConfigCubit.state.hetznerServer?.ip4;
|
||||
if (domain != null && ipAddress != null) {
|
||||
final List<DnsRecord> records =
|
||||
await cloudflare.getDnsRecords(cloudFlareDomain: domain);
|
||||
final dkimPublicKey = await api.getDkim();
|
||||
final desiredRecords =
|
||||
_getDesiredDnsRecords(domain.domainName, ipAddress, dkimPublicKey);
|
||||
List<DesiredDnsRecord> foundRecords = [];
|
||||
for (final record in desiredRecords) {
|
||||
if (record.description ==
|
||||
'providers.domain.record_description.dkim') {
|
||||
final foundRecord = records.firstWhere(
|
||||
(r) => r.name == record.name && r.type == record.type,
|
||||
orElse: () => DnsRecord(
|
||||
name: record.name,
|
||||
type: record.type,
|
||||
content: '',
|
||||
ttl: 800,
|
||||
proxied: false));
|
||||
// remove all spaces and tabulators from
|
||||
// the foundRecord.content and the record.content
|
||||
// to compare them
|
||||
final foundContent =
|
||||
foundRecord.content?.replaceAll(RegExp(r'\s+'), '');
|
||||
final content = record.content.replaceAll(RegExp(r'\s+'), '');
|
||||
if (foundContent == content) {
|
||||
foundRecords.add(record.copyWith(isSatisfied: true));
|
||||
} else {
|
||||
foundRecords.add(record.copyWith(isSatisfied: false));
|
||||
}
|
||||
} else {
|
||||
if (records.any((r) =>
|
||||
r.name == record.name &&
|
||||
r.type == record.type &&
|
||||
r.content == record.content)) {
|
||||
foundRecords.add(record.copyWith(isSatisfied: true));
|
||||
} else {
|
||||
foundRecords.add(record.copyWith(isSatisfied: false));
|
||||
}
|
||||
}
|
||||
}
|
||||
emit(DnsRecordsState(
|
||||
dnsRecords: foundRecords,
|
||||
dnsState: foundRecords.any((r) => r.isSatisfied == false)
|
||||
? DnsRecordsStatus.error
|
||||
: DnsRecordsStatus.good,
|
||||
));
|
||||
} else {
|
||||
emit(DnsRecordsState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onChange(Change<DnsRecordsState> change) {
|
||||
print(change);
|
||||
super.onChange(change);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clear() async {
|
||||
emit(DnsRecordsState(dnsState: DnsRecordsStatus.error));
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
emit(state.copyWith(dnsState: DnsRecordsStatus.refreshing));
|
||||
await load();
|
||||
}
|
||||
|
||||
Future<void> fix() async {
|
||||
emit(state.copyWith(dnsState: DnsRecordsStatus.refreshing));
|
||||
final CloudFlareDomain? domain = appConfigCubit.state.cloudFlareDomain;
|
||||
final String? ipAddress = appConfigCubit.state.hetznerServer?.ip4;
|
||||
final dkimPublicKey = await api.getDkim();
|
||||
await cloudflare.removeSimilarRecords(cloudFlareDomain: domain!);
|
||||
await cloudflare.createMultipleDnsRecords(
|
||||
cloudFlareDomain: domain, ip4: ipAddress);
|
||||
await cloudflare.setDkim(dkimPublicKey, domain);
|
||||
await load();
|
||||
}
|
||||
|
||||
List<DesiredDnsRecord> _getDesiredDnsRecords(
|
||||
String? domainName, String? ipAddress, String? dkimPublicKey) {
|
||||
if (domainName == null || ipAddress == null || dkimPublicKey == null) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
DesiredDnsRecord(
|
||||
name: domainName,
|
||||
content: ipAddress,
|
||||
description: 'providers.domain.record_description.root',
|
||||
),
|
||||
DesiredDnsRecord(
|
||||
name: 'api.$domainName',
|
||||
content: ipAddress,
|
||||
description: 'providers.domain.record_description.api',
|
||||
),
|
||||
DesiredDnsRecord(
|
||||
name: 'cloud.$domainName',
|
||||
content: ipAddress,
|
||||
description: 'providers.domain.record_description.cloud',
|
||||
),
|
||||
DesiredDnsRecord(
|
||||
name: 'git.$domainName',
|
||||
content: ipAddress,
|
||||
description: 'providers.domain.record_description.git',
|
||||
),
|
||||
DesiredDnsRecord(
|
||||
name: 'meet.$domainName',
|
||||
content: ipAddress,
|
||||
description: 'providers.domain.record_description.meet',
|
||||
),
|
||||
DesiredDnsRecord(
|
||||
name: 'social.$domainName',
|
||||
content: ipAddress,
|
||||
description: 'providers.domain.record_description.social',
|
||||
),
|
||||
DesiredDnsRecord(
|
||||
name: 'password.$domainName',
|
||||
content: ipAddress,
|
||||
description: 'providers.domain.record_description.password',
|
||||
),
|
||||
DesiredDnsRecord(
|
||||
name: 'vpn.$domainName',
|
||||
content: ipAddress,
|
||||
description: 'providers.domain.record_description.vpn',
|
||||
),
|
||||
DesiredDnsRecord(
|
||||
name: domainName,
|
||||
content: domainName,
|
||||
description: 'providers.domain.record_description.mx',
|
||||
type: 'MX',
|
||||
category: DnsRecordsCategory.email,
|
||||
),
|
||||
DesiredDnsRecord(
|
||||
name: '_dmarc.$domainName',
|
||||
content: 'v=DMARC1; p=none',
|
||||
description: 'providers.domain.record_description.dmarc',
|
||||
type: 'TXT',
|
||||
category: DnsRecordsCategory.email,
|
||||
),
|
||||
DesiredDnsRecord(
|
||||
name: domainName,
|
||||
content: 'v=spf1 a mx ip4:$ipAddress -all',
|
||||
description: 'providers.domain.record_description.spf',
|
||||
type: 'TXT',
|
||||
category: DnsRecordsCategory.email,
|
||||
),
|
||||
DesiredDnsRecord(
|
||||
name: 'selector._domainkey.$domainName',
|
||||
content: dkimPublicKey,
|
||||
description: 'providers.domain.record_description.dkim',
|
||||
type: 'TXT',
|
||||
category: DnsRecordsCategory.email,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
part of 'dns_records_cubit.dart';
|
||||
|
||||
enum DnsRecordsStatus {
|
||||
uninitialized,
|
||||
refreshing,
|
||||
good,
|
||||
error,
|
||||
}
|
||||
|
||||
enum DnsRecordsCategory {
|
||||
services,
|
||||
email,
|
||||
other,
|
||||
}
|
||||
|
||||
class DnsRecordsState extends AppConfigDependendState {
|
||||
const DnsRecordsState({
|
||||
this.dnsState = DnsRecordsStatus.uninitialized,
|
||||
this.dnsRecords = const [],
|
||||
});
|
||||
|
||||
final DnsRecordsStatus dnsState;
|
||||
final List<DesiredDnsRecord> dnsRecords;
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
dnsState,
|
||||
dnsRecords,
|
||||
];
|
||||
|
||||
DnsRecordsState copyWith({
|
||||
DnsRecordsStatus? dnsState,
|
||||
List<DesiredDnsRecord>? dnsRecords,
|
||||
}) {
|
||||
return DnsRecordsState(
|
||||
dnsState: dnsState ?? this.dnsState,
|
||||
dnsRecords: dnsRecords ?? this.dnsRecords,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DesiredDnsRecord {
|
||||
const DesiredDnsRecord({
|
||||
required this.name,
|
||||
this.type = "A",
|
||||
required this.content,
|
||||
this.description = '',
|
||||
this.category = DnsRecordsCategory.services,
|
||||
this.isSatisfied = false,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String type;
|
||||
final String content;
|
||||
final String description;
|
||||
final DnsRecordsCategory category;
|
||||
final bool isSatisfied;
|
||||
|
||||
DesiredDnsRecord copyWith({
|
||||
String? name,
|
||||
String? type,
|
||||
String? content,
|
||||
String? description,
|
||||
DnsRecordsCategory? category,
|
||||
bool? isSatisfied,
|
||||
}) {
|
||||
return DesiredDnsRecord(
|
||||
name: name ?? this.name,
|
||||
type: type ?? this.type,
|
||||
content: content ?? this.content,
|
||||
description: description ?? this.description,
|
||||
category: category ?? this.category,
|
||||
isSatisfied: isSatisfied ?? this.isSatisfied,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,8 +3,8 @@ import 'package:json_annotation/json_annotation.dart';
|
|||
part 'dns_records.g.dart';
|
||||
|
||||
@JsonSerializable(createToJson: true, createFactory: false)
|
||||
class DnsRecords {
|
||||
DnsRecords({
|
||||
class DnsRecord {
|
||||
DnsRecord({
|
||||
required this.type,
|
||||
required this.name,
|
||||
required this.content,
|
||||
|
|
|
@ -6,7 +6,7 @@ part of 'dns_records.dart';
|
|||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Map<String, dynamic> _$DnsRecordsToJson(DnsRecords instance) =>
|
||||
Map<String, dynamic> _$DnsRecordsToJson(DnsRecord instance) =>
|
||||
<String, dynamic>{
|
||||
'type': instance.type,
|
||||
'name': instance.name,
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||
|
||||
class DnsDetailsPage extends StatefulWidget {
|
||||
@override
|
||||
_DnsDetailsPageState createState() => _DnsDetailsPageState();
|
||||
}
|
||||
|
||||
class _DnsDetailsPageState extends State<DnsDetailsPage> {
|
||||
Widget _getStateCard(DnsRecordsStatus dnsState, Function fixCallback) {
|
||||
var description = '';
|
||||
var subtitle = '';
|
||||
var icon = Icon(
|
||||
Icons.check,
|
||||
color: Colors.green,
|
||||
);
|
||||
switch (dnsState) {
|
||||
case DnsRecordsStatus.uninitialized:
|
||||
description = 'providers.domain.states.uninitialized'.tr();
|
||||
icon = Icon(
|
||||
Icons.refresh,
|
||||
);
|
||||
break;
|
||||
case DnsRecordsStatus.refreshing:
|
||||
description = 'providers.domain.states.refreshing'.tr();
|
||||
icon = Icon(
|
||||
Icons.refresh,
|
||||
);
|
||||
break;
|
||||
case DnsRecordsStatus.good:
|
||||
description = 'providers.domain.states.ok'.tr();
|
||||
icon = Icon(
|
||||
Icons.check,
|
||||
color: Colors.green,
|
||||
);
|
||||
break;
|
||||
case DnsRecordsStatus.error:
|
||||
description = 'providers.domain.states.error'.tr();
|
||||
subtitle = 'providers.domain.states.error_subtitle'.tr();
|
||||
icon = Icon(
|
||||
Icons.error,
|
||||
color: Colors.red,
|
||||
);
|
||||
break;
|
||||
}
|
||||
return ListTile(
|
||||
onTap: dnsState == DnsRecordsStatus.error ? () => fixCallback() : null,
|
||||
title: Text(
|
||||
description,
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
subtitle: subtitle != '' ? Text(subtitle) : null,
|
||||
leading: icon,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var isReady = context.watch<AppConfigCubit>().state is AppConfigFinished;
|
||||
final domain = getIt<ApiConfigModel>().cloudFlareDomain?.domainName ?? '';
|
||||
var dnsCubit = context.watch<DnsRecordsCubit>().state;
|
||||
|
||||
print(dnsCubit.dnsState);
|
||||
|
||||
if (!isReady) {
|
||||
return BrandHeroScreen(
|
||||
hasBackButton: true,
|
||||
headerTitle: '',
|
||||
heroIcon: BrandIcons.globe,
|
||||
heroTitle: 'providers.domain.screen_title'.tr(),
|
||||
children: <Widget>[
|
||||
BrandCards.outlined(
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
'not_ready_card.in_menu'.tr(),
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return BrandHeroScreen(
|
||||
hasBackButton: true,
|
||||
heroSubtitle: domain,
|
||||
heroIcon: BrandIcons.globe,
|
||||
heroTitle: 'providers.domain.screen_title'.tr(),
|
||||
children: <Widget>[
|
||||
BrandCards.outlined(
|
||||
child: Column(
|
||||
children: [
|
||||
_getStateCard(dnsCubit.dnsState, () {
|
||||
context.read<DnsRecordsCubit>().fix();
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 16.0),
|
||||
// Outlined card with a list of A records and their
|
||||
// status.
|
||||
BrandCards.outlined(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
title: Text(
|
||||
'providers.domain.cards.services.title'.tr(),
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
subtitle: Text(
|
||||
'providers.domain.cards.services.subtitle'.tr(),
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
),
|
||||
...dnsCubit.dnsRecords
|
||||
.where(
|
||||
(dnsRecord) =>
|
||||
dnsRecord.category == DnsRecordsCategory.services,
|
||||
)
|
||||
.map(
|
||||
(dnsRecord) => Column(
|
||||
children: [
|
||||
Divider(
|
||||
height: 1.0,
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
dnsRecord.isSatisfied
|
||||
? Icons.check
|
||||
: dnsCubit.dnsState ==
|
||||
DnsRecordsStatus.refreshing
|
||||
? Icons.refresh
|
||||
: Icons.error,
|
||||
color: dnsRecord.isSatisfied
|
||||
? Colors.green
|
||||
: dnsCubit.dnsState ==
|
||||
DnsRecordsStatus.refreshing
|
||||
? Colors.grey
|
||||
: Colors.red,
|
||||
),
|
||||
title: Text(
|
||||
dnsRecord.description.tr(),
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
subtitle: Text(
|
||||
dnsRecord.name,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.0),
|
||||
BrandCards.outlined(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
title: Text(
|
||||
'providers.domain.cards.email.title'.tr(),
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
subtitle: Text(
|
||||
'providers.domain.cards.email.subtitle'.tr(),
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
),
|
||||
...dnsCubit.dnsRecords
|
||||
.where(
|
||||
(dnsRecord) =>
|
||||
dnsRecord.category == DnsRecordsCategory.email,
|
||||
)
|
||||
.map(
|
||||
(dnsRecord) => Column(
|
||||
children: [
|
||||
Divider(
|
||||
height: 1.0,
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
dnsRecord.isSatisfied
|
||||
? Icons.check
|
||||
: dnsCubit.dnsState ==
|
||||
DnsRecordsStatus.refreshing
|
||||
? Icons.refresh
|
||||
: Icons.error,
|
||||
color: dnsRecord.isSatisfied
|
||||
? Colors.green
|
||||
: dnsCubit.dnsState ==
|
||||
DnsRecordsStatus.refreshing
|
||||
? Colors.grey
|
||||
: Colors.red,
|
||||
),
|
||||
title: Text(
|
||||
dnsRecord.description.tr(),
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,10 +3,9 @@ import 'package:flutter/material.dart';
|
|||
import 'package:selfprivacy/config/brand_theme.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/provider.dart';
|
||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||
|
@ -15,9 +14,9 @@ import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart
|
|||
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
||||
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||
import 'package:selfprivacy/ui/pages/backup_details/backup_details.dart';
|
||||
import 'package:selfprivacy/ui/pages/dns_details/dns_details.dart';
|
||||
import 'package:selfprivacy/ui/pages/server_details/server_details.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
import 'package:selfprivacy/utils/ui_helpers.dart';
|
||||
|
||||
var navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
|
@ -33,6 +32,18 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
|||
Widget build(BuildContext context) {
|
||||
var isReady = context.watch<AppConfigCubit>().state is AppConfigFinished;
|
||||
var isBackupInitialized = context.watch<BackupsCubit>().state.isInitialized;
|
||||
var dnsStatus = context.watch<DnsRecordsCubit>().state.dnsState;
|
||||
|
||||
StateType getDnsStatus() {
|
||||
if (dnsStatus == DnsRecordsStatus.uninitialized ||
|
||||
dnsStatus == DnsRecordsStatus.refreshing) {
|
||||
return StateType.uninitialized;
|
||||
}
|
||||
if (dnsStatus == DnsRecordsStatus.error) {
|
||||
return StateType.warning;
|
||||
}
|
||||
return StateType.stable;
|
||||
}
|
||||
|
||||
final cards = ProviderType.values
|
||||
.map(
|
||||
|
@ -43,7 +54,9 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
|||
state: isReady
|
||||
? (type == ProviderType.backup && !isBackupInitialized
|
||||
? StateType.uninitialized
|
||||
: StateType.stable)
|
||||
: (type == ProviderType.domain)
|
||||
? getDnsStatus()
|
||||
: StateType.stable)
|
||||
: StateType.uninitialized,
|
||||
type: type,
|
||||
),
|
||||
|
@ -103,19 +116,13 @@ class _Card extends StatelessWidget {
|
|||
|
||||
break;
|
||||
case ProviderType.domain:
|
||||
title = 'providers.domain.card_title'.tr();
|
||||
title = 'providers.domain.screen_title'.tr();
|
||||
message = domainName;
|
||||
stableText = 'providers.domain.status'.tr();
|
||||
|
||||
onTap = () => showBrandBottomSheet<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return _ProviderDetails(
|
||||
provider: provider,
|
||||
statusText: stableText,
|
||||
);
|
||||
},
|
||||
);
|
||||
onTap = () => Navigator.of(context).push(materialRoute(
|
||||
DnsDetailsPage(),
|
||||
));
|
||||
break;
|
||||
case ProviderType.backup:
|
||||
title = 'providers.backup.card_title'.tr();
|
||||
|
@ -150,77 +157,3 @@ class _Card extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ProviderDetails extends StatelessWidget {
|
||||
const _ProviderDetails({
|
||||
Key? key,
|
||||
required this.provider,
|
||||
required this.statusText,
|
||||
}) : super(key: key);
|
||||
|
||||
final ProviderModel provider;
|
||||
final String? statusText;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
late String title;
|
||||
late List<Widget> children;
|
||||
|
||||
var config = context.watch<AppConfigCubit>().state;
|
||||
|
||||
var domainName = UiHelpers.getDomainName(config);
|
||||
|
||||
switch (provider.type) {
|
||||
case ProviderType.server:
|
||||
throw ('wrong type');
|
||||
case ProviderType.domain:
|
||||
title = 'providers.domain.card_title'.tr();
|
||||
children = [
|
||||
BrandText.body1('providers.domain.bottom_sheet.1'.tr()),
|
||||
SizedBox(height: 10),
|
||||
BrandText.body1(domainName),
|
||||
SizedBox(height: 10),
|
||||
BrandText.body1('providers.domain.status'.tr()),
|
||||
];
|
||||
break;
|
||||
case ProviderType.backup:
|
||||
title = 'providers.backup.card_title'.tr();
|
||||
children = [
|
||||
BrandText.body1('providers.backup.bottom_sheet.1'.tr()),
|
||||
SizedBox(height: 10),
|
||||
BrandText.body1(
|
||||
'providers.backup.bottom_sheet.2'.tr(args: [domainName, 'Time'])),
|
||||
SizedBox(height: 10),
|
||||
BrandText.body1('providers.backup.status'.tr()),
|
||||
];
|
||||
break;
|
||||
}
|
||||
return BrandBottomSheet(
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 40),
|
||||
Padding(
|
||||
padding: paddingH15V0,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
IconStatusMask(
|
||||
status: provider.state,
|
||||
child: Icon(provider.icon, size: 40, color: Colors.white),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
BrandText.h1(title),
|
||||
SizedBox(height: 10),
|
||||
...children,
|
||||
SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue