Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions commit/api/bruno.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import frappe

@frappe.whitelist()
def generate_bruno_file(data):
request_data = frappe.parse_json(data)
"""
Generates .bru file content for a single request based on the provided request data.

:param request_data: A dictionary containing request information.
Expected keys are:
- name: The name of the request.
- arguments: A list of dictionaries containing argument information.
- def: The function definition.
- def_index: The index of the function definition.
- request_types: A list of request types (e.g., ['GET', 'POST']).
- xss_safe: A boolean indicating if the request is XSS safe.
- allow_guest: A boolean indicating if guests are allowed.
- other_decorators: A list of other decorators.
- index: An index number.
- block_start: The start of the block.
- block_end: The end of the block.
- file: The file path.
- api_path: The API path in the format 'raven.www.raven.get_context_for_dev'.
:return: A dictionary where keys are request types and values are the content of the corresponding .bru files.
"""
base_url_template = "{{baseUrl}}/api/method"

def format_name(name):
return ' '.join(word.capitalize() for word in name.split('_'))

name = format_name(request_data.get('name', 'Request'))
api_path = request_data.get('api_path', '')
request_types = request_data.get('request_types', ['GET']) or ['GET'] # Default to GET if empty
params = {arg['argument']: arg['default'] for arg in request_data.get('arguments', []) if arg['argument']}

bru_files = {}

for request_type in request_types:
seq = 1
request_type_upper = request_type.upper()
request_type_lower = request_type.lower()
url = f"{base_url_template}/{api_path}"

query_string = '&'.join([f'{k}={v}' for k, v in params.items() if v])
full_url = f'{url}?{query_string}' if query_string else url

bru_content = []

# Meta section
bru_content.append(f'meta {{\n name: {name}\n type: http\n seq: {seq}\n}}\n')

# Request section
bru_content.append(f'{request_type_lower} {{\n url: {full_url}\n body: none\n auth: none\n}}\n')

# Params section
if params:
bru_content.append(f'params:query {{\n')
for k, v in params.items():
if v:
bru_content.append(f' {k}: {v}\n')
bru_content.append('}\n')

bru_files[request_type_upper] = '\n'.join(bru_content)
frappe.local.response.filename = f'{name} {request_type}.bru' if len(request_types) > 1 else f'{name}.bru'
frappe.local.response.filecontent = bru_files[request_type_upper]
frappe.local.response.type = 'download'
18 changes: 12 additions & 6 deletions dashboard/src/components/features/api_viewer/APIDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ErrorBanner } from "@/components/common/ErrorBanner/ErrorBanner"
import { FullPageLoader } from "@/components/common/FullPageLoader/FullPageLoader"
import { Tabs } from "@/components/common/Tabs"
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { web_url } from "@/config/socket"
import { APIData, Argument } from "@/types/APIData"
import { XMarkIcon } from "@heroicons/react/24/outline"
import { useFrappeGetCall } from "frappe-react-sdk"
Expand Down Expand Up @@ -36,6 +37,13 @@ export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, set
}
}

const rest = useMemo(() => {
if (data) {
const { allow_guest, xss_safe, documentation, block_end, block_start, index, ...rest } = data
return rest
}
}, [data])

