Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions client/assets/next/img/connected_instance.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions client/assets/next/img/idle_instance.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 1 addition & 3 deletions client/lib/enterprise/config_update.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,7 @@ class ConfigurationUpdater extends HookConsumerWidget {
instance.name,
updateResult,
);
toaster.show(
message: "Instance ${instance.name} updated: $message",
);
talker.info("Instance ${instance.logName} updated: $message");
}
}
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:mobile/open/widgets/dg_app_bar.dart';
import 'package:mobile/open/widgets/dg_mfa_step_label.dart';
import 'package:mobile/open/widgets/dg_button.dart';
import 'package:mobile/open/widgets/dg_icon_button.dart';
import 'package:mobile/open/widgets/rive_asset_animation.dart';
import 'package:mobile/open/widgets/toaster/toast_manager.dart';
import 'package:mobile/theme/color.dart';
import 'package:mobile/theme/text.dart';
Expand Down Expand Up @@ -117,9 +118,13 @@ class OpenIdMfaWaitingScreen extends HookConsumerWidget {
const SizedBox(height: 8),
Text(
"Waiting for authentication in your browser...",
style: DgText.bodyXs400.copyWith(color: DgColor.fgWhite60),
style: DgText.bodySm400.copyWith(color: DgColor.fgWhite60),
),
const Expanded(
child: RiveAssetAnimation(
"assets/next/rive/open_id.riv",
),
),
const Spacer(),
DgButton(
text: 'Cancel',
style: DgButtonStyle.outlined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ Future<void> _handleSubmit(
final requestData = EnrollmentStartRequest(token: token);
final uri = Uri.parse(url);
final enrolmentResponse = await proxyApi.startEnrollment(uri, requestData);
if (!enrolmentResponse.user.enrolled) {
toaster.showError(message: "Only enrolled users can add devices.");
return;
}
final instanceId = enrolmentResponse.instance.id;
final dbInstance = await db.managers.defguardInstances
.filter((row) => row.uuid.equals(instanceId))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ class AddInstanceQrScreen extends HookConsumerWidget {
url,
requestData,
);
if (!registrationResponse.user.enrolled) {
toaster.showError(
message: "Only enrolled users can add devices.",
);
isLoading.value = false;
await controller.resume();
return;
}
final instanceId = registrationResponse.instance.id;
final dbInstance = await db.managers.defguardInstances
.filter((row) => row.uuid.equals(instanceId))
Expand Down
10 changes: 4 additions & 6 deletions client/lib/open/screens/instance/instance_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import 'package:mobile/open/api.dart';
import 'package:mobile/open/riverpod/biometrics_state.dart';
import 'package:mobile/open/riverpod/plugin/plugin.dart';
import 'package:mobile/open/screens/add_instance/data_gathering_dialog.dart';
import 'package:mobile/open/screens/instance/refresh_instance_screen.dart';
import 'package:mobile/open/screens/instance/services/tunnel_service.dart';
import 'package:mobile/open/screens/instance/widgets/connect_dialog.dart';
import 'package:mobile/open/screens/instance/widgets/connection_conflict_dialog.dart';
import 'package:mobile/open/screens/instance/widgets/delete_instance_dialog.dart';
import 'package:mobile/open/screens/instance/widgets/refresh_instance_dialog.dart';
import 'package:mobile/open/screens/mfa/remote_mfa_qr_screen.dart';
import 'package:mobile/open/widgets/dg_app_bar.dart';
import 'package:mobile/open/widgets/dg_bottom_sheet.dart';
Expand Down Expand Up @@ -364,11 +364,9 @@ class _InstanceAppBar extends ConsumerWidget implements PreferredSizeWidget {
onTap: () {
final instance = screenDataAsync.value?.instance;
if (instance == null) return;
showDialog(
context: context,
builder: (context) =>
RefreshInstanceDialog(instance: instance),
);
RefreshInstanceScreenRoute(
RefreshInstanceScreenData(instance: instance),
).push(context);
},
),
DgMenuItem(
Expand Down
205 changes: 205 additions & 0 deletions client/lib/open/screens/instance/refresh_instance_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:material_ui/material_ui.dart';
import 'package:mobile/data/db/database.dart';
import 'package:mobile/data/proxy/enrollment.dart';
import 'package:mobile/logging.dart';
import 'package:mobile/open/api.dart';
import 'package:mobile/open/widgets/dg_app_bar.dart';
import 'package:mobile/open/widgets/dg_button.dart';
import 'package:mobile/open/widgets/dg_icon_button.dart';
import 'package:mobile/open/widgets/dg_text_form_field.dart';
import 'package:mobile/open/widgets/toaster/toast_manager.dart';
import 'package:mobile/theme/color.dart';
import 'package:mobile/theme/spacing.dart';
import 'package:mobile/theme/text.dart';
import 'package:mobile/utils/update_instance.dart';

class RefreshInstanceScreenData {
final DefguardInstance instance;

const RefreshInstanceScreenData({required this.instance});
}

class RefreshInstanceScreen extends StatelessWidget {
final DefguardInstance instance;

const RefreshInstanceScreen({super.key, required this.instance});

@override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: DgAppBar(
context: context,
showLogo: false,
actionLeft: DgIconButton(
icon: "close",
onTap: () => Navigator.of(context).pop(),
),
),
body: Container(
decoration: const BoxDecoration(gradient: DgColor.gradientPrimary),
child: SafeArea(child: _RefreshInstanceContent(instance: instance)),
),
);
}
}

class _RefreshInstanceContent extends HookConsumerWidget {
final DefguardInstance instance;

const _RefreshInstanceContent({required this.instance});

@override
Widget build(BuildContext context, WidgetRef ref) {
final db = ref.read(databaseProvider);
final toaster = ref.read(toastManagerProvider.notifier);
final formKey = useMemoized(() => GlobalKey<FormState>());
final tokenController = useTextEditingController();
final urlController = useTextEditingController(text: instance.proxyUrl);
final isLoading = useState(false);

String? validateUrl(String? value) {
if (value == null || value.trim().isEmpty) {
return "This field is required";
}
if (!_isValidUri(value.trim())) {
return "Enter valid URL";
}
return null;
}

Future<void> submit() async {
if (!(formKey.currentState?.validate() ?? false)) return;
isLoading.value = true;
try {
final refreshed = await _refreshInstance(
db,
toaster,
instance,
Uri.parse(urlController.text.trim()),
tokenController.text.trim(),
);
if (refreshed && context.mounted) {
Navigator.of(context).pop();
}
} finally {
if (context.mounted) isLoading.value = false;
}
}

return Form(
key: formKey,
child: CustomScrollView(
slivers: [
SliverPadding(
padding: const .fromLTRB(20, 12, 20, 0),
sliver: SliverList(
delegate: SliverChildListDelegate([
Semantics(
identifier: "refresh_instance_header",
child: Text(
"Refresh Instance",
style: DgText.h4.copyWith(color: DgColor.fgWhite100),
textAlign: .left,
),
),
const SizedBox(height: DgSpacing.sm),
Text(
"Enter your proxy URL and instance token to refresh the configuration.",
style: DgText.bodySm400.copyWith(color: DgColor.fgWhite60),
textAlign: .left,
),
const SizedBox(height: DgSpacing.xl3),
DgTextFormField(
identifier: "refresh_instance_token",
size: .big,
controller: tokenController,
label: "Token",
required: true,
hintText: "Instance token",
validator: (v) => v == null || v.trim().isEmpty
? "This field is required"
: null,
),
const SizedBox(height: DgSpacing.xl),
DgTextFormField(
identifier: "refresh_instance_url",
size: .big,
controller: urlController,
label: "URL",
required: true,
hintText: "Proxy URL",
keyboardType: .url,
validator: validateUrl,
),
]),
),
),
SliverFillRemaining(
hasScrollBody: false,
child: Padding(
padding: const .fromLTRB(20, 0, 20, 20),
child: Align(
alignment: .bottomCenter,
child: DgButton(
identifier: "refresh_instance_submit",
text: "Refresh",
style: .primary,
size: .big,
width: double.infinity,
loading: isLoading.value,
onTap: submit,
),
),
),
),
],
),
);
}
}

bool _isValidUri(String value) {
final uri = Uri.tryParse(value);
return uri != null && uri.hasScheme && uri.hasAuthority;
}

Future<bool> _refreshInstance(
AppDatabase db,
ToastManager toaster,
DefguardInstance instance,
Uri uri,
String token,
) async {
talker.debug("Submitting instance refresh form");
try {
// this is only for dio to capture cookies required for network info call
await proxyApi.startEnrollment(uri, EnrollmentStartRequest(token: token));
final networkInfo = await proxyApi.networkInfo(uri, instance.pubKey);
talker.debug("Retrieved new instance information from proxy");
final updateResult = await updateInstance(
db: db,
instance: instance,
configs: networkInfo.configs,
info: networkInfo.instance,
token: networkInfo.token,
);
if (updateResult != null && updateResult.didChange) {
final message = getInstanceUpdateMessage(instance.name, updateResult);
toaster.show(message: "Instance ${instance.name} updated: $message");
} else {
toaster.show(message: "Instance information refreshed");
}
talker.info("Instance information refreshed successfully");
return true;
} catch (e, st) {
toaster.showError(
message: "Failed to refresh instance information",
error: e,
stackTrace: st,
);
return false;
}
}
Loading
Loading