CH-J SM MobileSerververwaltung über SSH
Menü
Veröffentlichte Quellen

CH-J SM Mobile

Durchsuchen Sie Verzeichnisse und Dateien einer bestimmten Anwendungsausgabe.

Quellen als ZIP herunterladen
CH-J / SM Mobile 175 Dateien
sm-mobile-20261016T185235Z-8e93ecc1b59a Hinzugefügt: · Erstellt:
profile_io.dart Hinzugefügt: Datum nicht erfasst 5,3 KB · 159 Zeilen
Code Datei herunterladen
1import 'dart:typed_data';
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 '../services/profile_transfer.dart';
10import 'adaptive.dart';
12/// Imports desktop-compatible `chj-server-profiles` version 1 JSON after a
13/// preview and a duplicate policy. Credentials are never part of the file.
14Future<void> importProfiles(BuildContext context, AppController c) async {
15 final epoch = c.vault.generation;
16 final file = await c.withSystemDialog(
17 () =>
18 FilePicker.pickFile(type: FileType.custom, allowedExtensions: ['json']),
19 );
20 if (file == null ||
21 !context.mounted ||
22 epoch != c.vault.generation ||
23 !c.vault.unlocked) {
24 return;
25 }
26 final profiles = ProfileTransfer.decode(
27 await readBounded(file.readAsByteStream(), ProfileTransfer.maxBytes),
28 );
29 if (!context.mounted) return;
30 final policy = await Navigator.push<String>(
31 context,
32 MaterialPageRoute(
33 builder: (_) => ImportPreview(profiles, c.vault.profiles),
34 ),
35 );
36 if (policy == null || !c.vault.unlocked || epoch != c.vault.generation) {
37 return;
38 }
39 await c.vault.change((d) {
40 final list = d['profiles'] as List;
41 final seen = {
42 for (final p in list)
43 ServerProfile.fromJson(Map<String, dynamic>.from(p)).identity: p,
44 };
45 for (final p in profiles) {
46 final match = seen[p.identity];
47 if (match != null && policy == 'skipDuplicates') continue;
48 final next = ServerProfile(
49 id: match != null && policy == 'updateDuplicates' ? match['id'] : null,
50 name: p.name,
51 host: p.host,
52 username: p.username,
53 port: p.port,
54 );
55 if (match != null && policy == 'updateDuplicates') {
56 list[list.indexOf(match)] = next.toJson();
57 (d['secrets'] as Map).remove(next.id);
58 } else {
59 list.add(next.toJson());
60 }
61 seen[p.identity] = list.lastWhere((r) => r['id'] == next.id);
62 }
63 });
64 c.ssh.disconnect(clear: true);
65 c.changed();
68/// Exports profiles as desktop-compatible JSON, without credentials.
69Future<void> exportProfiles(AppController c) async {
70 if (c.vault.profiles.isEmpty) throw const Failure('invalidImport');
71 final bytes = Uint8List.fromList(ProfileTransfer.encode(c.vault.profiles));
72 await c.withSystemDialog(
73 () => FilePicker.saveFile(
74 fileName: 'chj-server-profiles.json',
75 bytes: bytes,
76 mimeType: 'application/json',
77 ),
78 );
81class ImportPreview extends StatefulWidget {
82 final List<ServerProfile> profiles, existing;
83 const ImportPreview(this.profiles, this.existing, {super.key});
84 @override
85 State<ImportPreview> createState() => _ImportPreviewState();
88class _ImportPreviewState extends State<ImportPreview> {
89 String policy = 'skipDuplicates';
90 @override
91 Widget build(BuildContext context) {
92 final seen = widget.existing.map((p) => p.identity).toSet();
93 final duplicates = widget.profiles.map((p) {
94 final found = !seen.add(p.identity);
95 return found;
96 }).toList();
97 return Scaffold(
98 appBar: AppBar(title: Text(context.tr('importPreview'))),
99 body: SafeArea(
100 child: MinHeight(
101 minHeight: 320,
102 child: Column(
103 children: [
104 Padding(
105 padding: const EdgeInsets.all(16),
106 child: Text(context.tr('importAdvice')),
107 ),
108 Padding(
109 padding: const EdgeInsets.symmetric(horizontal: 16),
110 child: DropdownButtonFormField<String>(
111 initialValue: policy,
112 isExpanded: true,
113 items:
114 ['skipDuplicates', 'updateDuplicates', 'createDuplicates']
115 .map(
116 (v) => DropdownMenuItem(
117 value: v,
118 child: Text(
119 context.tr(v),
120 overflow: TextOverflow.ellipsis,
121 ),
122 ),
123 )
124 .toList(),
125 onChanged: (v) => setState(() => policy = v!),
126 ),
127 ),
128 Expanded(
129 child: ListView.builder(
130 itemCount: widget.profiles.length,
131 itemBuilder: (ctx, i) {
132 final p = widget.profiles[i];
133 return ListTile(
134 title: Text(p.name),
135 subtitle: Text('${p.username}@${p.host}:${p.port}'),
136 trailing: duplicates[i]
137 ? Tooltip(
138 message: ctx.tr('duplicate'),
139 child: const Icon(Icons.copy),
140 )
141 : null,
142 );
143 },
144 ),
145 ),
146 Padding(
147 padding: const EdgeInsets.all(16),
148 child: FilledButton(
149 onPressed: () => Navigator.pop(context, policy),
150 child: Text(context.tr('importAction')),
151 ),
152 ),
153 ],
154 ),
155 ),
156 ),
157 );
158 }
SHA-2562ae0dbdeee1c944ea7134e7ec7a9559788eae3a5de132f8fdeb48e0ab2abfb22

CH-J Proprietary Software License 1.14 · Die Quellen werden unter der CH-J Proprietary Software License 1.14 bereitgestellt. Ihre Verfügbarkeit ändert die Lizenzbedingungen nicht und gewährt keine zusätzlichen Rechte.

SHA-256 des Archivs46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655
Dateien