const requestTypeBorderColor = (requestType: string) => {
switch (requestType) {
case 'GET':
Expand All @@ -56,13 +64,11 @@ export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, set
<div className="border-b border-gray-200 pb-3 sm:flex sm:items-center sm:justify-between">
<h1 className="text-lg font-semibold leading-6 text-gray-900">API Details</h1>
<div className="mt-3 flex sm:ml-4 sm:mt-0 space-x-2">
{/* <button
type="button"
className="py-1 px-4 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-md border border-gray-200 hover:bg-gray-100 hover:text-blue-600 focus:z-10 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
>
Share
<button>
<a href={`${web_url}/api/method/commit.api.bruno.generate_bruno_file?data=${JSON.stringify(rest)}`} target="_blank">
<svg id="emoji" width="34" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg"><g id="color"><path fill="#F4AA41" stroke="none" d="M23.5,14.5855l-4.5,1.75l-7.25,8.5l-4.5,10.75l2,5.25c1.2554,3.7911,3.5231,7.1832,7.25,10l2.5-3.3333 c0,0,3.8218,7.7098,10.7384,8.9598c0,0,10.2616,1.936,15.5949-0.8765c3.4203-1.8037,4.4167-4.4167,4.4167-4.4167l3.4167-3.4167 l1.5833,2.3333l2.0833-0.0833l5.4167-7.25L64,37.3355l-0.1667-4.5l-2.3333-5.5l-4.8333-7.4167c0,0-2.6667-4.9167-8.1667-3.9167 c0,0-6.5-4.8333-11.8333-4.0833S32.0833,10.6688,23.5,14.5855z"></path><polygon fill="#EA5A47" stroke="none" points="36,47.2521 32.9167,49.6688 30.4167,49.6688 30.3333,53.5021 31.0833,57.0021 32.1667,58.9188 35,60.4188 39.5833,59.8355 41.1667,58.0855 42.1667,53.8355 41.9167,49.8355 39.9167,50.0855"></polygon><polygon fill="#3F3F3F" stroke="none" points="32.5,36.9188 30.9167,40.6688 33.0833,41.9188 34.3333,42.4188 38.6667,42.5855 41.5833,40.3355 39.8333,37.0855"></polygon></g><g id="hair"></g><g id="skin"></g><g id="skin-shadow"></g><g id="line"><path fill="#000000" stroke="none" d="M29.5059,30.1088c0,0-1.8051,1.2424-2.7484,0.6679c-0.9434-0.5745-1.2424-1.8051-0.6679-2.7484 s1.805-1.2424,2.7484-0.6679S29.5059,30.1088,29.5059,30.1088z"></path><path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M33.1089,37.006h6.1457c0.4011,0,0.7634,0.2397,0.9203,0.6089l1.1579,2.7245l-2.1792,1.1456 c-0.6156,0.3236-1.3654-0.0645-1.4567-0.754"></path><path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M34.7606,40.763c-0.1132,0.6268-0.7757,0.9895-1.3647,0.7471l-2.3132-0.952l1.0899-2.9035 c0.1465-0.3901,0.5195-0.6486,0.9362-0.6486"></path><path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M30.4364,50.0268c0,0-0.7187,8.7934,3.0072,9.9375c2.6459,0.8125,5.1497,0.5324,6.0625-0.25 c0.875-0.75,2.6323-4.4741,1.8267-9.6875"></path><path fill="#000000" stroke="none" d="M44.2636,30.1088c0,0,1.805,1.2424,2.7484,0.6679c0.9434-0.5745,1.2424-1.8051,0.6679-2.7484 c-0.5745-0.9434-1.805-1.2424-2.7484-0.6679C43.9881,27.9349,44.2636,30.1088,44.2636,30.1088z"></path><path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M25.6245,42.8393c-0.475,3.6024,2.2343,5.7505,4.2847,6.8414c1.1968,0.6367,2.6508,0.5182,3.7176-0.3181l2.581-2.0233l2.581,2.0233 c1.0669,0.8363,2.5209,0.9548,3.7176,0.3181c2.0504-1.0909,4.7597-3.239,4.2847-6.8414"></path><path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M19.9509,28.3572c-2.3166,5.1597-0.5084,13.0249,0.119,15.3759c0.122,0.4571,0.0755,0.9355-0.1271,1.3631l-1.9874,4.1937 c-0.623,1.3146-2.3934,1.5533-3.331,0.4409c-3.1921-3.7871-8.5584-11.3899-6.5486-16.686 c7.0625-18.6104,15.8677-18.1429,15.8677-18.1429c2.8453-1.9336,13.1042-6.9375,24.8125,0.875c0,0,8.6323-1.7175,14.9375,16.9375 c1.8036,5.3362-3.4297,12.8668-6.5506,16.6442c-0.9312,1.127-2.7162,0.8939-3.3423-0.4272l-1.9741-4.1656 c-0.2026-0.4275-0.2491-0.906-0.1271-1.3631c0.6275-2.3509,2.4356-10.2161,0.119-15.3759"></path><path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M52.6309,46.4628c0,0-3.0781,6.7216-7.8049,8.2712"></path><path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M19.437,46.969c0,0,3.0781,6.0823,7.8049,7.632"></path><line x1="36.2078" x2="36.2078" y1="47.3393" y2="44.3093" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"></line></g></svg>
</a>
</button>
<button className="text-white bg-gradient-to-r from-blue-500 via-blue-600 to-blue-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-blue-300 dark:focus:ring-blue-800 font-medium rounded-lg text-sm px-4 py-1 text-center">Export</button> */}
<button
type="button"
className="relative rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,17 @@ const ProjectCard = ({ project, mutate, orgName }: ProjectCardProps) => {
const [openDeleteDialogModal, setOpenDeleteDialogModal] = useState(false)

return (
<Card className="w-[220px] h-[300px] relative">
<Card className="w-[200px] h-[320px] relative">
<CardContent className="p-4">
<div className="flex flex-col gap-4 items-start">
<Avatar className="h-32 w-full flex items-center rounded-md border border-gray-100">
<AvatarImage src={project.image} />
<AvatarFallback className="rounded-md text-4xl">
{appNameInitials}
</AvatarFallback>
</Avatar>
<div className="w-full flex items-center justify-center">
<Avatar className="h-32 w-32 flex items-center rounded-md border border-gray-100">
<AvatarImage src={project.image} />
<AvatarFallback className="rounded-md text-4xl">
{appNameInitials}
</AvatarFallback>
</Avatar>
</div>

<div className="flex flex-col gap-1 w-full">
<div className="flex justify-between items-center">
Expand Down
5 changes: 5 additions & 0 deletions dashboard/src/config/socket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const host = window.location.hostname;
const web_port = window.location.port ? `:${window.location.port}` : '';
const protocol = web_port ? 'http' : 'https';

export const web_url = `${protocol}://${host}${web_port}`;