Flutter Hacker Virtual Machine Controller — Cyber Security Dashboard UI
Demo :
Click Video πππ
⭐ Features :
-
Modern Hacker Dashboard UI
-
Virtual Machine Control
-
Matrix Rain Effect
-
Fake Cyber Simulation
-
Flutter Desktop App
-
Open Source Project
Code :
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:google_fonts/google_fonts.dart';
// -----------------------------------------------------------------------------
// CONSTANTS & THEME
// -----------------------------------------------------------------------------
class AppColors {
static const Color background = Color(0xFF030303); // Deep black
static const Color surface = Color(0xFF0A0A0A);
static const Color primary = Color(0xFF00FF41); // Matrix Green
static const Color secondary = Color(0xFF008F11); // Darker Green
static const Color accent = Color(0xFF00FFFF); // Cyan
static const Color error = Color(0xFFFF0033);
static const Color terminal = Color(0xFF1E1E1E);
}
class AppTheme {
static ThemeData get darkTheme {
return ThemeData.dark().copyWith(
scaffoldBackgroundColor: AppColors.background,
primaryColor: AppColors.primary,
colorScheme: const ColorScheme.dark(
primary: AppColors.primary,
secondary: AppColors.accent,
surface: AppColors.surface,
background: AppColors.background,
),
textTheme: GoogleFonts.firaCodeTextTheme(ThemeData.dark().textTheme).apply(
bodyColor: AppColors.primary,
displayColor: AppColors.primary,
),
iconTheme: const IconThemeData(color: AppColors.primary),
);
}
}
// -----------------------------------------------------------------------------
// MODELS
// -----------------------------------------------------------------------------
class VirtualMachine {
final String id;
final String name;
final String state; // running, powered off, saved, unknown
final String osIcon; // simplified for now
VirtualMachine({
required this.id,
required this.name,
required this.state,
this.osIcon = 'assets/linux.png',
});
factory VirtualMachine.fromJson(Map<String, dynamic> json) {
return VirtualMachine(
id: json['id'] ?? '',
name: json['name'] ?? 'Unknown',
state: json['state'] ?? 'unknown',
);
}
}
class SystemStats {
final int cpu;
final int ram;
final int netIn;
final int netOut;
SystemStats({this.cpu = 0, this.ram = 0, this.netIn = 0, this.netOut = 0});
factory SystemStats.fromJson(Map<String, dynamic> json) {
return SystemStats(
cpu: json['cpu'] ?? 0,
ram: json['ram'] ?? 0,
netIn: json['network_in'] ?? 0,
netOut: json['network_out'] ?? 0,
);
}
}
// -----------------------------------------------------------------------------
// SERVICE
// -----------------------------------------------------------------------------
class VBoxService extends ChangeNotifier {
List<VirtualMachine> _vms = [];
SystemStats _stats = SystemStats();
List<String> _logs = [];
bool _isLoading = false;
List<VirtualMachine> get vms => _vms;
SystemStats get stats => _stats;
List<String> get logs => _logs;
bool get isLoading => _isLoading;
final String _scriptPath = 'backend/vbox_controller.py';
// Mock data for Web/Mobile or when Python fails
bool _useMock = false;
final List<VirtualMachine> _mockVMs = [
VirtualMachine(id: 'mock-1', name: 'CyberLinux_2077', state: 'running'),
VirtualMachine(id: 'mock-2', name: 'WinServer_Black', state: 'powered off'),
VirtualMachine(id: 'mock-3', name: 'NetRunner_v9', state: 'saved'),
];
Future<void> fetchVMs() async {
_isLoading = true;
notifyListeners();
try {
if (kIsWeb || _useMock) {
await Future.delayed(const Duration(milliseconds: 800)); // Fake network play
_vms = List.from(_mockVMs);
_log("fetched vms (Simulated)");
} else {
final result = await _runPython(['list']);
if (result.containsKey('vms')) {
_vms = (result['vms'] as List)
.map((e) => VirtualMachine.fromJson(e))
.toList();
}
_log("fetched vms");
}
} catch (e) {
// If native fetch fails, switch to mock automatically so app is usable
_log("Connection failed ($e). Switching to Simulation Mode.");
_useMock = true;
_vms = List.from(_mockVMs);
}
_isLoading = false;
notifyListeners();
}
Future<void> startVM(String id) async {
_log("initiating launch sequence for $id...");
notifyListeners();
if (_useMock || kIsWeb) {
await Future.delayed(const Duration(seconds: 2));
final index = _mockVMs.indexWhere((vm) => vm.id == id);
if (index != -1) {
_mockVMs[index] = VirtualMachine(id: id, name: _mockVMs[index].name, state: 'running');
_log("VM $id started successfully (Simulated).");
fetchVMs();
}
return;
}
try {
final result = await _runPython(['start', id]);
if (result['status'] == 'success') {
_log("VM $id started successfully.");
fetchVMs();
} else {
_log("Failed to start VM: ${result['message']}");
}
} catch (e) {
_log("Error starting VM: $e");
}
}
Future<void> stopVM(String id) async {
_log("sending kill signal to $id...");
notifyListeners();
if (_useMock || kIsWeb) {
await Future.delayed(const Duration(seconds: 1));
final index = _mockVMs.indexWhere((vm) => vm.id == id);
if (index != -1) {
_mockVMs[index] = VirtualMachine(id: id, name: _mockVMs[index].name, state: 'powered off');
_log("VM $id stopped (Simulated).");
fetchVMs();
}
return;
}
try {
final result = await _runPython(['stop', id]);
if (result['status'] == 'success') {
_log("VM $id stopped.");
fetchVMs();
} else {
_log("Failed to stop VM: ${result['message']}");
}
} catch (e) {
_log("Error stopping VM: $e");
}
}
Future<void> restartVM(String id) async {
_log("rebooting system $id...");
notifyListeners();
if (_useMock || kIsWeb) {
await Future.delayed(const Duration(seconds: 3));
final index = _mockVMs.indexWhere((vm) => vm.id == id);
if (index != -1) {
_mockVMs[index] = VirtualMachine(id: id, name: _mockVMs[index].name, state: 'running');
_log("VM $id restarted (Simulated).");
fetchVMs();
}
return;
}
try {
final result = await _runPython(['restart', id]);
if (result['status'] == 'success') {
_log("VM $id restarted.");
fetchVMs();
} else {
_log("Failed to restart VM: ${result['message']}");
}
} catch (e) {
_log("Error restarting VM: $e");
}
}
Future<void> modifyVM(String id, int cpus, int memoryMb) async {
_log("modifying hardware spec for $id...");
if (_useMock || kIsWeb) {
await Future.delayed(const Duration(milliseconds: 500));
_log("VM $id spec updated: ${cpus}vCPU, ${memoryMb}MB RAM (Simulated).");
return;
}
try {
final result = await _runPython([
'modify',
id,
'--cpus',
cpus.toString(),
'--memory',
memoryMb.toString()
]);
if (result['status'] == 'success') {
_log("VM $id spec updated: ${cpus}vCPU, ${memoryMb}MB RAM.");
} else {
_log("Failed to update spec: ${result['message']}");
}
} catch (e) {
_log("Error modifying VM: $e");
}
}
Future<void> updateStats() async {
if (_useMock || kIsWeb) {
// Mock stats
_stats = SystemStats(
cpu: (Random().nextInt(30) + 10),
ram: (Random().nextInt(40) + 20),
netIn: Random().nextInt(1000),
netOut: Random().nextInt(500),
);
notifyListeners();
return;
}
try {
final result = await _runPython(['stats']);
_stats = SystemStats.fromJson(result);
notifyListeners();
} catch (e) {
// Switch to mock if stats fail repeatedly?
// For now, silent fail or maybe switch if we haven't already
if (!_useMock) {
// Don't auto switch just on stats fail to avoid jitter, but maybe safe to do so
}
}
}
Future<Map<String, dynamic>> _runPython(List<String> args) async {
// If on web, throw immediately to trigger mock fallback
if (kIsWeb) throw Exception("Web does not support Process.run");
try {
final processResult = await Process.run(
'python',
[_scriptPath, ...args],
runInShell: true,
);
if (processResult.exitCode != 0) {
throw Exception("Python Error: ${processResult.stderr}");
}
final output = processResult.stdout.toString().trim();
return jsonDecode(output);
} catch (e) {
throw Exception("Process Error: $e");
}
}
void _log(String message) {
String timestamp = DateTime.now().toIso8601String().substring(11, 19);
_logs.add("[$timestamp] $message");
if (_logs.length > 50) _logs.removeAt(0);
notifyListeners();
}
}
// -----------------------------------------------------------------------------
// WIDGETS
// -----------------------------------------------------------------------------
class MatrixRainEffect extends StatefulWidget {
final Widget child;
const MatrixRainEffect({super.key, required this.child});
@override
State<MatrixRainEffect> createState() => _MatrixRainEffectState();
}
class _MatrixRainEffectState extends State<MatrixRainEffect>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
List<double> _drops = [];
final Random _random = Random();
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this, duration: const Duration(seconds: 1))
..repeat();
}
void _initDrops(double width) {
int cols = (width / 15).floor();
if (_drops.length != cols) {
_drops = List.generate(cols, (i) => _random.nextDouble() * -1000);
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
LayoutBuilder(
builder: (context, constraints) {
_initDrops(constraints.maxWidth);
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
painter: _MatrixRainPainter(_drops, _random),
size: Size(constraints.maxWidth, constraints.maxHeight),
);
},
);
},
),
widget.child,
],
);
}
}
class _MatrixRainPainter extends CustomPainter {
final List<double> drops;
final Random random;
_MatrixRainPainter(this.drops, this.random);
@override
void paint(Canvas canvas, Size size) {
final textPainter = TextPainter(textDirection: TextDirection.ltr);
for (int i = 0; i < drops.length; i++) {
drops[i] += random.nextInt(10) + 5;
if (drops[i] > size.height) {
drops[i] = random.nextDouble() * -100;
}
final char = String.fromCharCode(0x30A0 + random.nextInt(96));
textPainter.text = TextSpan(
text: char,
style: TextStyle(
color: AppColors.primary.withOpacity(0.3),
fontSize: 14,
fontFamily: 'monospace'));
textPainter.layout();
textPainter.paint(canvas, Offset(i * 15.0, drops[i]));
for (int j = 1; j < 5; j++) {
textPainter.text = TextSpan(
text: String.fromCharCode(0x30A0 + random.nextInt(96)),
style: TextStyle(
color: AppColors.primary.withOpacity(0.3 - (j * 0.05)),
fontSize: 14,
));
textPainter.layout();
textPainter.paint(canvas, Offset(i * 15.0, drops[i] - (j * 15)));
}
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
class GlassContainer extends StatelessWidget {
final Widget child;
final double opacity;
final EdgeInsets padding;
final Color? borderColor;
const GlassContainer({
super.key,
required this.child,
this.opacity = 0.1,
this.padding = const EdgeInsets.all(16),
this.borderColor,
});
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(16),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
padding: padding,
decoration: BoxDecoration(
color: AppColors.surface.withOpacity(opacity),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: borderColor ?? AppColors.primary.withOpacity(0.3),
width: 1,
),
boxShadow: [
BoxShadow(
color: (borderColor ?? AppColors.primary).withOpacity(0.1),
blurRadius: 10,
spreadRadius: 1,
)
]),
child: child,
),
),
);
}
}
class CyberButton extends StatefulWidget {
final String text;
final VoidCallback onPressed;
final Color? color;
final IconData? icon;
const CyberButton(
{super.key,
required this.text,
required this.onPressed,
this.color,
this.icon});
@override
State<CyberButton> createState() => _CyberButtonState();
}
class _CyberButtonState extends State<CyberButton> {
bool _isHovered = false;
@override
Widget build(BuildContext context) {
final color = widget.color ?? AppColors.primary;
return MouseRegion(
onEnter: (_) => setState(() => _isHovered = true),
onExit: (_) => setState(() => _isHovered = false),
child: GestureDetector(
onTap: widget.onPressed,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
color: _isHovered ? color.withOpacity(0.2) : Colors.transparent,
border: Border.all(color: color, width: 2),
borderRadius: BorderRadius.circular(4),
boxShadow: _isHovered
? [
BoxShadow(
color: color.withOpacity(0.5),
blurRadius: 16,
spreadRadius: 2)
]
: [],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.icon != null) ...[
Icon(widget.icon, color: color, size: 20),
const SizedBox(width: 8),
],
Text(
widget.text.toUpperCase(),
style: TextStyle(
color: color,
fontWeight: FontWeight.bold,
letterSpacing: 2,
),
),
],
),
),
),
);
}
}
class TerminalPanel extends StatelessWidget {
final List<String> logs;
const TerminalPanel({super.key, required this.logs});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.terminal,
border: Border(
top: BorderSide(
color: AppColors.primary.withOpacity(0.5), width: 2)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("TERMINAL_OUTPUT //",
style: TextStyle(
color: AppColors.primary, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Expanded(
child: ListView.builder(
itemCount: logs.length,
reverse: true,
itemBuilder: (context, index) {
final log = logs[logs.length - 1 - index];
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text("> $log",
style: const TextStyle(
color: Colors.white70,
fontFamily: 'Courier',
fontSize: 12)),
);
},
),
),
],
),
);
}
}
class ResourceGraph extends StatelessWidget {
final String label;
final int value;
final Color color;
final List<FlSpot> history;
const ResourceGraph(
{super.key,
required this.label,
required this.value,
required this.color,
required this.history});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label,
style: TextStyle(color: color, fontWeight: FontWeight.bold)),
Text("$value%",
style: TextStyle(
color: color, fontWeight: FontWeight.bold, fontSize: 18)),
],
),
const SizedBox(height: 8),
SizedBox(
height: 100,
child: LineChart(
LineChartData(
gridData: FlGridData(show: false),
titlesData: FlTitlesData(show: false),
borderData: FlBorderData(show: false),
lineBarsData: [
LineChartBarData(
spots: history,
isCurved: true,
color: color,
barWidth: 2,
isStrokeCapRound: true,
dotData: FlDotData(show: false),
belowBarData:
BarAreaData(show: true, color: color.withOpacity(0.1)),
),
],
minX: 0,
maxX: 20,
minY: 0,
maxY: 100,
),
),
),
],
);
}
}
// -----------------------------------------------------------------------------
// SCREENS
// -----------------------------------------------------------------------------
class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key});
@override
State<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
// History for graphs
final List<FlSpot> cpuHistory = [];
final List<FlSpot> ramHistory = [];
Timer? _timer;
@override
void initState() {
super.initState();
// Start fetching
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<VBoxService>().fetchVMs();
});
// Update stats loop
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
final service = context.read<VBoxService>();
service.updateStats();
setState(() {
if (cpuHistory.length > 20) {
cpuHistory.removeAt(0);
ramHistory.removeAt(0);
}
// Shift x values
for (int i = 0; i < cpuHistory.length; i++) {
cpuHistory[i] = FlSpot(cpuHistory[i].x - 1, cpuHistory[i].y);
ramHistory[i] = FlSpot(ramHistory[i].x - 1, ramHistory[i].y);
}
cpuHistory.add(FlSpot(20, service.stats.cpu.toDouble()));
ramHistory.add(FlSpot(20, service.stats.ram.toDouble()));
});
});
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
Widget _buildSidebar(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (MediaQuery.of(context).size.width < 900) ...[
const SizedBox(height: 50), // Spacing for drawer
],
const Text("MINI_VM_CONTROLLER",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: AppColors.primary)),
const SizedBox(height: 40),
const Text("SYSTEM_RESOURCES",
style: TextStyle(color: Colors.grey, fontSize: 12)),
const SizedBox(height: 20),
ResourceGraph(
label: "CPU_CORE_0",
value: context.watch<VBoxService>().stats.cpu,
color: AppColors.primary,
history: cpuHistory),
const SizedBox(height: 20),
ResourceGraph(
label: "RAM_ALLOCATION",
value: context.watch<VBoxService>().stats.ram,
color: AppColors.accent,
history: ramHistory),
const Spacer(),
GlassContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text("STATUS: ONLINE",
style: TextStyle(color: AppColors.primary)),
SizedBox(height: 5),
Text("SECURE CONNECTION",
style: TextStyle(color: AppColors.primary)),
SizedBox(height: 5),
Text("ENCRYPTION: AES-256",
style: TextStyle(color: AppColors.primary)),
],
),
)
],
);
}
Widget _buildMainContent(BuildContext context, bool isMobile) {
return Column(
children: [
// Header
Container(
height: 80,
padding: const EdgeInsets.symmetric(horizontal: 30),
alignment: Alignment.centerLeft,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
isMobile ? "DASHBOARD" : "DASHBOARD // VIRTUAL_MACHINES",
style: const TextStyle(fontSize: 24, letterSpacing: 2),
),
IconButton(
onPressed: () => context.read<VBoxService>().fetchVMs(),
icon: const Icon(Icons.refresh))
],
),
),
// VM Grid
Expanded(
child: Consumer<VBoxService>(
builder: (context, service, _) {
if (service.isLoading && service.vms.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
if (service.vms.isEmpty) {
return Center(
child: Text("NO VMs DETECTED",
style: TextStyle(
color: AppColors.error.withOpacity(0.7),
fontSize: 20)));
}
return GridView.builder(
padding: const EdgeInsets.all(30),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isMobile ? 1 : 3,
childAspectRatio: isMobile ? 1.5 : 1.1,
crossAxisSpacing: 20,
mainAxisSpacing: 20,
),
itemCount: service.vms.length,
itemBuilder: (context, index) {
final vm = service.vms[index];
return VMCard(vm: vm);
},
);
},
),
),
// Terminal
SizedBox(
height: 200,
child: Consumer<VBoxService>(
builder: (context, service, _) => TerminalPanel(logs: service.logs),
),
),
],
);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final bool isMobile = constraints.maxWidth < 900;
return Scaffold(
extendBodyBehindAppBar: true,
appBar: isMobile
? AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: Builder(
builder: (context) => IconButton(
icon: const Icon(Icons.menu, color: AppColors.primary),
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
title: const Text("MINI_VM",
style: TextStyle(color: AppColors.primary)),
centerTitle: true,
)
: null,
drawer: isMobile
? Drawer(
backgroundColor: Colors.black.withOpacity(0.9),
child: Padding(
padding: const EdgeInsets.all(20),
child: _buildSidebar(context),
),
)
: null,
body: Stack(
children: [
// Background
const MatrixRainEffect(child: SizedBox.expand()),
// Gradient Overlay
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppColors.background.withOpacity(0.9),
AppColors.background.withOpacity(0.7),
],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
),
),
// Responsive Layout
isMobile
? Column(
children: [
const SizedBox(
height: kToolbarHeight + 20),
Expanded(child: _buildMainContent(context, true)),
],
)
: Row(
children: [
Container(
width: 280,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
border: Border(
right: BorderSide(
color: AppColors.primary.withOpacity(0.2))),
color: Colors.black.withOpacity(0.5),
),
child: _buildSidebar(context),
),
Expanded(child: _buildMainContent(context, false)),
],
),
],
),
);
},
);
}
}
class VMCard extends StatelessWidget {
final VirtualMachine vm;
const VMCard({super.key, required this.vm});
@override
Widget build(BuildContext context) {
Color statusColor = AppColors.error;
if (vm.state.toLowerCase().contains("running"))
statusColor = AppColors.primary;
if (vm.state.toLowerCase().contains("saved")) statusColor = Colors.orange;
return GlassContainer(
borderColor: statusColor.withOpacity(0.5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(Icons.computer, color: statusColor, size: 40),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.2),
borderRadius: BorderRadius.circular(4),
border: Border.all(color: statusColor.withOpacity(0.5))),
child: Text(vm.state.toUpperCase(),
style: TextStyle(
color: statusColor,
fontSize: 10,
fontWeight: FontWeight.bold)),
)
],
),
Text(vm.name,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis),
Text(vm.id,
style: TextStyle(fontSize: 10, color: Colors.grey[500]),
maxLines: 1,
overflow: TextOverflow.ellipsis),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_ActionButton(
icon: Icons.play_arrow,
color: AppColors.primary,
onPressed: () => context.read<VBoxService>().startVM(vm.id)),
_ActionButton(
icon: Icons.stop,
color: AppColors.error,
onPressed: () => context.read<VBoxService>().stopVM(vm.id)),
_ActionButton(
icon: Icons.refresh,
color: Colors.orange,
onPressed: () =>
context.read<VBoxService>().restartVM(vm.id)),
_ActionButton(
icon: Icons.settings,
color: Colors.cyan,
onPressed: () => _showSettingsDialog(context, vm)),
],
)
],
),
);
}
void _showSettingsDialog(BuildContext context, VirtualMachine vm) {
showDialog(
context: context, builder: (context) => _ResourceSettingsDialog(vm: vm));
}
}
class _ResourceSettingsDialog extends StatefulWidget {
final VirtualMachine vm;
const _ResourceSettingsDialog({required this.vm});
@override
State<_ResourceSettingsDialog> createState() =>
_ResourceSettingsDialogState();
}
class _ResourceSettingsDialogState extends State<_ResourceSettingsDialog> {
int _selectedPreset = 1; // 0: Low, 1: Medium, 2: High
final List<Map<String, dynamic>> presets = [
{"name": "LOW", "cpu": 1, "ram": 2048},
{"name": "MEDIUM", "cpu": 2, "ram": 4096},
{"name": "HIGH", "cpu": 4, "ram": 8192},
];
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: AppColors.surface,
shape: RoundedRectangleBorder(
side: const BorderSide(color: AppColors.primary),
borderRadius: BorderRadius.circular(16)),
title: Text("MODIFY RESOURCES // ${widget.vm.name}",
style: const TextStyle(color: AppColors.primary)),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text("Select Performance Preset:",
style: TextStyle(color: Colors.white70)),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(presets.length, (index) {
final isSelected = _selectedPreset == index;
return GestureDetector(
onTap: () => setState(() => _selectedPreset = index),
child: Container(
padding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
decoration: BoxDecoration(
color: isSelected
? AppColors.primary.withOpacity(0.2)
: Colors.transparent,
border: Border.all(
color: isSelected ? AppColors.primary : Colors.grey),
borderRadius: BorderRadius.circular(8)),
child: Column(
children: [
Text(presets[index]["name"],
style: TextStyle(
color:
isSelected ? AppColors.primary : Colors.grey,
fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Text("${presets[index]["cpu"]} vCPU",
style: const TextStyle(
color: Colors.white70, fontSize: 12)),
Text("${presets[index]["ram"]} MB",
style: const TextStyle(
color: Colors.white70, fontSize: 12)),
],
),
),
);
}),
),
const SizedBox(height: 20),
const Text("Warning: VM must be stopped to apply changes.",
style: TextStyle(color: Colors.orange, fontSize: 10)),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("CANCEL", style: TextStyle(color: Colors.grey))),
CyberButton(
text: "APPLY PERMUTATIONS",
onPressed: () {
final p = presets[_selectedPreset];
context
.read<VBoxService>()
.modifyVM(widget.vm.id, p["cpu"], p["ram"]);
Navigator.pop(context);
})
],
);
}
}
class _ActionButton extends StatelessWidget {
final IconData icon;
final Color color;
final VoidCallback onPressed;
const _ActionButton(
{required this.icon, required this.color, required this.onPressed});
@override
Widget build(BuildContext context) {
return IconButton(
onPressed: onPressed,
icon: Icon(icon, color: color),
hoverColor: color.withOpacity(0.2),
tooltip: "Action",
);
}
}
// -----------------------------------------------------------------------------
// MAIN ENTRY POINT
// -----------------------------------------------------------------------------
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => VBoxService()),
],
child: const MiniVMControllerApp(),
),
);
}
class MiniVMControllerApp extends StatelessWidget {
const MiniVMControllerApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Mini VM Controller',
debugShowCheckedModeBanner: false,
theme: AppTheme.darkTheme,
home: const DashboardScreen(),
);
}
}
Comments
Post a Comment