mirror of
https://github.com/foss42/apidash.git
synced 2025-12-03 03:17:00 +08:00
Refactor terminal and remove duplicate code.
This commit is contained in:
@@ -3,7 +3,6 @@ import 'package:apidash_design_system/apidash_design_system.dart';
|
|||||||
import 'package:apidash_core/apidash_core.dart';
|
import 'package:apidash_core/apidash_core.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
// Terminal enums moved here from models to avoid circular imports and simplify usage
|
|
||||||
|
|
||||||
const kDiscordUrl = "https://bit.ly/heyfoss";
|
const kDiscordUrl = "https://bit.ly/heyfoss";
|
||||||
const kGitUrl = "https://github.com/foss42/apidash";
|
const kGitUrl = "https://github.com/foss42/apidash";
|
||||||
@@ -86,15 +85,6 @@ enum HistoryRetentionPeriod {
|
|||||||
final IconData icon;
|
final IconData icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Source category of a terminal entry
|
|
||||||
enum TerminalSource { network, js, system }
|
|
||||||
|
|
||||||
/// Severity level of a terminal entry
|
|
||||||
enum TerminalLevel { debug, info, warn, error }
|
|
||||||
|
|
||||||
/// Phase of a network log lifecycle
|
|
||||||
enum NetworkPhase { started, progress, completed, failed }
|
|
||||||
|
|
||||||
enum ItemMenuOption {
|
enum ItemMenuOption {
|
||||||
edit("Rename"),
|
edit("Rename"),
|
||||||
delete("Delete"),
|
delete("Delete"),
|
||||||
@@ -521,3 +511,6 @@ const kMsgClearHistorySuccess = 'History cleared successfully';
|
|||||||
const kMsgClearHistoryError = 'Error clearing history';
|
const kMsgClearHistoryError = 'Error clearing history';
|
||||||
const kMsgShareError = "Unable to share";
|
const kMsgShareError = "Unable to share";
|
||||||
const kLabelGenerateUI = "Generate UI";
|
const kLabelGenerateUI = "Generate UI";
|
||||||
|
// Terminal Page
|
||||||
|
const kMsgNoLogs = 'No logs yet';
|
||||||
|
const kMsgSendToView = 'Send a request to view its details in the console.';
|
||||||
|
|||||||
@@ -2,4 +2,3 @@ export 'history_meta_model.dart';
|
|||||||
export 'history_request_model.dart';
|
export 'history_request_model.dart';
|
||||||
export 'request_model.dart';
|
export 'request_model.dart';
|
||||||
export 'settings_model.dart';
|
export 'settings_model.dart';
|
||||||
export 'terminal/models.dart';
|
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
import 'package:apidash_core/apidash_core.dart' show APIType, HTTPVerb;
|
|
||||||
|
|
||||||
/// Source category of a terminal entry
|
|
||||||
enum TerminalSource { network, js, system }
|
|
||||||
|
|
||||||
/// Severity level of a terminal entry
|
|
||||||
enum TerminalLevel { debug, info, warn, error }
|
|
||||||
|
|
||||||
/// Phase of a network log lifecycle
|
|
||||||
enum NetworkPhase { started, progress, completed, failed }
|
|
||||||
|
|
||||||
class BodyChunk {
|
|
||||||
BodyChunk({required this.ts, required this.text, required this.sizeBytes});
|
|
||||||
|
|
||||||
final DateTime ts;
|
|
||||||
final String text; // preview text (could be partial)
|
|
||||||
final int sizeBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
class NetworkLogData {
|
|
||||||
NetworkLogData({
|
|
||||||
required this.phase,
|
|
||||||
required this.apiType,
|
|
||||||
required this.method,
|
|
||||||
required this.url,
|
|
||||||
this.requestHeaders,
|
|
||||||
this.requestBodyPreview,
|
|
||||||
this.responseStatus,
|
|
||||||
this.responseHeaders,
|
|
||||||
this.responseBodyPreview,
|
|
||||||
this.duration,
|
|
||||||
this.isStreaming = false,
|
|
||||||
this.sentAt,
|
|
||||||
this.completedAt,
|
|
||||||
this.errorMessage,
|
|
||||||
List<BodyChunk>? chunks,
|
|
||||||
}) : chunks = chunks ?? <BodyChunk>[];
|
|
||||||
|
|
||||||
final NetworkPhase phase;
|
|
||||||
final APIType apiType;
|
|
||||||
final HTTPVerb method;
|
|
||||||
final String url;
|
|
||||||
final Map<String, String>? requestHeaders;
|
|
||||||
final String? requestBodyPreview;
|
|
||||||
final int? responseStatus;
|
|
||||||
final Map<String, String>? responseHeaders;
|
|
||||||
final String? responseBodyPreview;
|
|
||||||
final Duration? duration;
|
|
||||||
final bool isStreaming;
|
|
||||||
final DateTime? sentAt;
|
|
||||||
final DateTime? completedAt;
|
|
||||||
final String? errorMessage;
|
|
||||||
final List<BodyChunk> chunks;
|
|
||||||
|
|
||||||
NetworkLogData copyWith({
|
|
||||||
NetworkPhase? phase,
|
|
||||||
APIType? apiType,
|
|
||||||
HTTPVerb? method,
|
|
||||||
String? url,
|
|
||||||
Map<String, String>? requestHeaders,
|
|
||||||
String? requestBodyPreview,
|
|
||||||
int? responseStatus,
|
|
||||||
Map<String, String>? responseHeaders,
|
|
||||||
String? responseBodyPreview,
|
|
||||||
Duration? duration,
|
|
||||||
bool? isStreaming,
|
|
||||||
DateTime? sentAt,
|
|
||||||
DateTime? completedAt,
|
|
||||||
String? errorMessage,
|
|
||||||
List<BodyChunk>? chunks,
|
|
||||||
}) {
|
|
||||||
return NetworkLogData(
|
|
||||||
phase: phase ?? this.phase,
|
|
||||||
apiType: apiType ?? this.apiType,
|
|
||||||
method: method ?? this.method,
|
|
||||||
url: url ?? this.url,
|
|
||||||
requestHeaders: requestHeaders ?? this.requestHeaders,
|
|
||||||
requestBodyPreview: requestBodyPreview ?? this.requestBodyPreview,
|
|
||||||
responseStatus: responseStatus ?? this.responseStatus,
|
|
||||||
responseHeaders: responseHeaders ?? this.responseHeaders,
|
|
||||||
responseBodyPreview: responseBodyPreview ?? this.responseBodyPreview,
|
|
||||||
duration: duration ?? this.duration,
|
|
||||||
isStreaming: isStreaming ?? this.isStreaming,
|
|
||||||
sentAt: sentAt ?? this.sentAt,
|
|
||||||
completedAt: completedAt ?? this.completedAt,
|
|
||||||
errorMessage: errorMessage ?? this.errorMessage,
|
|
||||||
chunks: chunks ?? this.chunks,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class JsLogData {
|
|
||||||
JsLogData({
|
|
||||||
required this.level,
|
|
||||||
required this.args,
|
|
||||||
this.stack,
|
|
||||||
this.context,
|
|
||||||
this.contextRequestId,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String level; // log | warn | error | fatal
|
|
||||||
final List<String> args;
|
|
||||||
final String? stack;
|
|
||||||
final String? context; // preRequest | postResponse | global
|
|
||||||
final String? contextRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SystemLogData {
|
|
||||||
SystemLogData({required this.category, required this.message, this.stack});
|
|
||||||
final String category; // ui | provider | io | storage | unknown
|
|
||||||
final String message;
|
|
||||||
final String? stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
class TerminalEntry {
|
|
||||||
TerminalEntry({
|
|
||||||
required this.id,
|
|
||||||
required this.ts,
|
|
||||||
required this.source,
|
|
||||||
required this.level,
|
|
||||||
this.requestId,
|
|
||||||
this.correlationId,
|
|
||||||
this.tags = const <String>[],
|
|
||||||
this.network,
|
|
||||||
this.js,
|
|
||||||
this.system,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String id;
|
|
||||||
final DateTime ts;
|
|
||||||
final TerminalSource source;
|
|
||||||
final TerminalLevel level;
|
|
||||||
final String? requestId; // App request id for correlation
|
|
||||||
final String? correlationId; // Additional correlation if any
|
|
||||||
final List<String> tags;
|
|
||||||
final NetworkLogData? network;
|
|
||||||
final JsLogData? js;
|
|
||||||
final SystemLogData? system;
|
|
||||||
|
|
||||||
TerminalEntry copyWith({
|
|
||||||
DateTime? ts,
|
|
||||||
TerminalSource? source,
|
|
||||||
TerminalLevel? level,
|
|
||||||
String? requestId,
|
|
||||||
String? correlationId,
|
|
||||||
List<String>? tags,
|
|
||||||
NetworkLogData? network,
|
|
||||||
JsLogData? js,
|
|
||||||
SystemLogData? system,
|
|
||||||
}) {
|
|
||||||
return TerminalEntry(
|
|
||||||
id: id,
|
|
||||||
ts: ts ?? this.ts,
|
|
||||||
source: source ?? this.source,
|
|
||||||
level: level ?? this.level,
|
|
||||||
requestId: requestId ?? this.requestId,
|
|
||||||
correlationId: correlationId ?? this.correlationId,
|
|
||||||
tags: tags ?? this.tags,
|
|
||||||
network: network ?? this.network,
|
|
||||||
js: js ?? this.js,
|
|
||||||
system: system ?? this.system,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ import 'package:apidash_core/apidash_core.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
|
import 'package:apidash/terminal/terminal.dart';
|
||||||
import 'providers.dart';
|
import 'providers.dart';
|
||||||
import '../models/models.dart';
|
import '../models/models.dart';
|
||||||
import '../services/services.dart';
|
import '../services/services.dart';
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
import 'package:apidash/terminal/terminal.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
import 'package:apidash_core/apidash_core.dart';
|
import 'package:apidash_core/apidash_core.dart';
|
||||||
import '../models/terminal/models.dart';
|
|
||||||
import '../consts.dart';
|
|
||||||
|
|
||||||
final terminalStateProvider =
|
final terminalStateProvider =
|
||||||
StateNotifierProvider<TerminalController, TerminalState>((ref) {
|
StateNotifierProvider<TerminalController, TerminalState>((ref) {
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import '../../models/terminal/models.dart';
|
|
||||||
import '../../consts.dart';
|
|
||||||
import '../../providers/terminal_providers.dart';
|
|
||||||
import '../../providers/collection_providers.dart';
|
|
||||||
import '../../widgets/button_copy.dart';
|
|
||||||
import '../../widgets/field_search.dart';
|
|
||||||
import '../../widgets/terminal_tiles.dart';
|
|
||||||
import '../../widgets/empty_message.dart';
|
|
||||||
import 'package:apidash_design_system/apidash_design_system.dart';
|
import 'package:apidash_design_system/apidash_design_system.dart';
|
||||||
import '../../widgets/terminal_level_filter_menu.dart';
|
import 'package:apidash/terminal/terminal.dart';
|
||||||
|
import 'package:apidash/widgets/widgets.dart';
|
||||||
|
import 'package:apidash/providers/providers.dart';
|
||||||
|
import 'package:apidash/consts.dart';
|
||||||
|
|
||||||
class TerminalPage extends ConsumerStatefulWidget {
|
class TerminalPage extends ConsumerStatefulWidget {
|
||||||
const TerminalPage({super.key});
|
const TerminalPage({super.key});
|
||||||
@@ -109,10 +104,9 @@ class _TerminalPageState extends ConsumerState<TerminalPage> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: filtered.isEmpty
|
child: filtered.isEmpty
|
||||||
? const Center(
|
? const Center(
|
||||||
child: EmptyMessage(
|
child: SimpleText(
|
||||||
title: 'No logs yet',
|
title: kMsgNoLogs,
|
||||||
subtitle:
|
subtitle: kMsgSendToView,
|
||||||
'Send a request to see details here in the console',
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: ListView.separated(
|
: ListView.separated(
|
||||||
|
|||||||
8
lib/terminal/enums.dart
Normal file
8
lib/terminal/enums.dart
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/// Source category of a terminal entry
|
||||||
|
enum TerminalSource { network, js, system }
|
||||||
|
|
||||||
|
/// Severity level of a terminal entry
|
||||||
|
enum TerminalLevel { debug, info, warn, error }
|
||||||
|
|
||||||
|
/// Phase of a network log lifecycle
|
||||||
|
enum NetworkPhase { started, progress, completed, failed }
|
||||||
7
lib/terminal/models/body_chunk.dart
Normal file
7
lib/terminal/models/body_chunk.dart
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
class BodyChunk {
|
||||||
|
BodyChunk({required this.ts, required this.text, required this.sizeBytes});
|
||||||
|
|
||||||
|
final DateTime ts;
|
||||||
|
final String text; // preview text (could be partial)
|
||||||
|
final int sizeBytes;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export 'network_log_data.dart';
|
export 'body_chunk.dart';
|
||||||
export 'js_log_data.dart';
|
export 'js_log_data.dart';
|
||||||
|
export 'network_log_data.dart';
|
||||||
export 'system_log_data.dart';
|
export 'system_log_data.dart';
|
||||||
export 'terminal_entry.dart';
|
export 'terminal_entry.dart';
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:apidash/consts.dart';
|
|
||||||
import 'package:apidash_core/apidash_core.dart' show APIType, HTTPVerb;
|
import 'package:apidash_core/apidash_core.dart' show APIType, HTTPVerb;
|
||||||
|
import '../enums.dart';
|
||||||
|
import 'body_chunk.dart';
|
||||||
|
|
||||||
class NetworkLogData {
|
class NetworkLogData {
|
||||||
NetworkLogData({
|
NetworkLogData({
|
||||||
@@ -72,11 +73,3 @@ class NetworkLogData {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BodyChunk {
|
|
||||||
BodyChunk({required this.ts, required this.text, required this.sizeBytes});
|
|
||||||
|
|
||||||
final DateTime ts;
|
|
||||||
final String text; // preview text (could be partial)
|
|
||||||
final int sizeBytes;
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:apidash/consts.dart';
|
import '../enums.dart';
|
||||||
import 'network_log_data.dart';
|
import 'network_log_data.dart';
|
||||||
import 'js_log_data.dart';
|
import 'js_log_data.dart';
|
||||||
import 'system_log_data.dart';
|
import 'system_log_data.dart';
|
||||||
3
lib/terminal/terminal.dart
Normal file
3
lib/terminal/terminal.dart
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export 'models/models.dart';
|
||||||
|
export 'widgets/widgets.dart';
|
||||||
|
export 'enums.dart';
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../consts.dart';
|
import '../enums.dart';
|
||||||
|
|
||||||
class TerminalLevelFilterMenu extends StatelessWidget {
|
class TerminalLevelFilterMenu extends StatelessWidget {
|
||||||
const TerminalLevelFilterMenu({
|
const TerminalLevelFilterMenu({
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:apidash_design_system/apidash_design_system.dart';
|
import 'package:apidash_design_system/apidash_design_system.dart';
|
||||||
import 'package:apidash_core/apidash_core.dart';
|
import 'package:apidash_core/apidash_core.dart';
|
||||||
import '../consts.dart';
|
import 'package:apidash/utils/utils.dart';
|
||||||
import '../models/terminal/models.dart';
|
import 'package:apidash/widgets/widgets.dart';
|
||||||
import '../utils/ui_utils.dart';
|
import '../enums.dart';
|
||||||
import 'expandable_section.dart';
|
import '../models/models.dart';
|
||||||
import 'highlight_text.dart';
|
|
||||||
|
|
||||||
class SystemLogTile extends StatelessWidget {
|
class SystemLogTile extends StatelessWidget {
|
||||||
const SystemLogTile({
|
const SystemLogTile({
|
||||||
2
lib/terminal/widgets/widgets.dart
Normal file
2
lib/terminal/widgets/widgets.dart
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export 'terminal_level_filter_menu.dart';
|
||||||
|
export 'terminal_tiles.dart';
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class EmptyMessage extends StatelessWidget {
|
|
||||||
const EmptyMessage({
|
|
||||||
super.key,
|
|
||||||
this.title = 'No logs yet',
|
|
||||||
this.subtitle = 'Send a request to view its details in the console.',
|
|
||||||
this.icon,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
final String subtitle;
|
|
||||||
final IconData? icon;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
if (icon != null) ...[
|
|
||||||
Icon(icon, size: 36, color: theme.colorScheme.outline),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
],
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: theme.textTheme.titleMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
|
||||||
color: theme.colorScheme.outline,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
47
lib/widgets/text_simple.dart
Normal file
47
lib/widgets/text_simple.dart
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SimpleText extends StatelessWidget {
|
||||||
|
const SimpleText({
|
||||||
|
super.key,
|
||||||
|
this.title,
|
||||||
|
this.subtitle,
|
||||||
|
this.icon,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String? title;
|
||||||
|
final String? subtitle;
|
||||||
|
final IconData? icon;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (icon != null) ...[
|
||||||
|
Icon(icon, size: 36, color: theme.colorScheme.outline),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
],
|
||||||
|
if (title != null) ...[
|
||||||
|
Text(
|
||||||
|
title!,
|
||||||
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
],
|
||||||
|
if (subtitle != null)
|
||||||
|
Text(
|
||||||
|
subtitle!,
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: theme.colorScheme.outline,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,8 +35,6 @@ export 'field_read_only.dart';
|
|||||||
export 'field_text_bounded.dart';
|
export 'field_text_bounded.dart';
|
||||||
export 'field_search.dart';
|
export 'field_search.dart';
|
||||||
export 'expandable_section.dart';
|
export 'expandable_section.dart';
|
||||||
export 'empty_message.dart';
|
|
||||||
export 'terminal_tiles.dart';
|
|
||||||
export 'field_url.dart';
|
export 'field_url.dart';
|
||||||
export 'intro_message.dart';
|
export 'intro_message.dart';
|
||||||
export 'markdown.dart';
|
export 'markdown.dart';
|
||||||
@@ -71,6 +69,8 @@ export 'table_map.dart';
|
|||||||
export 'table_request_form.dart';
|
export 'table_request_form.dart';
|
||||||
export 'table_request.dart';
|
export 'table_request.dart';
|
||||||
export 'tab_label.dart';
|
export 'tab_label.dart';
|
||||||
|
export 'text_highlighted_selectable.dart';
|
||||||
|
export 'text_simple.dart';
|
||||||
export 'texts.dart';
|
export 'texts.dart';
|
||||||
export 'uint8_audio_player.dart';
|
export 'uint8_audio_player.dart';
|
||||||
export 'widget_not_sent.dart';
|
export 'widget_not_sent.dart';
|
||||||
|
|||||||
Reference in New Issue
Block a user