neoagent 2.4.1-beta.44 → 2.4.1-beta.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +12 -0
- package/docs/configuration.md +6 -0
- package/flutter_app/lib/main_app_shell.dart +0 -2
- package/flutter_app/lib/main_chat.dart +1 -0
- package/flutter_app/lib/main_controller.dart +14 -2
- package/flutter_app/lib/main_models.dart +36 -0
- package/flutter_app/lib/main_navigation.dart +1 -9
- package/flutter_app/lib/main_settings.dart +4 -265
- package/flutter_app/lib/main_shared.dart +138 -3
- package/flutter_app/lib/main_unified.dart +5 -100
- package/lib/manager.js +32 -0
- package/package.json +1 -1
- package/runtime/paths.js +3 -1
- package/server/admin/access.js +198 -0
- package/server/admin/admin.css +268 -0
- package/server/admin/admin.js +348 -0
- package/server/admin/analytics.js +128 -0
- package/server/admin/index.html +993 -0
- package/server/admin/login.html +251 -0
- package/server/admin/logo.svg +43 -0
- package/server/admin/sql.js +134 -0
- package/server/admin/users.js +147 -0
- package/server/db/database.js +19 -0
- package/server/http/static.js +4 -1
- package/server/index.js +1 -1
- package/server/middleware/adminAuth.js +48 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +70538 -71095
- package/server/routes/admin.js +618 -0
- package/server/routes/settings.js +10 -2
- package/server/services/account/admin_two_factor.js +132 -0
- package/server/services/widgets/service.js +17 -1
- package/server/utils/logger.js +44 -6
package/.env.example
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
# Core runtime
|
|
3
3
|
########################################
|
|
4
4
|
|
|
5
|
+
# Admin dashboard credentials — set during `neoagent setup`.
|
|
6
|
+
# Access the dashboard at http://localhost:<PORT>/admin
|
|
7
|
+
# Run `neoagent admin` to display these credentials at any time.
|
|
8
|
+
ADMIN_USERNAME=admin
|
|
9
|
+
ADMIN_PASSWORD=
|
|
10
|
+
|
|
11
|
+
# Admin API key for programmatic access to /admin/api/* endpoints.
|
|
12
|
+
# Use: Authorization: Bearer <key>
|
|
13
|
+
# Generate or rotate via the admin dashboard → Access → Generate key.
|
|
14
|
+
# Leave blank to disable API key auth (dashboard session auth still works).
|
|
15
|
+
ADMIN_API_KEY=
|
|
16
|
+
|
|
5
17
|
PORT=3333
|
|
6
18
|
NODE_ENV=production
|
|
7
19
|
# Auto-generated on first start or setup if left blank.
|
package/docs/configuration.md
CHANGED
|
@@ -4,6 +4,12 @@ NeoAgent reads server config from `~/.neoagent/.env`. Run `neoagent setup` to ge
|
|
|
4
4
|
|
|
5
5
|
All AI provider credentials, OAuth client secrets, and deployment settings are server-side only — never sent to the client or exposed in the UI.
|
|
6
6
|
|
|
7
|
+
## Admin Dashboard
|
|
8
|
+
|
|
9
|
+
The admin dashboard at `/admin` provides a web UI for operator tasks including AI provider key management, server logs, and runtime updates. Credentials are generated during `neoagent setup` (or run `neoagent admin` to view them).
|
|
10
|
+
|
|
11
|
+
Navigate to **Providers** in the sidebar to set or rotate API keys without editing `.env` manually — changes take effect immediately without a server restart.
|
|
12
|
+
|
|
7
13
|
## Minimal Config
|
|
8
14
|
|
|
9
15
|
```dotenv
|
|
@@ -2176,8 +2176,6 @@ class _SectionBody extends StatelessWidget {
|
|
|
2176
2176
|
return SettingsWorkspacePanel(controller: controller);
|
|
2177
2177
|
case AppSection.accountSettings:
|
|
2178
2178
|
return SettingsWorkspacePanel(controller: controller);
|
|
2179
|
-
case AppSection.logs:
|
|
2180
|
-
return RunsAndLogsPanel(controller: controller);
|
|
2181
2179
|
case AppSection.skills:
|
|
2182
2180
|
return ToolsPanel(controller: controller);
|
|
2183
2181
|
case AppSection.agents:
|
|
@@ -458,6 +458,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
458
458
|
required String role,
|
|
459
459
|
required String platform,
|
|
460
460
|
bool transient = false,
|
|
461
|
+
Map<String, dynamic> metadata = const <String, dynamic>{},
|
|
461
462
|
}) {
|
|
462
463
|
final trimmed = content.trim();
|
|
463
464
|
if (trimmed.isEmpty) {
|
|
@@ -468,7 +469,8 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
468
469
|
if (previous != null &&
|
|
469
470
|
previous.role == role &&
|
|
470
471
|
previous.platform == platform &&
|
|
471
|
-
previous.content.trim() == trimmed
|
|
472
|
+
previous.content.trim() == trimmed &&
|
|
473
|
+
metadata.isEmpty) {
|
|
472
474
|
return;
|
|
473
475
|
}
|
|
474
476
|
|
|
@@ -481,6 +483,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
481
483
|
platform: platform,
|
|
482
484
|
createdAt: DateTime.now(),
|
|
483
485
|
transient: transient,
|
|
486
|
+
metadata: metadata,
|
|
484
487
|
),
|
|
485
488
|
];
|
|
486
489
|
}
|
|
@@ -4633,12 +4636,14 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
4633
4636
|
String content, {
|
|
4634
4637
|
required String platform,
|
|
4635
4638
|
bool transient = false,
|
|
4639
|
+
Map<String, dynamic> metadata = const <String, dynamic>{},
|
|
4636
4640
|
}) {
|
|
4637
4641
|
_appendChatMessage(
|
|
4638
4642
|
content,
|
|
4639
4643
|
role: 'assistant',
|
|
4640
4644
|
platform: platform,
|
|
4641
4645
|
transient: transient,
|
|
4646
|
+
metadata: metadata,
|
|
4642
4647
|
);
|
|
4643
4648
|
}
|
|
4644
4649
|
|
|
@@ -7418,7 +7423,14 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
7418
7423
|
}
|
|
7419
7424
|
final content = payload['content']?.toString().trim() ?? '';
|
|
7420
7425
|
if (content.isNotEmpty) {
|
|
7421
|
-
|
|
7426
|
+
final schema = payload['schema'];
|
|
7427
|
+
_appendAssistantChatMessage(
|
|
7428
|
+
content,
|
|
7429
|
+
platform: 'web',
|
|
7430
|
+
metadata: schema != null
|
|
7431
|
+
? <String, dynamic>{'schema': schema}
|
|
7432
|
+
: const <String, dynamic>{},
|
|
7433
|
+
);
|
|
7422
7434
|
}
|
|
7423
7435
|
streamingAssistant = '';
|
|
7424
7436
|
isSendingMessage = false;
|
|
@@ -1888,6 +1888,27 @@ class ChatEntry {
|
|
|
1888
1888
|
}
|
|
1889
1889
|
return null;
|
|
1890
1890
|
}
|
|
1891
|
+
|
|
1892
|
+
ChatRichPayload? get richPayload {
|
|
1893
|
+
final schema = metadata['schema'];
|
|
1894
|
+
if (schema is! Map) return null;
|
|
1895
|
+
final type = schema['type']?.toString() ?? '';
|
|
1896
|
+
if (type != 'quick_reply' && type != 'list_picker') return null;
|
|
1897
|
+
final optList = schema['options'];
|
|
1898
|
+
if (optList is! List) return null;
|
|
1899
|
+
final options = optList
|
|
1900
|
+
.whereType<Map>()
|
|
1901
|
+
.map(
|
|
1902
|
+
(item) => ChatPayloadOption(
|
|
1903
|
+
label: item['label']?.toString() ?? '',
|
|
1904
|
+
value: item['value']?.toString() ?? '',
|
|
1905
|
+
),
|
|
1906
|
+
)
|
|
1907
|
+
.where((o) => o.label.isNotEmpty)
|
|
1908
|
+
.toList(growable: false);
|
|
1909
|
+
if (options.isEmpty) return null;
|
|
1910
|
+
return ChatRichPayload(type: type, options: options);
|
|
1911
|
+
}
|
|
1891
1912
|
}
|
|
1892
1913
|
|
|
1893
1914
|
class SharedChatAttachment {
|
|
@@ -4041,3 +4062,18 @@ class ToolEventItem {
|
|
|
4041
4062
|
|
|
4042
4063
|
String get compactSummary => _condenseRunText(summary, maxLength: 120);
|
|
4043
4064
|
}
|
|
4065
|
+
|
|
4066
|
+
class ChatPayloadOption {
|
|
4067
|
+
const ChatPayloadOption({required this.label, required this.value});
|
|
4068
|
+
|
|
4069
|
+
final String label;
|
|
4070
|
+
final String value;
|
|
4071
|
+
}
|
|
4072
|
+
|
|
4073
|
+
class ChatRichPayload {
|
|
4074
|
+
const ChatRichPayload({required this.type, required this.options});
|
|
4075
|
+
|
|
4076
|
+
final String type;
|
|
4077
|
+
final List<ChatPayloadOption> options;
|
|
4078
|
+
}
|
|
4079
|
+
|
|
@@ -21,7 +21,6 @@ enum AppSection {
|
|
|
21
21
|
runs,
|
|
22
22
|
settings,
|
|
23
23
|
accountSettings,
|
|
24
|
-
logs,
|
|
25
24
|
skills,
|
|
26
25
|
agents,
|
|
27
26
|
integrations,
|
|
@@ -76,13 +75,11 @@ extension AppSectionX on AppSection {
|
|
|
76
75
|
case AppSection.messaging:
|
|
77
76
|
return 'Messaging';
|
|
78
77
|
case AppSection.runs:
|
|
79
|
-
return 'Runs
|
|
78
|
+
return 'Runs';
|
|
80
79
|
case AppSection.settings:
|
|
81
80
|
return 'Settings';
|
|
82
81
|
case AppSection.accountSettings:
|
|
83
82
|
return 'Account settings';
|
|
84
|
-
case AppSection.logs:
|
|
85
|
-
return 'Logs';
|
|
86
83
|
case AppSection.skills:
|
|
87
84
|
return 'Skills';
|
|
88
85
|
case AppSection.agents:
|
|
@@ -120,8 +117,6 @@ extension AppSectionX on AppSection {
|
|
|
120
117
|
return Icons.tune;
|
|
121
118
|
case AppSection.accountSettings:
|
|
122
119
|
return Icons.manage_accounts_outlined;
|
|
123
|
-
case AppSection.logs:
|
|
124
|
-
return Icons.article_outlined;
|
|
125
120
|
case AppSection.skills:
|
|
126
121
|
return Icons.extension_outlined;
|
|
127
122
|
case AppSection.agents:
|
|
@@ -158,7 +153,6 @@ extension AppSectionX on AppSection {
|
|
|
158
153
|
case AppSection.health:
|
|
159
154
|
return SidebarGroup.automation;
|
|
160
155
|
case AppSection.runs:
|
|
161
|
-
case AppSection.logs:
|
|
162
156
|
case AppSection.settings:
|
|
163
157
|
case AppSection.accountSettings:
|
|
164
158
|
case AppSection.messaging:
|
|
@@ -169,8 +163,6 @@ extension AppSectionX on AppSection {
|
|
|
169
163
|
|
|
170
164
|
AppSection get canonicalSection {
|
|
171
165
|
switch (this) {
|
|
172
|
-
case AppSection.logs:
|
|
173
|
-
return AppSection.runs;
|
|
174
166
|
case AppSection.skills:
|
|
175
167
|
case AppSection.mcp:
|
|
176
168
|
return AppSection.integrations;
|
|
@@ -139,15 +139,6 @@ const _desktopSettingsSection = _SettingsSection('desktop', <String>[
|
|
|
139
139
|
'input',
|
|
140
140
|
], requiresDesktop: true);
|
|
141
141
|
|
|
142
|
-
const _updatesSettingsSection = _SettingsSection('updates', <String>[
|
|
143
|
-
'updates',
|
|
144
|
-
'release',
|
|
145
|
-
'channel',
|
|
146
|
-
'version',
|
|
147
|
-
'self update',
|
|
148
|
-
'upgrade',
|
|
149
|
-
]);
|
|
150
|
-
|
|
151
142
|
const _diagnosticsSettingsSection = _SettingsSection('diagnostics', <String>[
|
|
152
143
|
'diagnostics',
|
|
153
144
|
'logs',
|
|
@@ -163,7 +154,6 @@ const List<_SettingsSection> _settingsSearchSections = <_SettingsSection>[
|
|
|
163
154
|
_modelsSettingsSection,
|
|
164
155
|
_voiceRecordingSettingsSection,
|
|
165
156
|
_desktopSettingsSection,
|
|
166
|
-
_updatesSettingsSection,
|
|
167
157
|
_diagnosticsSettingsSection,
|
|
168
158
|
];
|
|
169
159
|
|
|
@@ -328,7 +318,7 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
328
318
|
_PageTitle(
|
|
329
319
|
title: 'Settings',
|
|
330
320
|
subtitle:
|
|
331
|
-
'Workspace, models, recording,
|
|
321
|
+
'Workspace, models, recording, and diagnostics controls.',
|
|
332
322
|
trailing: _settingsSaveButton(controller),
|
|
333
323
|
)
|
|
334
324
|
else
|
|
@@ -348,7 +338,7 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
348
338
|
onChanged: (_) => setState(() {}),
|
|
349
339
|
decoration: InputDecoration(
|
|
350
340
|
labelText: 'Search settings',
|
|
351
|
-
hintText: 'Models, browser, voice,
|
|
341
|
+
hintText: 'Models, browser, voice, diagnostics...',
|
|
352
342
|
prefixIcon: const Icon(Icons.search),
|
|
353
343
|
suffixIcon: searchQuery.isEmpty
|
|
354
344
|
? null
|
|
@@ -409,13 +399,6 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
409
399
|
_buildDesktopSection(controller),
|
|
410
400
|
const SizedBox(height: 16),
|
|
411
401
|
],
|
|
412
|
-
if (_matchesSettingsSection(
|
|
413
|
-
searchQuery,
|
|
414
|
-
_updatesSettingsSection,
|
|
415
|
-
)) ...<Widget>[
|
|
416
|
-
_buildUpdatesSection(controller),
|
|
417
|
-
const SizedBox(height: 16),
|
|
418
|
-
],
|
|
419
402
|
if (_matchesSettingsSection(
|
|
420
403
|
searchQuery,
|
|
421
404
|
_diagnosticsSettingsSection,
|
|
@@ -424,7 +407,7 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
424
407
|
const _EmptyCard(
|
|
425
408
|
title: 'No matching settings',
|
|
426
409
|
subtitle:
|
|
427
|
-
'Try a broader search like models, browser,
|
|
410
|
+
'Try a broader search like models, browser, or voice.',
|
|
428
411
|
),
|
|
429
412
|
],
|
|
430
413
|
],
|
|
@@ -534,7 +517,7 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
534
517
|
const _SectionTitle('Overview'),
|
|
535
518
|
const SizedBox(height: 10),
|
|
536
519
|
Text(
|
|
537
|
-
'Configure workspace behavior,
|
|
520
|
+
'Configure workspace behavior, models, and recording defaults.',
|
|
538
521
|
style: TextStyle(color: _textSecondary, height: 1.45),
|
|
539
522
|
),
|
|
540
523
|
const SizedBox(height: 14),
|
|
@@ -1523,250 +1506,6 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
1523
1506
|
);
|
|
1524
1507
|
}
|
|
1525
1508
|
|
|
1526
|
-
Widget _buildUpdatesSection(NeoAgentController controller) {
|
|
1527
|
-
return Card(
|
|
1528
|
-
child: Padding(
|
|
1529
|
-
padding: const EdgeInsets.all(20),
|
|
1530
|
-
child: Column(
|
|
1531
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1532
|
-
children: <Widget>[
|
|
1533
|
-
const _SectionTitle('Updates'),
|
|
1534
|
-
const SizedBox(height: 10),
|
|
1535
|
-
Text(
|
|
1536
|
-
'Client and runtime update controls live here.',
|
|
1537
|
-
style: TextStyle(color: _textSecondary, height: 1.45),
|
|
1538
|
-
),
|
|
1539
|
-
if (!kIsWeb) ...<Widget>[
|
|
1540
|
-
const SizedBox(height: 16),
|
|
1541
|
-
LayoutBuilder(
|
|
1542
|
-
builder: (context, constraints) {
|
|
1543
|
-
final compact = constraints.maxWidth < 720;
|
|
1544
|
-
final checkButton = FilledButton.icon(
|
|
1545
|
-
onPressed:
|
|
1546
|
-
controller.isCheckingAppUpdate ||
|
|
1547
|
-
!controller.appUpdaterConfigured
|
|
1548
|
-
? null
|
|
1549
|
-
: () => controller.checkForAppUpdates(),
|
|
1550
|
-
style: FilledButton.styleFrom(backgroundColor: _accent),
|
|
1551
|
-
icon: controller.isCheckingAppUpdate
|
|
1552
|
-
? _inlineProgressIndicator()
|
|
1553
|
-
: const Icon(Icons.sync),
|
|
1554
|
-
label: Text(
|
|
1555
|
-
controller.isCheckingAppUpdate
|
|
1556
|
-
? 'Checking...'
|
|
1557
|
-
: 'Check now',
|
|
1558
|
-
),
|
|
1559
|
-
);
|
|
1560
|
-
final appHeading = Text(
|
|
1561
|
-
'Client App',
|
|
1562
|
-
style: TextStyle(
|
|
1563
|
-
fontWeight: FontWeight.w700,
|
|
1564
|
-
color: _textPrimary,
|
|
1565
|
-
),
|
|
1566
|
-
);
|
|
1567
|
-
if (compact) {
|
|
1568
|
-
return Column(
|
|
1569
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1570
|
-
children: <Widget>[
|
|
1571
|
-
appHeading,
|
|
1572
|
-
const SizedBox(height: 10),
|
|
1573
|
-
checkButton,
|
|
1574
|
-
],
|
|
1575
|
-
);
|
|
1576
|
-
}
|
|
1577
|
-
return Row(
|
|
1578
|
-
children: <Widget>[
|
|
1579
|
-
Expanded(child: appHeading),
|
|
1580
|
-
checkButton,
|
|
1581
|
-
],
|
|
1582
|
-
);
|
|
1583
|
-
},
|
|
1584
|
-
),
|
|
1585
|
-
const SizedBox(height: 12),
|
|
1586
|
-
if (!controller.appUpdaterConfigured)
|
|
1587
|
-
Text(
|
|
1588
|
-
'Client app updates are not configured for this build.',
|
|
1589
|
-
style: TextStyle(color: _textSecondary, height: 1.5),
|
|
1590
|
-
),
|
|
1591
|
-
],
|
|
1592
|
-
const Divider(height: 32),
|
|
1593
|
-
if (controller.updateStatus.allowSelfUpdate) ...<Widget>[
|
|
1594
|
-
LayoutBuilder(
|
|
1595
|
-
builder: (context, constraints) {
|
|
1596
|
-
final compact = constraints.maxWidth < 780;
|
|
1597
|
-
final channelPicker = DropdownButtonFormField<String>(
|
|
1598
|
-
initialValue: controller.updateStatus.releaseChannel,
|
|
1599
|
-
decoration: const InputDecoration(
|
|
1600
|
-
labelText: 'Runtime release channel',
|
|
1601
|
-
),
|
|
1602
|
-
items: const <DropdownMenuItem<String>>[
|
|
1603
|
-
DropdownMenuItem<String>(
|
|
1604
|
-
value: 'stable',
|
|
1605
|
-
child: Text('Stable'),
|
|
1606
|
-
),
|
|
1607
|
-
DropdownMenuItem<String>(
|
|
1608
|
-
value: 'beta',
|
|
1609
|
-
child: Text('Beta'),
|
|
1610
|
-
),
|
|
1611
|
-
],
|
|
1612
|
-
onChanged:
|
|
1613
|
-
controller.isSavingReleaseChannel ||
|
|
1614
|
-
controller.isTriggeringUpdate ||
|
|
1615
|
-
controller.updateStatus.state == 'running'
|
|
1616
|
-
? null
|
|
1617
|
-
: (value) {
|
|
1618
|
-
if (value != null) {
|
|
1619
|
-
unawaited(controller.setReleaseChannel(value));
|
|
1620
|
-
}
|
|
1621
|
-
},
|
|
1622
|
-
);
|
|
1623
|
-
|
|
1624
|
-
final channelHelper = Text(
|
|
1625
|
-
controller.updateStatus.releaseChannel == 'beta'
|
|
1626
|
-
? 'Beta follows preview releases.'
|
|
1627
|
-
: 'Stable follows production releases.',
|
|
1628
|
-
style: TextStyle(color: _textSecondary),
|
|
1629
|
-
);
|
|
1630
|
-
|
|
1631
|
-
if (compact) {
|
|
1632
|
-
return Column(
|
|
1633
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1634
|
-
children: <Widget>[
|
|
1635
|
-
channelPicker,
|
|
1636
|
-
const SizedBox(height: 8),
|
|
1637
|
-
channelHelper,
|
|
1638
|
-
const SizedBox(height: 16),
|
|
1639
|
-
],
|
|
1640
|
-
);
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
return Padding(
|
|
1644
|
-
padding: const EdgeInsets.only(bottom: 16),
|
|
1645
|
-
child: Row(
|
|
1646
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1647
|
-
children: <Widget>[
|
|
1648
|
-
Expanded(child: channelPicker),
|
|
1649
|
-
const SizedBox(width: 12),
|
|
1650
|
-
Expanded(child: channelHelper),
|
|
1651
|
-
],
|
|
1652
|
-
),
|
|
1653
|
-
);
|
|
1654
|
-
},
|
|
1655
|
-
),
|
|
1656
|
-
LayoutBuilder(
|
|
1657
|
-
builder: (context, constraints) {
|
|
1658
|
-
final compact = constraints.maxWidth < 780;
|
|
1659
|
-
final runtimeTitle = Text(
|
|
1660
|
-
'Runtime',
|
|
1661
|
-
style: TextStyle(
|
|
1662
|
-
fontWeight: FontWeight.w700,
|
|
1663
|
-
color: _textPrimary,
|
|
1664
|
-
),
|
|
1665
|
-
);
|
|
1666
|
-
final updateButton = FilledButton.icon(
|
|
1667
|
-
onPressed:
|
|
1668
|
-
controller.isSavingReleaseChannel ||
|
|
1669
|
-
controller.isTriggeringUpdate ||
|
|
1670
|
-
controller.updateStatus.state == 'running'
|
|
1671
|
-
? null
|
|
1672
|
-
: controller.triggerUpdate,
|
|
1673
|
-
style: FilledButton.styleFrom(backgroundColor: _accent),
|
|
1674
|
-
icon: controller.isTriggeringUpdate
|
|
1675
|
-
? const SizedBox.square(
|
|
1676
|
-
dimension: 16,
|
|
1677
|
-
child: CircularProgressIndicator(
|
|
1678
|
-
strokeWidth: 2,
|
|
1679
|
-
color: Colors.white,
|
|
1680
|
-
),
|
|
1681
|
-
)
|
|
1682
|
-
: Icon(Icons.system_update),
|
|
1683
|
-
label: Text('Update'),
|
|
1684
|
-
);
|
|
1685
|
-
if (compact) {
|
|
1686
|
-
return Column(
|
|
1687
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1688
|
-
children: <Widget>[
|
|
1689
|
-
runtimeTitle,
|
|
1690
|
-
const SizedBox(height: 10),
|
|
1691
|
-
updateButton,
|
|
1692
|
-
],
|
|
1693
|
-
);
|
|
1694
|
-
}
|
|
1695
|
-
return Row(
|
|
1696
|
-
children: <Widget>[
|
|
1697
|
-
Expanded(child: runtimeTitle),
|
|
1698
|
-
updateButton,
|
|
1699
|
-
],
|
|
1700
|
-
);
|
|
1701
|
-
},
|
|
1702
|
-
),
|
|
1703
|
-
] else ...<Widget>[
|
|
1704
|
-
Text(
|
|
1705
|
-
'Runtime',
|
|
1706
|
-
style: TextStyle(
|
|
1707
|
-
fontWeight: FontWeight.w700,
|
|
1708
|
-
color: _textPrimary,
|
|
1709
|
-
),
|
|
1710
|
-
),
|
|
1711
|
-
const SizedBox(height: 10),
|
|
1712
|
-
Text(
|
|
1713
|
-
'Updates and release tracks are managed for this deployment.',
|
|
1714
|
-
style: TextStyle(color: _textSecondary),
|
|
1715
|
-
),
|
|
1716
|
-
],
|
|
1717
|
-
const SizedBox(height: 12),
|
|
1718
|
-
LayoutBuilder(
|
|
1719
|
-
builder: (context, constraints) {
|
|
1720
|
-
final compact = constraints.maxWidth < 760;
|
|
1721
|
-
final statusRow = Wrap(
|
|
1722
|
-
spacing: 10,
|
|
1723
|
-
runSpacing: 10,
|
|
1724
|
-
crossAxisAlignment: WrapCrossAlignment.center,
|
|
1725
|
-
children: <Widget>[
|
|
1726
|
-
_StatusPill(
|
|
1727
|
-
label: controller.updateStatus.badgeLabel,
|
|
1728
|
-
color: controller.updateStatus.badgeColor,
|
|
1729
|
-
),
|
|
1730
|
-
_StatusPill(
|
|
1731
|
-
label: controller.updateStatus.releaseChannelLabel,
|
|
1732
|
-
color: controller.updateStatus.releaseChannel == 'beta'
|
|
1733
|
-
? _warning
|
|
1734
|
-
: _accent,
|
|
1735
|
-
),
|
|
1736
|
-
Text(
|
|
1737
|
-
controller.updateStatus.message,
|
|
1738
|
-
style: TextStyle(color: _textSecondary),
|
|
1739
|
-
),
|
|
1740
|
-
Text('${controller.updateStatus.progress}%'),
|
|
1741
|
-
],
|
|
1742
|
-
);
|
|
1743
|
-
if (compact) {
|
|
1744
|
-
return Column(
|
|
1745
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1746
|
-
children: <Widget>[statusRow],
|
|
1747
|
-
);
|
|
1748
|
-
}
|
|
1749
|
-
return statusRow;
|
|
1750
|
-
},
|
|
1751
|
-
),
|
|
1752
|
-
const SizedBox(height: 10),
|
|
1753
|
-
ClipRRect(
|
|
1754
|
-
borderRadius: BorderRadius.circular(999),
|
|
1755
|
-
child: LinearProgressIndicator(
|
|
1756
|
-
minHeight: 8,
|
|
1757
|
-
value: controller.updateStatus.progress / 100,
|
|
1758
|
-
backgroundColor: _bgSecondary,
|
|
1759
|
-
color: _accent,
|
|
1760
|
-
),
|
|
1761
|
-
),
|
|
1762
|
-
const SizedBox(height: 12),
|
|
1763
|
-
Text(controller.updateStatus.versionLine),
|
|
1764
|
-
],
|
|
1765
|
-
),
|
|
1766
|
-
),
|
|
1767
|
-
);
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
1509
|
Widget _buildDiagnosticsSection(NeoAgentController controller) {
|
|
1771
1510
|
return Card(
|
|
1772
1511
|
child: Padding(
|