Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/new-launch-flow'
Browse files Browse the repository at this point in the history
  • Loading branch information
ricab committed Oct 23, 2024
2 parents d285b1c + 622e7fb commit abe0391
Show file tree
Hide file tree
Showing 40 changed files with 1,167 additions and 394 deletions.
4 changes: 4 additions & 0 deletions include/multipass/dart_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ int default_id();

long long memory_in_bytes(char* value);

const char* human_readable_memory(long long bytes);

long long get_total_disk_size();

char* default_mount_target(char* source);
}

Expand Down
5 changes: 4 additions & 1 deletion include/multipass/memory_size.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,12 @@ class MemorySize
long long in_megabytes() const noexcept;
long long in_gigabytes() const noexcept;

std::string human_readable() const;
std::string human_readable(unsigned int precision = 1, bool trim_zeros = false) const;

static MemorySize from_bytes(long long bytes) noexcept;

private:
explicit MemorySize(long long bytes) noexcept;
long long bytes;
};

Expand Down
19 changes: 19 additions & 0 deletions src/client/gui/ffi/dart_ffi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
#include "multipass/logging/log.h"
#include "multipass/memory_size.h"
#include "multipass/name_generator.h"
#include "multipass/platform.h"
#include "multipass/settings/settings.h"
#include "multipass/standard_paths.h"
#include "multipass/utils.h"
#include "multipass/version.h"

#include <QStorageInfo>

namespace mp = multipass;
namespace mpc = multipass::client;
namespace mpl = multipass::logging;
Expand Down Expand Up @@ -227,6 +231,21 @@ long long memory_in_bytes(char* value)
}
}

const char* human_readable_memory(long long bytes)
{
const auto string = mp::MemorySize::from_bytes(bytes).human_readable(/*precision=*/2, /*trim_zeros=*/true);
return strdup(string.c_str());
}

long long get_total_disk_size()
{
const auto mp_storage = MP_PLATFORM.multipass_storage_location();
const auto location =
mp_storage.isEmpty() ? MP_STDPATHS.writableLocation(mp::StandardPaths::AppDataLocation) : mp_storage;
QStorageInfo storageInfo{location};
return storageInfo.bytesTotal();
}

char* default_mount_target(char* source)
{
static constexpr auto error = "failed retrieving default mount target";
Expand Down
4 changes: 2 additions & 2 deletions src/client/gui/lib/catalogue/catalogue.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'package:grpc/grpc.dart';

import '../providers.dart';
import 'image_card.dart';
import 'launch_panel.dart';
import 'launch_form.dart';

final imagesProvider = FutureProvider<List<ImageInfo>>((ref) async {
if (ref.watch(daemonAvailableProvider)) {
Expand Down Expand Up @@ -114,7 +114,7 @@ class CatalogueScreen extends ConsumerWidget {
);

return Scaffold(
endDrawer: const LaunchPanel(),
endDrawer: const LaunchForm(),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 140).copyWith(top: 40),
child: Column(
Expand Down
16 changes: 5 additions & 11 deletions src/client/gui/lib/catalogue/image_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'package:flutter_svg/flutter_svg.dart';

import '../providers.dart';
import 'launch_form.dart';
import 'launch_panel.dart';

class ImageCard extends ConsumerWidget {
final ImageInfo image;
Expand Down Expand Up @@ -61,22 +60,17 @@ class ImageCard extends ConsumerWidget {
onPressed: () {
final name = ref.read(randomNameProvider);
final aliasInfo = image.aliasesInfo.first;
final request = LaunchRequest(
final launchRequest = LaunchRequest(
instanceName: name,
image: aliasInfo.alias,
numCores: defaultCpus,
memSize: '${defaultRam}B',
diskSpace: '${defaultDisk}B',
remoteName:
aliasInfo.hasRemoteName() ? aliasInfo.remoteName : null,
);

final grpcClient = ref.read(grpcClientProvider);
final operation = LaunchOperation(
stream: grpcClient.launch(request),
name: name,
image: imageName(image),
);

ref.read(launchOperationProvider.notifier).state = operation;
Scaffold.of(context).openEndDrawer();
initiateLaunchFlow(ref, launchRequest);
},
child: const Text('Launch'),
),
Expand Down
101 changes: 61 additions & 40 deletions src/client/gui/lib/catalogue/launch_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@ import 'dart:async';

import 'package:basics/basics.dart';
import 'package:flutter/material.dart' hide Switch, ImageInfo;
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:rxdart/rxdart.dart';

import '../ffi.dart';
import '../notifications.dart';
import '../platform/platform.dart';
import '../providers.dart';
import '../sidebar.dart';
import '../switch.dart';
import '../vm_details/cpus_slider.dart';
import '../vm_details/disk_slider.dart';
import '../vm_details/mapping_slider.dart';
import '../vm_details/mount_points.dart';
import '../vm_details/ram_slider.dart';
import '../vm_details/spec_input.dart';
import 'launch_panel.dart';

final launchingImageProvider = StateProvider<ImageInfo>((_) => ImageInfo());

Expand All @@ -26,6 +31,10 @@ String imageName(ImageInfo imageInfo) {
: '$result ${imageInfo.codename}';
}

final defaultCpus = 1;
final defaultRam = 1.gibi;
final defaultDisk = 5.gibi;

class LaunchForm extends ConsumerStatefulWidget {
const LaunchForm({super.key});

Expand Down Expand Up @@ -75,33 +84,19 @@ class _LaunchFormState extends ConsumerState<LaunchForm> {
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w300),
);

final cpusInput = SpecInput(
initialValue: '1',
validator: (value) => (int.tryParse(value!) ?? 0) > 0
? null
: 'Number of CPUs must be greater than 0',
onSaved: (value) => launchRequest.numCores = int.parse(value!),
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
helper: 'Number of cores',
label: 'CPUs',
final cpusSlider = CpusSlider(
initialValue: defaultCpus,
onSaved: (value) => launchRequest.numCores = value!,
);

final memoryInput = SpecInput(
initialValue: '1',
helper: 'Default unit in Gigabytes',
label: 'Memory',
validator: memorySizeValidator,
onSaved: (value) => launchRequest.memSize =
double.tryParse(value!) != null ? '${value}GB' : value,
final memorySlider = RamSlider(
initialValue: defaultRam,
onSaved: (value) => launchRequest.memSize = '${value!}B',
);

final diskInput = SpecInput(
initialValue: '5',
helper: 'Default unit in Gigabytes',
label: 'Disk',
validator: memorySizeValidator,
onSaved: (value) => launchRequest.diskSpace =
double.tryParse(value!) != null ? '${value}GB' : value,
final diskSlider = DiskSlider(
initialValue: defaultDisk,
onSaved: (value) => launchRequest.diskSpace = '${value!}B',
);

final bridgedSwitch = FormField<bool>(
Expand Down Expand Up @@ -211,12 +206,12 @@ class _LaunchFormState extends ConsumerState<LaunchForm> {
height: 50,
child: Text('Resources', style: TextStyle(fontSize: 24)),
),
Row(children: [
cpusInput,
const SizedBox(width: 24),
memoryInput,
const SizedBox(width: 24),
diskInput,
Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
Expanded(child: cpusSlider),
const SizedBox(width: 86),
Expanded(child: memorySlider),
const SizedBox(width: 86),
Expanded(child: diskSlider),
]),
const Divider(height: 60),
const SizedBox(
Expand Down Expand Up @@ -251,13 +246,16 @@ class _LaunchFormState extends ConsumerState<LaunchForm> {
child: Container(
alignment: Alignment.topCenter,
color: Colors.white,
padding: const EdgeInsets.all(16),
child: Form(
key: formKey,
autovalidateMode: AutovalidateMode.always,
child: SingleChildScrollView(
clipBehavior: Clip.none,
controller: scrollController,
child: formBody,
child: Padding(
padding: const EdgeInsets.all(24),
child: formBody,
),
),
),
),
Expand Down Expand Up @@ -303,16 +301,39 @@ class _LaunchFormState extends ConsumerState<LaunchForm> {
mountRequest.targetPaths.first.instanceName = launchRequest.instanceName;
}

final grpcClient = ref.read(grpcClientProvider);
final operation = LaunchOperation(
stream: grpcClient.launch(launchRequest, mountRequests),
name: launchRequest.instanceName,
image: imageName(imageInfo),
);
ref.read(launchOperationProvider.notifier).state = operation;
initiateLaunchFlow(ref, launchRequest, mountRequests);
Scaffold.of(context).closeEndDrawer();
}
}

void initiateLaunchFlow(
WidgetRef ref,
LaunchRequest launchRequest, [
List<MountRequest> mountRequests = const [],
]) {
final grpcClient = ref.read(grpcClientProvider);
final launchingVmsNotifier = ref.read(launchingVmsProvider.notifier);

launchingVmsNotifier.add(launchRequest);
final cancelCompleter = Completer<void>();
final launchStream = grpcClient
.launch(
launchRequest,
mountRequests: mountRequests,
cancel: cancelCompleter.future,
)
.doOnDone(() => launchingVmsNotifier.remove(launchRequest.instanceName));

final notification = LaunchingNotification(
name: launchRequest.instanceName,
cancelCompleter: cancelCompleter,
stream: launchStream,
);

ref.read(notificationsProvider.notifier).add(notification);
ref.read(sidebarKeyProvider.notifier).set('vm-${launchRequest.instanceName}');
}

FormFieldValidator<String> nameValidator(
Iterable<String> existingNames,
Iterable<String> deletedNames,
Expand Down
118 changes: 0 additions & 118 deletions src/client/gui/lib/catalogue/launch_operation_progress.dart

This file was deleted.

Loading

0 comments on commit abe0391

Please sign in to comment.