Merge branch 'main' into Dashbot_v2

This commit is contained in:
Ashita Prasad
2025-05-17 17:04:48 +05:30
committed by GitHub
22 changed files with 179 additions and 90 deletions

View File

@@ -11,7 +11,10 @@ A List of API endpoints that can be used for testing API Dash
#### For Testing HTTP PUT, PATCH, DELETE
- https://reqres.in/
#### For Testing HTTP OPTIONS
- https://reqbin.com/echo/options
#### For Testing sites with Bad Certificate
- https://badssl.com/
- https://www.ssl.com/sample-valid-revoked-and-expired-ssl-tls-certificates/

View File

@@ -1,4 +1,3 @@
import 'dart:convert';
import '../services/dashbot_service.dart';
import 'package:apidash/models/request_model.dart';
@@ -19,20 +18,14 @@ class ExplainFeature {
return "Error: Invalid API request (missing endpoint).";
}
final method = requestModel.httpRequestModel?.method
.toString()
.split('.')
.last
.toUpperCase() ??
"GET";
final endpoint = requestModel.httpRequestModel!.url;
final method =
requestModel.httpRequestModel?.method.name.toUpperCase() ?? "GET";
final url = requestModel.httpRequestModel!.url;
final headers = requestModel.httpRequestModel?.enabledHeadersMap ?? {};
final parameters = requestModel.httpRequestModel?.enabledParamsMap ?? {};
final body = requestModel.httpRequestModel?.body;
final rawResponse = responseModel.body;
final responseBody =
rawResponse is String ? rawResponse : jsonEncode(rawResponse);
final statusCode = responseModel.statusCode ?? 0;
final body = requestModel.httpRequestModel?.body ?? '';
final responseBody = responseModel.body;
final statusCode = responseModel.statusCode;
final prompt = '''
FOCUSED API INTERACTION BREAKDOWN
@@ -41,10 +34,16 @@ FOCUSED API INTERACTION BREAKDOWN
- Endpoint Purpose: What is this API endpoint designed to do?
- Interaction Type: Describe the core purpose of this specific request
**Request Mechanics:**
- Exact Endpoint: $endpoint
**Request Details:**
- Endpoint: $url
- HTTP Method: $method
- Key Parameters: ${parameters.isNotEmpty ? 'Specific inputs driving the request' : 'No custom parameters'}
- Request Headers: ${headers.isEmpty ? "None" : headers}
- URL Parameters: ${parameters.isEmpty ? "None" : parameters}
- Request Body: ${body.isEmpty ? "None" : body}
**Response Details**
- Status Code: $statusCode
- Content: $responseBody
**Response CORE Insights:**
- Status: Success or Failure?

View File

@@ -44,4 +44,5 @@ const _$HTTPVerbEnumMap = {
HTTPVerb.put: 'put',
HTTPVerb.patch: 'patch',
HTTPVerb.delete: 'delete',
HTTPVerb.options: 'options',
};

View File

@@ -17,6 +17,7 @@ class SettingsModel {
this.historyRetentionPeriod = HistoryRetentionPeriod.oneWeek,
this.workspaceFolderPath,
this.isSSLDisabled = false,
this.isDashBotEnabled = true,
});
final bool isDark;
@@ -31,6 +32,7 @@ class SettingsModel {
final HistoryRetentionPeriod historyRetentionPeriod;
final String? workspaceFolderPath;
final bool isSSLDisabled;
final bool isDashBotEnabled;
SettingsModel copyWith({
bool? isDark,
@@ -45,6 +47,7 @@ class SettingsModel {
HistoryRetentionPeriod? historyRetentionPeriod,
String? workspaceFolderPath,
bool? isSSLDisabled,
bool? isDashBotEnabled,
}) {
return SettingsModel(
isDark: isDark ?? this.isDark,
@@ -61,6 +64,7 @@ class SettingsModel {
historyRetentionPeriod ?? this.historyRetentionPeriod,
workspaceFolderPath: workspaceFolderPath ?? this.workspaceFolderPath,
isSSLDisabled: isSSLDisabled ?? this.isSSLDisabled,
isDashBotEnabled: isDashBotEnabled ?? this.isDashBotEnabled,
);
}
@@ -80,6 +84,7 @@ class SettingsModel {
historyRetentionPeriod: historyRetentionPeriod,
workspaceFolderPath: workspaceFolderPath,
isSSLDisabled: isSSLDisabled,
isDashBotEnabled: isDashBotEnabled,
);
}
@@ -134,6 +139,7 @@ class SettingsModel {
}
final workspaceFolderPath = data["workspaceFolderPath"] as String?;
final isSSLDisabled = data["isSSLDisabled"] as bool?;
final isDashBotEnabled = data["isDashBotEnabled"] as bool?;
const sm = SettingsModel();
@@ -151,6 +157,7 @@ class SettingsModel {
historyRetentionPeriod ?? HistoryRetentionPeriod.oneWeek,
workspaceFolderPath: workspaceFolderPath,
isSSLDisabled: isSSLDisabled,
isDashBotEnabled: isDashBotEnabled,
);
}
@@ -170,6 +177,7 @@ class SettingsModel {
"historyRetentionPeriod": historyRetentionPeriod.name,
"workspaceFolderPath": workspaceFolderPath,
"isSSLDisabled": isSSLDisabled,
"isDashBotEnabled": isDashBotEnabled,
};
}
@@ -194,7 +202,8 @@ class SettingsModel {
other.activeEnvironmentId == activeEnvironmentId &&
other.historyRetentionPeriod == historyRetentionPeriod &&
other.workspaceFolderPath == workspaceFolderPath &&
other.isSSLDisabled == isSSLDisabled;
other.isSSLDisabled == isSSLDisabled &&
other.isDashBotEnabled == isDashBotEnabled;
}
@override
@@ -213,6 +222,7 @@ class SettingsModel {
historyRetentionPeriod,
workspaceFolderPath,
isSSLDisabled,
isDashBotEnabled,
);
}
}

View File

@@ -33,6 +33,7 @@ class ThemeStateNotifier extends StateNotifier<SettingsModel> {
HistoryRetentionPeriod? historyRetentionPeriod,
String? workspaceFolderPath,
bool? isSSLDisabled,
bool? isDashBotEnabled,
}) async {
state = state.copyWith(
isDark: isDark,
@@ -47,6 +48,7 @@ class ThemeStateNotifier extends StateNotifier<SettingsModel> {
historyRetentionPeriod: historyRetentionPeriod,
workspaceFolderPath: workspaceFolderPath,
isSSLDisabled: isSSLDisabled,
isDashBotEnabled: isDashBotEnabled,
);
await setSettingsToSharedPrefs(state);
}

View File

@@ -1,3 +1,4 @@
import 'package:apidash/consts.dart';
import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart';
import 'package:multi_trigger_autocomplete_plus/multi_trigger_autocomplete_plus.dart';
@@ -37,6 +38,8 @@ class EnvCellField extends StatelessWidget {
decoration: getTextFieldInputDecoration(
clrScheme,
hintText: hintText,
isDense: true,
contentPadding: kIsMobile ? kPh6b12 : null,
),
autocompleteNoTrigger: autocompleteNoTrigger,
onChanged: onChanged,

View File

@@ -17,7 +17,7 @@ class Dashboard extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final railIdx = ref.watch(navRailIndexStateProvider);
final isDashBotVisible = ref.watch(dashBotVisibilityProvider);
final settings = ref.watch(settingsProvider);
return Scaffold(
body: SafeArea(
child: Stack(
@@ -138,8 +138,9 @@ class Dashboard extends ConsumerWidget {
],
),
),
// TODO: Release DashBot
floatingActionButton: !isDashBotVisible ? const DashBotFAB() : null,
floatingActionButton: settings.isDashBotEnabled
? const DashBotFAB()
: null,
);
}
}

View File

@@ -182,7 +182,7 @@ class EditEnvironmentVariablesState
color: Theme.of(context).colorScheme.surface,
borderRadius: kBorderRadius12,
),
margin: kP10,
margin: kPh10t10,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@@ -203,27 +203,28 @@ class EditEnvironmentVariablesState
),
),
),
kVSpacer40,
if (!kIsMobile) kVSpacer40,
],
),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: kPb15,
child: ElevatedButton.icon(
onPressed: () {
variableRows.add(kEnvironmentVariableEmptyModel);
_onFieldChange(selectedId!);
},
icon: const Icon(Icons.add),
label: const Text(
kLabelAddVariable,
style: kTextStyleButton,
if (!kIsMobile)
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: kPb15,
child: ElevatedButton.icon(
onPressed: () {
variableRows.add(kEnvironmentVariableEmptyModel);
_onFieldChange(selectedId!);
},
icon: const Icon(Icons.add),
label: const Text(
kLabelAddVariable,
style: kTextStyleButton,
),
),
),
),
),
],
);
}

View File

@@ -185,7 +185,7 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
return Stack(
children: [
Container(
margin: kP10,
margin: kPh10t10,
child: Column(
children: [
Expanded(
@@ -205,27 +205,28 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
),
),
),
kVSpacer40,
if (!kIsMobile) kVSpacer40,
],
),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: kPb15,
child: ElevatedButton.icon(
onPressed: () {
formRows.add(kFormDataEmptyModel);
_onFieldChange();
},
icon: const Icon(Icons.add),
label: const Text(
kLabelAddFormField,
style: kTextStyleButton,
if (!kIsMobile)
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: kPb15,
child: ElevatedButton.icon(
onPressed: () {
formRows.add(kFormDataEmptyModel);
_onFieldChange();
},
icon: const Icon(Icons.add),
label: const Text(
kLabelAddFormField,
style: kTextStyleButton,
),
),
),
),
),
],
);
}

View File

@@ -178,7 +178,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
return Stack(
children: [
Container(
margin: kP10,
margin: kPh10t10,
child: Column(
children: [
Expanded(
@@ -198,28 +198,29 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
),
),
),
kVSpacer40,
if (!kIsMobile) kVSpacer40,
],
),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: kPb15,
child: ElevatedButton.icon(
onPressed: () {
headerRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false);
_onFieldChange();
},
icon: const Icon(Icons.add),
label: const Text(
kLabelAddHeader,
style: kTextStyleButton,
if (!kIsMobile)
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: kPb15,
child: ElevatedButton.icon(
onPressed: () {
headerRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false);
_onFieldChange();
},
icon: const Icon(Icons.add),
label: const Text(
kLabelAddHeader,
style: kTextStyleButton,
),
),
),
),
),
],
);
}

View File

@@ -178,7 +178,7 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
return Stack(
children: [
Container(
margin: kP10,
margin: kPh10t10,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@@ -199,28 +199,29 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
),
),
),
kVSpacer40,
if (!kIsMobile) kVSpacer40,
],
),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: kPb15,
child: ElevatedButton.icon(
onPressed: () {
paramRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false);
_onFieldChange();
},
icon: const Icon(Icons.add),
label: const Text(
kLabelAddParam,
style: kTextStyleButton,
if (!kIsMobile)
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: kPb15,
child: ElevatedButton.icon(
onPressed: () {
paramRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false);
_onFieldChange();
},
icon: const Icon(Icons.add),
label: const Text(
kLabelAddParam,
style: kTextStyleButton,
),
),
),
),
),
],
);
}

View File

@@ -50,6 +50,18 @@ class SettingsPage extends ConsumerWidget {
ref.read(settingsProvider.notifier).update(isDark: value);
},
),
ADListTile(
type: ListTileType.switchOnOff,
title: 'DashBot',
subtitle:
'Current selection: ${settings.isDashBotEnabled ? "Enabled" : "Disabled"}',
value: settings.isDashBotEnabled,
onChanged: (bool? value) {
ref
.read(settingsProvider.notifier)
.update(isDashBotEnabled: value);
},
),
ADListTile(
type: ListTileType.switchOnOff,
title: 'Collection Pane Scrollbar Visiblity',

View File

@@ -51,6 +51,7 @@ Color getHTTPMethodColor(HTTPVerb? method) {
HTTPVerb.put => kColorHttpMethodPut,
HTTPVerb.patch => kColorHttpMethodPatch,
HTTPVerb.delete => kColorHttpMethodDelete,
HTTPVerb.options => kColorHttpMethodOptions,
_ => kColorHttpMethodGet,
};
return col;

View File

@@ -281,8 +281,18 @@ class _JsonPreviewerState extends State<JsonPreviewer> {
size: 18,
),
onPressed: () async {
await _copy(
kJsonEncoder.convert(toJson(node)), sm);
final val = toJson(node);
String toCopy = '';
if (node.isClass ||
node.isArray ||
node.isRoot) {
toCopy = kJsonEncoder.convert(val);
} else {
toCopy = (val.values as Iterable)
.first
.toString();
}
await _copy(toCopy, sm);
},
),
)

View File

@@ -17,7 +17,8 @@ enum HTTPVerb {
post("POST"),
put("PUT"),
patch("PAT"),
delete("DEL");
delete("DEL"),
options("OPT");
const HTTPVerb(this.abbr);
final String abbr;

View File

@@ -58,6 +58,7 @@ const _$HTTPVerbEnumMap = {
HTTPVerb.put: 'put',
HTTPVerb.patch: 'patch',
HTTPVerb.delete: 'delete',
HTTPVerb.options: 'options',
};
const _$ContentTypeEnumMap = {

View File

@@ -94,6 +94,7 @@ Future<(HttpResponse?, Duration?, String?)> sendHttpRequest(
case HTTPVerb.put:
case HTTPVerb.patch:
case HTTPVerb.delete:
case HTTPVerb.options:
final request = prepareHttpRequest(
url: requestUrl,
method: requestModel.method.name.toUpperCase(),

View File

@@ -23,6 +23,7 @@ final kColorHttpMethodPost = Colors.blue.shade800;
final kColorHttpMethodPut = Colors.amber.shade900;
final kColorHttpMethodPatch = kColorHttpMethodPut;
final kColorHttpMethodDelete = Colors.red.shade800;
final kColorHttpMethodOptions = Colors.deepPurple.shade800;
final kColorGQL = Colors.pink.shade600;

View File

@@ -53,12 +53,22 @@ const kPh20t40 = EdgeInsets.only(
right: 20,
top: 40,
);
const kPh10t10 = EdgeInsets.only(
left: 10,
right: 10,
top: 10,
);
const kPs0o6 = EdgeInsets.only(
left: 0,
top: 6,
right: 6,
bottom: 6,
);
const kPh6b12 = EdgeInsets.only(
left: 6.0,
right: 6.0,
bottom: 12.0,
);
const kPh60 = EdgeInsets.symmetric(horizontal: 60);
const kPh60v60 = EdgeInsets.symmetric(vertical: 60, horizontal: 60);
const kPt24l4 = EdgeInsets.only(

View File

@@ -466,3 +466,9 @@ const httpRequestModelPost13 = HttpRequestModel(
"text": "I LOVE Flutter"
}""",
);
/// Basic OPTIONS request model
const httpRequestModelOptions1 = HttpRequestModel(
method: HTTPVerb.options,
url: 'https://reqbin.com/echo/options',
);

View File

@@ -253,3 +253,9 @@ const requestModelPost13 = RequestModel(
apiType: APIType.rest,
httpRequestModel: httpRequestModelPost13,
);
const requestModelOptions1 = RequestModel(
id: 'options1',
apiType: APIType.rest,
httpRequestModel: httpRequestModelOptions1,
);

View File

@@ -119,4 +119,21 @@ void main() {
test('Testing hashcode', () {
expect(responseModel.hashCode, greaterThan(0));
});
test('Testing fromResponse for OPTIONS method', () async {
var responseRec = await sendHttpRequest(
requestModelOptions1.id,
requestModelOptions1.apiType,
requestModelOptions1.httpRequestModel!,
defaultUriScheme: kDefaultUriScheme,
noSSL: false,
);
final responseData = responseModel.fromResponse(response: responseRec.$1!);
expect(responseData.statusCode, 200);
expect(responseData.headers?['access-control-allow-methods'], 'GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS');
expect(responseData.headers?['access-control-allow-methods']?.contains("OPTIONS"), true);
expect(responseData.headers?['allow'], 'GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS');
expect(responseData.headers?['allow']?.contains("OPTIONS"), true);
});
}