mirror of
https://github.com/MeowLynxSea/vissh.git
synced 2025-07-09 19:44:34 +00:00
完成了SSH登录验证
This commit is contained in:
parent
04b1944f84
commit
b561a7f612
@ -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:
|
||||||
|
140
lib/main.dart
140
lib/main.dart
@ -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
127
lib/pages/login_page.dart
Normal 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('连接'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user