CH-J SM Mobileupravljanje strežnikov prek SSH
Meni
Objavljen vir

CH-J SM Mobile

Brskajte po imenikih in datotekah za določeno izdajo programa.

Prenos vir ZIP
CH-J / SM Mobile 134 datoteke
sm-mobile-20261016T050549Z-3678f2a05fa9 Dodano: · Zgrajena:
Datoteke
profile_editor.dart Dodano: Datum, ki ni zabeležen 6,3 KB · 191 vrstice
Prenesi datoteko
1import 'dart:convert';
2import 'package:file_picker/file_picker.dart';
3import 'package:flutter/material.dart';
4import '../core/app_controller.dart';
5import '../core/failure.dart';
6import '../l10n/strings.dart';
7import '../models/server_profile.dart';
8import '../services/local_files.dart';
9import 'common.dart';
11class ProfileEditor extends StatefulWidget {
12 final AppController controller;
13 final ServerProfile? profile;
14 const ProfileEditor(this.controller, {this.profile, super.key});
15 @override
16 State<ProfileEditor> createState() => _ProfileEditorState();
19class _ProfileEditorState extends State<ProfileEditor> {
20 late final name = TextEditingController(text: widget.profile?.name);
21 late final host = TextEditingController(text: widget.profile?.host);
22 late final username = TextEditingController(text: widget.profile?.username);
23 late final port = TextEditingController(
24 text: '${widget.profile?.port ?? 22}',
25 );
26 final password = TextEditingController();
27 late AuthMethod auth = widget.profile?.auth ?? AuthMethod.password;
28 String? key;
29 bool savePassword = false, clearPassword = false, busy = false;
30 Future<void> chooseKey() async {
31 try {
32 final picked = await widget.controller.withSystemDialog(
33 () => FilePicker.pickFile(),
34 );
35 if (picked == null || !mounted || !widget.controller.vault.unlocked) {
36 return;
37 }
38 final text = utf8.decode(
39 await readBounded(picked.readAsByteStream(), 1024 * 1024),
40 );
41 if (!text.contains('PRIVATE KEY')) throw const Failure('invalidKey');
42 if (mounted) setState(() => key = text);
43 } catch (e) {
44 if (mounted) showFailure(context, e);
45 }
46 }
48 Future<void> save() async {
49 setState(() => busy = true);
50 try {
51 final c = widget.controller;
52 final profile = ServerProfile(
53 id: widget.profile?.id,
54 name: name.text,
55 host: host.text,
56 username: username.text,
57 port: int.tryParse(port.text) ?? 0,
58 auth: auth,
59 );
60 if (auth == AuthMethod.privateKey &&
61 key == null &&
62 c.vault.secret(profile.id, 'privateKey') == null) {
63 throw const Failure('invalidKey');
64 }
65 await c.vault.save(
66 profile,
67 password: auth == AuthMethod.privateKey || clearPassword
68 ? ''
69 : savePassword
70 ? password.text
71 : null,
72 privateKey: auth == AuthMethod.password ? '' : key,
73 );
74 password.clear();
75 key = null;
76 if (c.ssh.profile?.id == profile.id) c.ssh.disconnect(clear: true);
77 c.changed();
78 if (mounted) Navigator.pop(context);
79 } catch (e) {
80 if (mounted) showFailure(context, e);
81 } finally {
82 if (mounted) setState(() => busy = false);
83 }
84 }
86 @override
87 void dispose() {
88 for (final field in [name, host, username, port, password]) {
89 field.dispose();
90 }
91 key = null;
92 super.dispose();
93 }
95 @override
96 Widget build(BuildContext context) {
97 Widget field(
98 TextEditingController controller,
99 String label, {
100 TextInputType? type,
101 }) => Padding(
102 padding: const EdgeInsets.only(bottom: 16),
103 child: TextField(
104 controller: controller,
105 keyboardType: type,
106 autocorrect: false,
107 enableSuggestions: false,
108 decoration: InputDecoration(labelText: context.tr(label)),
109 ),
110 );
111 return Scaffold(
112 appBar: AppBar(
113 title: Text(
114 context.tr(widget.profile == null ? 'addServer' : 'editServer'),
115 ),
116 ),
117 body: SafeArea(
118 child: Center(
119 child: ConstrainedBox(
120 constraints: const BoxConstraints(maxWidth: 600),
121 child: ListView(
122 padding: const EdgeInsets.all(20),
123 children: [
124 field(name, 'name'),
125 field(host, 'host', type: TextInputType.url),
126 field(username, 'username'),
127 field(port, 'port', type: TextInputType.number),
128 DropdownButtonFormField<AuthMethod>(
129 initialValue: auth,
130 items: AuthMethod.values
131 .map(
132 (a) => DropdownMenuItem(
133 value: a,
134 child: Text(context.tr(a.name)),
135 ),
136 )
137 .toList(),
138 onChanged: (a) => setState(() => auth = a!),
139 ),
140 const SizedBox(height: 16),
141 if (auth == AuthMethod.privateKey) ...[
142 OutlinedButton.icon(
143 onPressed: chooseKey,
144 icon: const Icon(Icons.key),
145 label: Text(context.tr('chooseKey')),
146 ),
147 if (key != null ||
148 (widget.profile != null &&
149 widget.controller.vault.secret(
150 widget.profile!.id,
151 'privateKey',
152 ) !=
153 null))
154 Text(context.tr('keyStored')),
155 ] else ...[
156 CheckboxListTile(
157 value: savePassword,
158 onChanged: (v) => setState(() => savePassword = v!),
159 title: Text(context.tr('savePassword')),
160 ),
161 if (savePassword)
162 TextField(
163 controller: password,
164 obscureText: true,
165 autocorrect: false,
166 enableSuggestions: false,
167 decoration: InputDecoration(
168 labelText: context.tr('password'),
169 ),
170 ),
171 if (widget.profile != null)
172 CheckboxListTile(
173 value: clearPassword,
174 onChanged: (v) => setState(() => clearPassword = v!),
175 title: Text(context.tr('clearPassword')),
176 ),
177 ],
178 const SizedBox(height: 20),
179 FilledButton(
180 onPressed: busy ? null : save,
181 child: Text(context.tr('save')),
182 ),
183 if (busy) const LinearProgressIndicator(),
184 ],
185 ),
186 ),
187 ),
188 ),
189 );
190 }
SHA-2560f8a63df1421e14e9080f0a849b7b2f0523340f933eb9f732d518e5b9cff0292

CH-J Proprietary Software License 1.14 · Vir je na voljo pod CH-J Proprietary Software License 1.14. Njena razpoložljivost ne spreminja pogojev licence ali dodeljuje dodatnih pravic.

Arhiv SHA-256c486a807e4828998b9c57ab0f2e760f7980b31965fe118e89d3e1a5a2b65be65