完成了SSH登录验证

This commit is contained in:
梦凌汐 2025-07-09 13:28:27 +08:00
parent 04b1944f84
commit b561a7f612
4 changed files with 260 additions and 11 deletions

View File

@ -7,6 +7,9 @@
# The following line activates a set of recommended lints for Flutter apps, # The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices. # packages, and plugins designed to encourage good coding practices.
analyzer:
errors:
use_build_context_synchronously: ignore
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
linter: linter:

View File

@ -1,7 +1,9 @@
import 'package:dartssh2/dartssh2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'widgets/draggable_window.dart'; import 'widgets/draggable_window.dart';
import 'models/window_data.dart'; import 'models/window_data.dart';
import 'widgets/taskbar.dart'; import 'widgets/taskbar.dart';
import 'pages/login_page.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
@ -14,13 +16,15 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
home: const WindowManager(), home: const LoginPage(),
); );
} }
} }
class WindowManager extends StatefulWidget { class WindowManager extends StatefulWidget {
const WindowManager({super.key}); final SSHClient sshClient;
final String host;
const WindowManager({super.key, required this.sshClient, required this.host});
@override @override
State<WindowManager> createState() => _WindowManagerState(); State<WindowManager> createState() => _WindowManagerState();
@ -30,9 +34,34 @@ class _WindowManagerState extends State<WindowManager> {
final List<WindowData> _windows = []; final List<WindowData> _windows = [];
int _nextWindowId = 0; int _nextWindowId = 0;
bool _isVerified = false;
String _verificationMessage = '连接到服务器...';
String _verificationFailedMessage = '';
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_verifyConnection();
}
Future<void> _verifyConnection() async {
try {
await widget.sshClient.run('echo Vissh SSH Connection Verified');
if (!mounted) return;
setState(() {
_isVerified = true;
_setupInitialWindows();
});
} catch (e) {
if (!mounted) return;
setState(() {
_verificationMessage = '无法登录';
_verificationFailedMessage = '请检查服务器地址和凭据,然后重试。\n错误: $e';
});
}
}
void _setupInitialWindows() {
_addWindow( _addWindow(
'File Explorer', 'File Explorer',
const Offset(100, 100), const Offset(100, 100),
@ -44,8 +73,9 @@ class _WindowManagerState extends State<WindowManager> {
_addWindow( _addWindow(
'Terminal', 'Terminal',
const Offset(150, 150), const Offset(150, 150),
const Center( Center(
child: Text('Run your commands...', style: TextStyle(color: Colors.white)), child: Text('SSH connected to: ${widget.host}',
style: const TextStyle(color: Colors.white)),
), ),
Icons.web, Icons.web,
); );
@ -72,10 +102,10 @@ class _WindowManagerState extends State<WindowManager> {
final windowIndex = _windows.indexWhere((w) => w.id == id); final windowIndex = _windows.indexWhere((w) => w.id == id);
if (windowIndex != -1) { if (windowIndex != -1) {
final window = _windows[windowIndex]; final window = _windows[windowIndex];
if(window.isMinimized) { if (window.isMinimized) {
window.isMinimized = false; window.isMinimized = false;
} }
if (windowIndex != _windows.length - 1) { if (windowIndex != _windows.length - 1) {
final window = _windows.removeAt(windowIndex); final window = _windows.removeAt(windowIndex);
_windows.add(window); _windows.add(window);
@ -92,7 +122,7 @@ class _WindowManagerState extends State<WindowManager> {
} }
}); });
} }
void _onWindowIconTap(String id) { void _onWindowIconTap(String id) {
final windowIndex = _windows.indexWhere((w) => w.id == id); final windowIndex = _windows.indexWhere((w) => w.id == id);
if (windowIndex == -1) return; if (windowIndex == -1) return;
@ -132,7 +162,7 @@ class _WindowManagerState extends State<WindowManager> {
void _updateWindowSize(String id, Size size) { void _updateWindowSize(String id, Size size) {
final windowIndex = _windows.indexWhere((w) => w.id == id); final windowIndex = _windows.indexWhere((w) => w.id == id);
if (windowIndex != -1) { if (windowIndex != -1) {
setState(() { setState(() {
_windows[windowIndex].size = size; _windows[windowIndex].size = size;
}); });
} }
@ -149,15 +179,100 @@ class _WindowManagerState extends State<WindowManager> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (!_isVerified) {
// --- Win11 ---
bool hasError = _verificationFailedMessage.isNotEmpty;
return Scaffold(
backgroundColor: const Color(0xff0078D4), // Win11
body: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: NetworkImage('https://www.meowdream.cn/background.jpg'),
fit: BoxFit.cover,
),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 120),
hasError
? Icon(Icons.error_outline, color: Colors.redAccent, size: 48)
: SizedBox(
width: 48,
height: 48,
child: CircularProgressIndicator(
strokeWidth: 3.5,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white.withValues(alpha: 0.8)),
),
),
const SizedBox(height: 40),
Text(
_verificationMessage,
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.w300,
),
),
const SizedBox(height: 10),
if (hasError)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0),
child: Text(
_verificationFailedMessage,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white.withValues(alpha: 0.8),
fontSize: 14,
),
),
),
if (hasError)
SizedBox(height: 16),
if (hasError)
TextButton(
onPressed: () {
if (Navigator.canPop(context)) {
Navigator.of(context).pop();
}
},
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
backgroundColor: Colors.white.withValues(alpha: 0.1),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
),
child: const Text(
'返回',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
),
),
],
),
),
),
);
}
final sortedWindowsForTaskbar = List<WindowData>.from(_windows); final sortedWindowsForTaskbar = List<WindowData>.from(_windows);
sortedWindowsForTaskbar.sort((a, b) { sortedWindowsForTaskbar.sort((a, b) {
final aId = int.tryParse(a.id.split('_').last) ?? 0; final aId = int.tryParse(a.id.split('_').last) ?? 0;
final bId = int.tryParse(b.id.split('_').last) ?? 0; final bId = int.tryParse(b.id.split('_').last) ?? 0;
return aId.compareTo(bId); return aId.compareTo(bId);
}); });
final topMostIndex = _windows.lastIndexWhere((w) => !w.isMinimized); final topMostIndex = _windows.lastIndexWhere((w) => !w.isMinimized);
final activeWindowId = topMostIndex != -1 ? _windows[topMostIndex].id : null; final activeWindowId =
topMostIndex != -1 ? _windows[topMostIndex].id : null;
return Scaffold( return Scaffold(
backgroundColor: Colors.blueGrey[900], backgroundColor: Colors.blueGrey[900],
@ -209,7 +324,10 @@ class _WindowManagerState extends State<WindowManager> {
onPressed: () => _addWindow( onPressed: () => _addWindow(
'New Window', 'New Window',
const Offset(200, 200), const Offset(200, 200),
const Center(child: Text('This is the new window content area', style: TextStyle(color: Colors.white))), const Center(
child: Text('This is the new window content area',
style: TextStyle(color: Colors.white))
),
Icons.add_circle_outline, Icons.add_circle_outline,
), ),
tooltip: 'Add New Window', tooltip: 'Add New Window',

127
lib/pages/login_page.dart Normal file
View File

@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import 'package:dartssh2/dartssh2.dart';
import '../main.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final TextEditingController _hostController = TextEditingController();
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
bool _isLoading = false;
Future<void> _login() async {
setState(() {
_isLoading = true;
});
try {
final client = SSHClient(
await SSHSocket.connect(_hostController.text, 22),
username: _usernameController.text,
onPasswordRequest: () => _passwordController.text,
);
// ignore: use_build_context_synchronously
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
WindowManager(sshClient: client, host: _hostController.text)),
);
} catch (e) {
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Login Failed: $e')),
);
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: NetworkImage('https://www.meowdream.cn/background.jpg'),
fit: BoxFit.cover,
),
),
child: Center(
child: Container(
padding: const EdgeInsets.all(20.0),
width: 300,
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.7),
borderRadius: BorderRadius.circular(10),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('登录',
style: TextStyle(color: Colors.white, fontSize: 24)),
const SizedBox(height: 20),
TextField(
controller: _hostController,
style: const TextStyle(color: Colors.white),
decoration: const InputDecoration(
labelText: '地址',
labelStyle: TextStyle(color: Colors.white70),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white70)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white)),
),
),
const SizedBox(height: 10),
TextField(
controller: _usernameController,
style: const TextStyle(color: Colors.white),
decoration: const InputDecoration(
labelText: '用户名',
labelStyle: TextStyle(color: Colors.white70),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white70)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white)),
),
),
const SizedBox(height: 10),
TextField(
controller: _passwordController,
obscureText: true,
style: const TextStyle(color: Colors.white),
decoration: const InputDecoration(
labelText: '密码',
labelStyle: TextStyle(color: Colors.white70),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white70)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white)),
),
),
const SizedBox(height: 20),
_isLoading
? const CircularProgressIndicator()
: ElevatedButton.icon(
onPressed: _login,
icon: const Icon(Icons.login),
label: const Text('连接'),
),
],
),
),
),
),
);
}
}

View File

@ -11,6 +11,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
dartssh2: ^2.13.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: