mirror of
https://github.com/foss42/apidash.git
synced 2025-11-30 17:59:18 +08:00
Merge branch 'main' into Dashbot_v2
This commit is contained in:
@@ -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/
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -44,4 +44,5 @@ const _$HTTPVerbEnumMap = {
|
||||
HTTPVerb.put: 'put',
|
||||
HTTPVerb.patch: 'patch',
|
||||
HTTPVerb.delete: 'delete',
|
||||
HTTPVerb.options: 'options',
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -51,6 +51,7 @@ Color getHTTPMethodColor(HTTPVerb? method) {
|
||||
HTTPVerb.put => kColorHttpMethodPut,
|
||||
HTTPVerb.patch => kColorHttpMethodPatch,
|
||||
HTTPVerb.delete => kColorHttpMethodDelete,
|
||||
HTTPVerb.options => kColorHttpMethodOptions,
|
||||
_ => kColorHttpMethodGet,
|
||||
};
|
||||
return col;
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -58,6 +58,7 @@ const _$HTTPVerbEnumMap = {
|
||||
HTTPVerb.put: 'put',
|
||||
HTTPVerb.patch: 'patch',
|
||||
HTTPVerb.delete: 'delete',
|
||||
HTTPVerb.options: 'options',
|
||||
};
|
||||
|
||||
const _$ContentTypeEnumMap = {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
|
||||
@@ -253,3 +253,9 @@ const requestModelPost13 = RequestModel(
|
||||
apiType: APIType.rest,
|
||||
httpRequestModel: httpRequestModelPost13,
|
||||
);
|
||||
|
||||
const requestModelOptions1 = RequestModel(
|
||||
id: 'options1',
|
||||
apiType: APIType.rest,
|
||||
httpRequestModel: httpRequestModelOptions1,
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user