requestkit 0.5.0 → 0.6.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b3ef7e7cd97233efe4a2d92df61192c3a622588c8925fb2b1639f95ba396ec6
4
- data.tar.gz: 8479411497b672380fb53e0a9f2d81f0960d2b6f0cc8984332ef97f2f9f47811
3
+ metadata.gz: b03d1163eb2e887c855f1c4ca9b6d56bbfe9d38be3a89e6f4d9322a6b2c8768d
4
+ data.tar.gz: 75acfe2ad2e7a0bbfa88549d53b6096b1967b197c7cbcef2fee2cba63386c958
5
5
  SHA512:
6
- metadata.gz: 9a27d2dcffca1175edfc1402e0f9c253812fb1d2965a56d3adaf1abbcf7f7d5a36540434ce0a68dd72c5fe15353b7b54f589f1f6437e4585707ecdeafca48089
7
- data.tar.gz: bf364c6b6ffb7a062ed3f73c3e00bf2113724eb3da705a02cc674d3296d5e884ee345030f51fcb084fb89da47da4d67d894d7be72387550dc4f673d9b684b900
6
+ metadata.gz: 4c6f9be4c8c65c91af603485349ad39bc15617eaef8304d203a99731750fe498c245ef0265cd6a75db4530929e0983eb07eb9c3fff30a42b36a127c2b888363f
7
+ data.tar.gz: a35745e18c86bf03ac31d72f7c087a25f39102c72a098b6193914df1685babd6d7b5bef11c0de6d7d88c4c20900041a915c9b05e9c9a44b8a728c923da4d55b6
data/README.md CHANGED
@@ -42,14 +42,14 @@ curl -X POST http://localhost:4000/stripe/webhook \
42
42
  Open `http://localhost:4000` in your browser to see all captured requests with headers and body.
43
43
 
44
44
 
45
- ### Custom Port
45
+ ### Custom port
46
46
 
47
47
  ```bash
48
48
  requestkit --port 8080
49
49
  ```
50
50
 
51
51
 
52
- ### Persistent Storage
52
+ ### Persistent storage
53
53
 
54
54
  By default, requests are stored in memory and cleared when you stop the server. Use file storage to persist across restarts:
55
55
  ```bash
@@ -59,7 +59,7 @@ requestkit --storage file
59
59
  Requests are saved to `~/.config/requestkit/requestkit.db`.
60
60
 
61
61
 
62
- ### Custom Database Path
62
+ ### Custom database path
63
63
 
64
64
  ```bash
65
65
  requestkit --storage file --database-path ./my-project.db
@@ -71,14 +71,12 @@ requestkit --storage file --database-path ./my-project.db
71
71
  Create a configuration file to set defaults:
72
72
 
73
73
  **User-wide settings** (`~/.config/requestkit/config.yml`):
74
-
75
74
  ```yaml
76
75
  port: 5000
77
76
  storage: file
78
77
  ```
79
78
 
80
79
  **Project-specific settings** (`./.requestkit.yml`):
81
-
82
80
  ```yaml
83
81
  storage: memory
84
82
  default_namespace: my-rails-app
@@ -87,7 +85,7 @@ default_namespace: my-rails-app
87
85
  Configuration precedence: CLI flags > project config > user config > defaults
88
86
 
89
87
 
90
- ### Available Options
88
+ ### Available options
91
89
 
92
90
  | Option | Description | Default |
93
91
  |--------|-------------|---------|
@@ -111,6 +109,28 @@ curl http://localhost:4000/github/push-event
111
109
  Filter by namespace in the web UI. Requests to `/` use the `default_namespace` from your config.
112
110
 
113
111
 
112
+ ## Sending requests
113
+
114
+ Create JSON files to send HTTP requests and test your APIs. Requestkit loads request definitions from:
115
+
116
+ 1. `./.requestkit/requests/:namespace/:name.json` (project-specific)
117
+ 2. `~/.config/requestkit/requests/:namespace/name.json` (user-wide)
118
+
119
+ Example: (`.requestkit/requests/api/create-user.json`):
120
+ ```json
121
+ {
122
+ "method": "POST",
123
+ "url": "http://localhost:3000/api/users",
124
+
125
+ "headers": {
126
+ "Content-Type": "application/json"
127
+ },
128
+
129
+ "body": "{\"name\": \"John Doe\", \"email\": \"john@example.com\"}"
130
+ }
131
+ ```
132
+
133
+
114
134
  ## Help
115
135
 
116
136
  ```bash
@@ -120,4 +140,4 @@ requestkit help
120
140
 
121
141
  ## License
122
142
 
123
- Perron is released under the [MIT License](https://opensource.org/licenses/MIT).
143
+ Requestkit is released under the [MIT License](https://opensource.org/licenses/MIT).
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "erb"
4
4
 
5
+ require "requestkit/server/request"
6
+
5
7
  module Requestkit
6
8
  class Server
7
9
  class Render
@@ -13,6 +15,7 @@ module Requestkit
13
15
  context = {
14
16
  requests: selected_namespace ? database.by_namespace(selected_namespace) : database.all,
15
17
  namespaces: database.namespaces,
18
+ saved_requests: Request.all_saved,
16
19
  selected_namespace: selected_namespace
17
20
  }
18
21
 
@@ -2,21 +2,31 @@
2
2
 
3
3
  require "async/http/client"
4
4
  require "openssl"
5
+ require "json" # not needed?
5
6
 
6
7
  module Requestkit
7
8
  class Server
8
9
  class Request
9
10
  class << self
10
- def send(database:, namespace:)
11
+ def send(database:, namespace:, name:)
12
+ request_definition = load_request(namespace: namespace, name: name)
13
+
14
+ unless request_definition
15
+ return false
16
+ end
17
+
11
18
  request_data = {
12
- headers: {"Content-Type" => "application/json", "User-Agent" => "Requestkit/#{Requestkit::VERSION}"},
13
- body: '{"test": "data"}'
19
+ headers: request_definition["headers"].merge({"User-Agent" => "Requestkit/#{Requestkit::VERSION}"}),
20
+ body: request_definition["body"]
14
21
  }
15
22
 
16
- endpoint = ssl_endpoint_for("https://httpbin.org/post")
23
+ url = request_definition["url"]
24
+ method = request_definition["method"].downcase
25
+
26
+ endpoint = ssl_endpoint_for(url)
17
27
  client = Async::HTTP::Client.new(endpoint)
18
28
 
19
- response = client.post("/post", request_data[:headers], request_data[:body])
29
+ response = client.send(method, URI(url).path, request_data[:headers], request_data[:body])
20
30
  response_data = {
21
31
  headers: response.headers.to_h,
22
32
  body: response.body&.read || ""
@@ -26,18 +36,98 @@ module Requestkit
26
36
 
27
37
  database.store(
28
38
  namespace: namespace,
29
- method: "POST",
30
- path: "https://httpbin.org/post",
39
+ method: method.upcase,
40
+ path: url,
31
41
  request: request_data.to_json,
32
42
  response: response_data.to_json,
33
43
  status: response.status,
34
44
  direction: "outbound",
35
45
  timestamp: Time.now.iso8601
36
46
  )
47
+
48
+ true
49
+ rescue => error
50
+ puts "Error sending request: #{error.message}"
51
+
52
+ false
53
+ end
54
+
55
+ def all_saved
56
+ {}.tap do |requests|
57
+ [local_requests_dir, user_requests_dir].each do |base_dir|
58
+ next unless Dir.exist?(base_dir)
59
+
60
+ Dir.glob(File.join(base_dir, "*", "*.json")).each do |file_path|
61
+ namespace = File.basename(File.dirname(file_path))
62
+ name = File.basename(file_path, ".json")
63
+
64
+ begin
65
+ definition = JSON.parse(File.read(file_path))
66
+
67
+ unless definition["method"] && definition["url"]
68
+ next
69
+ end
70
+
71
+ requests[namespace] ||= []
72
+ requests[namespace] << {
73
+ "name" => name,
74
+ "method" => definition["method"],
75
+ "url" => definition["url"]
76
+ }
77
+ rescue JSON::ParserError
78
+ next
79
+ end
80
+ end
81
+ # Dir.glob(File.join(base_dir, "*", "*.json")).each do |file_path|
82
+ # namespace = File.basename(File.dirname(file_path))
83
+ # name = File.basename(file_path, ".json")
84
+ # definition = JSON.parse(File.read(file_path))
85
+
86
+ # requests[namespace] ||= []
87
+ # requests[namespace] << {
88
+ # "name" => name,
89
+ # "method" => definition["method"],
90
+ # "url" => definition["url"]
91
+ # }
92
+ # end
93
+ end
94
+ end
37
95
  end
38
96
 
39
97
  private
40
98
 
99
+ # def load_request(namespace:, name:)
100
+ # [local_request_path(namespace, name), user_request_path(namespace, name)].each do |path|
101
+ # return JSON.parse(File.read(path)) if File.exist?(path)
102
+ # end
103
+
104
+ # puts "Request file not found: #{namespace}/#{name}"
105
+ # nil
106
+ # end
107
+ def load_request(namespace:, name:)
108
+ [local_request_path(namespace, name), user_request_path(namespace, name)].each do |path|
109
+ if File.exist?(path)
110
+ definition = JSON.parse(File.read(path))
111
+
112
+ unless definition["method"] && definition["url"]
113
+ puts "Invalid request file #{namespace}/#{name}: missing `method` or `url`"
114
+
115
+ return nil
116
+ end
117
+
118
+ return definition
119
+ end
120
+ end
121
+
122
+ puts "Request file not found: #{namespace}/#{name}"
123
+
124
+ nil
125
+ rescue JSON::ParserError => error
126
+ puts "Invalid JSON in #{namespace}/#{name}: #{error.message}"
127
+
128
+ nil
129
+ end
130
+
41
131
  def ssl_endpoint_for(url)
42
132
  endpoint = Async::HTTP::Endpoint.parse(url)
43
133
  ssl_context = OpenSSL::SSL::SSLContext.new
@@ -45,6 +135,14 @@ module Requestkit
45
135
 
46
136
  endpoint.with(ssl_context: ssl_context)
47
137
  end
138
+
139
+ def local_request_path(namespace, name) = File.join(local_requests_dir, namespace, "#{name}.json")
140
+
141
+ def user_request_path(namespace, name) = File.join(user_requests_dir, namespace, "#{name}.json")
142
+
143
+ def local_requests_dir = File.join(Dir.pwd, ".requestkit", "requests")
144
+
145
+ def user_requests_dir = File.join(Dir.home, ".config", "requestkit", "requests")
48
146
  end
49
147
  end
50
148
  end
@@ -43,11 +43,18 @@ module Requestkit
43
43
  when ["/", "GET"]
44
44
  Render.html(request, @db, @config)
45
45
  when ["/send", "POST"]
46
- Request.send(database: @db, namespace: "test")
47
-
48
- notify!
49
-
50
- Protocol::HTTP::Response[200, {"content-type" => "text/plain"}, ["Request sent!"]]
46
+ query_parameters = Render.send(:query_params, from: request.path)
47
+ namespace = query_parameters["namespace"] || "test"
48
+ name = query_parameters["name"] || "default"
49
+
50
+ success = Request.send(database: @db, namespace: namespace, name: name)
51
+
52
+ if success
53
+ notify!
54
+ Protocol::HTTP::Response[303, {"location" => "/"}, []]
55
+ else
56
+ Protocol::HTTP::Response[400, {"content-type" => "text/plain"}, ["Failed to send request"]]
57
+ end
51
58
  when ["/index.css", "GET"]
52
59
  Render.css
53
60
  when ["/clear", "POST"]
@@ -1,2 +1,2 @@
1
1
  /*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-200:oklch(88.5% .062 18.334);--color-red-700:oklch(50.5% .213 27.518);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-800:oklch(27.8% .033 256.848);--color-white:#fff;--spacing:.25rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--radius-sm:.25rem;--radius-md:.375rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.flex{display:flex}.hidden{display:none}.inline-flex{display:inline-flex}.w-full{width:100%}.max-w-5xl{max-width:var(--container-5xl)}.border-collapse{border-collapse:collapse}.cursor-pointer{cursor:pointer}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-end{justify-content:flex-end}.gap-x-1\.5{column-gap:calc(var(--spacing)*1.5)}.gap-x-2{column-gap:calc(var(--spacing)*2)}.overflow-x-auto{overflow-x:auto}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-t-md{border-top-left-radius:var(--radius-md);border-top-right-radius:var(--radius-md)}.rounded-b-md{border-bottom-right-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-gray-200{border-color:var(--color-gray-200)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200\/60{background-color:#e5e7eb99}@supports (color:color-mix(in lab, red, red)){.bg-gray-200\/60{background-color:color-mix(in oklab,var(--color-gray-200)60%,transparent)}}.bg-white{background-color:var(--color-white)}.p-3{padding:calc(var(--spacing)*3)}.px-2{padding-inline:calc(var(--spacing)*2)}.py-1{padding-block:calc(var(--spacing)*1)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-800{color:var(--color-gray-800)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}:is(.\*\:px-4>*){padding-inline:calc(var(--spacing)*4)}:is(.\*\:py-1>*){padding-block:calc(var(--spacing)*1)}:is(.\*\:py-2>*){padding-block:calc(var(--spacing)*2)}:is(.\*\:text-left>*){text-align:left}:is(.\*\:text-sm>*){font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}:is(.\*\:text-xs>*){font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}:is(.\*\:font-semibold>*){--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}:is(.\*\:text-gray-500>*){color:var(--color-gray-500)}@media (hover:hover){.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-200:hover{background-color:var(--color-gray-200)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:text-red-700:hover{color:var(--color-red-700)}}.has-\[\+tr\[open\]\]\:bg-gray-50:has(+tr[open]){background-color:var(--color-gray-50)}.\[\&_svg\]\:size-3\.5 svg{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.\[\[open\]\]\:table-row[open]{display:table-row}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-200:oklch(88.5% .062 18.334);--color-red-500:oklch(63.7% .237 25.331);--color-red-700:oklch(50.5% .213 27.518);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-white:#fff;--spacing:.25rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--radius-sm:.25rem;--radius-md:.375rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.col-span-12{grid-column:span 12/span 12}.mx-auto{margin-inline:auto}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.w-full{width:100%}.max-w-7xl{max-width:var(--container-7xl)}.flex-1{flex:1}.border-collapse{border-collapse:collapse}.cursor-pointer{cursor:pointer}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-end{justify-content:flex-end}.gap-4{gap:calc(var(--spacing)*4)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}.gap-x-1\.5{column-gap:calc(var(--spacing)*1.5)}.gap-x-2{column-gap:calc(var(--spacing)*2)}.overflow-x-auto{overflow-x:auto}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-t-md{border-top-left-radius:var(--radius-md);border-top-right-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-gray-200{border-color:var(--color-gray-200)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200\/60{background-color:#e5e7eb99}@supports (color:color-mix(in lab, red, red)){.bg-gray-200\/60{background-color:color-mix(in oklab,var(--color-gray-200)60%,transparent)}}.bg-white{background-color:var(--color-white)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[\.675rem\],.text-\[0\.675rem\]{font-size:.675rem}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-red-700{color:var(--color-red-700)}.text-white{color:var(--color-white)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}:is(.\*\:px-4>*){padding-inline:calc(var(--spacing)*4)}:is(.\*\:py-1>*){padding-block:calc(var(--spacing)*1)}:is(.\*\:py-2>*){padding-block:calc(var(--spacing)*2)}:is(.\*\:text-left>*){text-align:left}:is(.\*\:text-sm>*){font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}:is(.\*\:text-xs>*){font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}:is(.\*\:font-semibold>*){--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}:is(.\*\:text-gray-500>*){color:var(--color-gray-500)}@media (hover:hover){.hover\:bg-blue-600:hover{background-color:var(--color-blue-600)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-200:hover{background-color:var(--color-gray-200)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:text-red-700:hover{color:var(--color-red-700)}}.has-\[\+tr\[open\]\]\:bg-gray-50:has(+tr[open]){background-color:var(--color-gray-50)}@media (min-width:48rem){.md\:col-span-4{grid-column:span 4/span 4}.md\:col-span-8{grid-column:span 8/span 8}}.\[\&_svg\]\:size-3\.5 svg{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.\[\&_svg\]\:size-4 svg{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\[open\]\]\:table-row[open]{display:table-row}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}
@@ -4,97 +4,155 @@
4
4
  <title>Requestkit</title>
5
5
  <meta charset="utf-8">
6
6
  <link href="/index.css" rel="stylesheet" />
7
+
7
8
  <script defer src="https://cdn.jsdelivr.net/npm/attractivejs@latest"></script>
8
9
  </head>
9
10
 
10
11
  <body class="bg-gray-100">
11
- <div class="max-w-5xl mx-auto px-2 py-4 bg-white rounded-b-md">
12
- <div class="flex items-center justify-between">
13
- <% if namespaces.any? %>
14
- <form method="get" action="/" id="namespace" class="inline-flex">
15
- <select name="namespace" data-action="form#submit" data-target="#namespace" class="inline-flex items-center gap-x-1.5 px-2 py-1 text-xs font-normal text-gray-600 bg-gray-100 rounded-md transition hover:bg-gray-200">
16
- <option value="">All namespaces</option>
17
-
18
- <% namespaces.each do |namespace| %>
19
- <option value="<%= namespace %>" <%= 'selected' if selected_namespace == namespace %>><%= namespace %></option>
20
- <% end %>
21
- </select>
22
- </form>
23
- <% end %>
24
-
25
- <div class="flex items-center justify-end gap-x-2 [&_svg]:size-3.5">
26
- <a href="/" class="inline-flex items-center gap-x-1.5 px-2 py-1 text-xs font-normal text-gray-600 bg-gray-100 rounded-md transition hover:bg-gray-200">
27
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M240,56v48a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16H211.4L184.81,71.64l-.25-.24a80,80,0,1,0-1.67,114.78,8,8,0,0,1,11,11.63A95.44,95.44,0,0,1,128,224h-1.32A96,96,0,1,1,195.75,60L224,85.8V56a8,8,0,1,1,16,0Z"/></svg>
28
-
29
- Refresh
30
- </a>
31
-
32
- <% unless requests.empty? %>
33
- <form method="post" action="/clear">
34
- <button type="submit" data-action="confirm" data-confirm-message="Clear all requests?" class="inline-flex items-center gap-x-1.5 px-2 py-1 text-xs font-normal text-gray-600 bg-gray-100 rounded-md cursor-pointer transition hover:bg-red-200 hover:text-red-700">
35
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"/></svg>
36
-
37
- Clear
38
- </button>
12
+ <div class="max-w-7xl mx-auto px-2 py-4 grid grid-cols-12 gap-4">
13
+ <div class="col-span-12 bg-white rounded-md p-4 md:col-span-8">
14
+ <div class="flex items-center justify-between">
15
+ <% if namespaces.any? %>
16
+ <form method="get" action="/" id="namespace" class="inline-flex">
17
+ <select name="namespace" data-action="form#submit" data-target="#namespace" class="inline-flex items-center gap-x-1.5 px-2 py-1 text-xs font-normal text-gray-600 bg-gray-100 rounded-md transition hover:bg-gray-200">
18
+ <option value="">All namespaces</option>
19
+
20
+ <% namespaces.each do |namespace| %>
21
+ <option value="<%= namespace %>" <%= 'selected' if selected_namespace == namespace %>><%= namespace %></option>
22
+ <% end %>
23
+ </select>
39
24
  </form>
40
25
  <% end %>
41
- </div>
42
- </div>
43
26
 
44
- <% if requests.empty? %>
45
- <p class="mt-3 text-sm text-gray-500">
46
- No webhooks received yet. Send a request to any endpoint.
47
- </p>
48
- <% else %>
49
- <table class="w-full mt-4 border-collapse">
50
- <thead>
51
- <tr class="rounded-t-md *:px-4 *:py-1 *:text-left *:text-xs *:font-semibold *:text-gray-500">
52
- <th>#</th>
27
+ <div class="flex items-center justify-end gap-x-2 [&_svg]:size-3.5">
28
+ <a href="/" class="inline-flex items-center gap-x-1.5 px-2 py-1 text-xs font-normal text-gray-600 bg-gray-100 rounded-md transition hover:bg-gray-200">
29
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M240,56v48a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16H211.4L184.81,71.64l-.25-.24a80,80,0,1,0-1.67,114.78,8,8,0,0,1,11,11.63A95.44,95.44,0,0,1,128,224h-1.32A96,96,0,1,1,195.75,60L224,85.8V56a8,8,0,1,1,16,0Z"/></svg>
53
30
 
54
- <th>Method</th>
31
+ Refresh
32
+ </a>
55
33
 
56
- <th>Path</th>
34
+ <% unless requests.empty? %>
35
+ <form method="post" action="/clear">
36
+ <button type="submit" data-action="confirm" data-confirm-message="Clear all requests?" class="inline-flex items-center gap-x-1.5 px-2 py-1 text-xs font-normal text-gray-600 bg-gray-100 rounded-md cursor-pointer transition hover:bg-red-200 hover:text-red-700">
37
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"/></svg>
57
38
 
58
- <th>Time</th>
59
- </tr>
60
- </thead>
39
+ Clear
40
+ </button>
41
+ </form>
42
+ <% end %>
43
+ </div>
44
+ </div>
61
45
 
62
- <tbody>
63
- <% requests.each do |request| %>
64
- <% request_data = JSON.parse(request["request"]) %>
65
- <tr data-action="toggleAttribute#open" data-target="#details-<%= request["id"] %>" class="*:px-4 *:py-2 *:text-sm has-[+tr[open]]:bg-gray-50 hover:bg-gray-50">
66
- <td><%= request["id"] %></td>
46
+ <% if requests.empty? %>
47
+ <p class="mt-3 text-sm text-gray-500">
48
+ No webhooks received yet. Send a request to any endpoint.
49
+ </p>
50
+ <% else %>
51
+ <table class="w-full mt-4 border-collapse">
52
+ <thead>
53
+ <tr class="rounded-t-md *:px-4 *:py-1 *:text-left *:text-xs *:font-semibold *:text-gray-500">
54
+ <th>#</th>
67
55
 
68
- <td class="text-xs font-medium"><%= request["method"] %></td>
56
+ <th>Method</th>
69
57
 
70
- <td>
71
- <code class="text-xs bg-gray-200/60 px-2 py-1 rounded-sm">
72
- <%= request["path"] %>
73
- </code>
74
- </td>
58
+ <th>Path</th>
75
59
 
76
- <td class="text-xs"><%= request["timestamp"] %></td>
60
+ <th>Time</th>
77
61
  </tr>
62
+ </thead>
63
+
64
+ <tbody>
65
+ <% requests.each do |request| %>
66
+ <% request_data = JSON.parse(request["request"]) %>
67
+ <tr data-action="toggleAttribute#open" data-target="#details-<%= request["id"] %>" class="*:px-4 *:py-2 *:text-sm has-[+tr[open]]:bg-gray-50 hover:bg-gray-50">
68
+ <td><%= request["id"] %></td>
69
+
70
+ <td class="flex items-center gap-x-1.5 text-[0.675rem] font-medium [&_svg]:size-4 [&_svg]:shrink-0 <%= 'text-red-700' if request["status"] && !(200..299).include?(request["status"].to_i) %>">
71
+ <% if request["direction"] == "outbound" %>
72
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M178.34,165.66,160,147.31V208a8,8,0,0,1-16,0V147.31l-18.34,18.35a8,8,0,0,1-11.32-11.32l32-32a8,8,0,0,1,11.32,0l32,32a8,8,0,0,1-11.32,11.32ZM160,40A88.08,88.08,0,0,0,81.29,88.68,64,64,0,1,0,72,216h40a8,8,0,0,0,0-16H72a48,48,0,0,1,0-96c1.1,0,2.2,0,3.29.12A88,88,0,0,0,72,128a8,8,0,0,0,16,0,72,72,0,1,1,100.8,66,8,8,0,0,0,3.2,15.34,7.9,7.9,0,0,0,3.2-.68A88,88,0,0,0,160,40Z"/></svg>
73
+ <% else %>
74
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M248,128a87.34,87.34,0,0,1-17.6,52.81,8,8,0,1,1-12.8-9.62A71.34,71.34,0,0,0,232,128a72,72,0,0,0-144,0,8,8,0,0,1-16,0,88,88,0,0,1,3.29-23.88C74.2,104,73.1,104,72,104a48,48,0,0,0,0,96H96a8,8,0,0,1,0,16H72A64,64,0,1,1,81.29,88.68,88,88,0,0,1,248,128Zm-69.66,42.34L160,188.69V128a8,8,0,0,0-16,0v60.69l-18.34-18.35a8,8,0,0,0-11.32,11.32l32,32a8,8,0,0,0,11.32,0l32-32a8,8,0,0,0-11.32-11.32Z"/></svg>
75
+ <% end %>
76
+
77
+ <%= request["method"] %>
78
+ </td>
79
+
80
+ <td>
81
+ <code class="text-xs bg-gray-200/60 px-2 py-1 rounded-sm">
82
+ <%= request["path"] %>
83
+ </code>
84
+ </td>
85
+
86
+ <td class="text-xs"><%= request["timestamp"] %></td>
87
+ </tr>
88
+
89
+ <tr id="details-<%= request["id"] %>" class="hidden w-full bg-gray-50 [[open]]:table-row">
90
+ <td colspan="5" class="px-2 py-3">
91
+ <section>
92
+ <h4 class="font-medium text-sm text-gray-800">
93
+ Headers
94
+ </h4>
95
+ <pre class="text-xs bg-white p-3 rounded-sm border border-gray-200 overflow-x-auto"><%= JSON.pretty_generate(request_data["headers"]) %></pre>
96
+
97
+ <h4 class="mt-4 font-medium text-sm text-gray-800">
98
+ Body
99
+ </h4>
100
+ <pre class="text-xs bg-white p-3 rounded-sm border border-gray-200 overflow-x-auto"><%= request_data["body"].empty? ? "(empty)" : request_data["body"] %></pre>
101
+
102
+ <% if request["response"] %>
103
+ <% response_data = JSON.parse(request["response"]) %>
104
+
105
+ <h4 class="mt-4 font-medium text-sm text-gray-800">Response Status</h4>
106
+ <pre class="text-xs bg-white p-3 rounded-sm border border-gray-200 overflow-x-auto"><%= request["status"] %></pre>
107
+
108
+ <h4 class="mt-4 font-medium text-sm text-gray-800">Response Headers</h4>
109
+ <pre class="text-xs bg-white p-3 rounded-sm border border-gray-200 overflow-x-auto"><%= JSON.pretty_generate(response_data["headers"]) %></pre>
110
+
111
+ <h4 class="mt-4 font-medium text-sm text-gray-800">Response Body</h4>
112
+ <pre class="text-xs bg-white p-3 rounded-sm border border-gray-200 overflow-x-auto"><%= response_data["body"].empty? ? "(empty)" : response_data["body"] %></pre>
113
+ <% end %>
114
+ </section>
115
+ </td>
116
+ </tr>
117
+ <% end %>
118
+ </tbody>
119
+ </table>
120
+ <% end %>
121
+ </div>
78
122
 
79
- <tr id="details-<%= request["id"] %>" class="hidden w-full bg-gray-50 [[open]]:table-row">
80
- <td colspan="5" class="px-2 py-3">
81
- <section>
82
- <h4 class="font-medium text-sm text-gray-800">
83
- Headers
84
- </h4>
85
- <pre class="text-xs bg-white p-3 rounded-sm border border-gray-200 overflow-x-auto"><%= JSON.pretty_generate(request_data["headers"]) %></pre>
86
-
87
- <h4 class="mt-4 font-medium text-sm text-gray-800">
88
- Body
89
- </h4>
90
- <pre class="text-xs bg-white p-3 rounded-sm border border-gray-200 overflow-x-auto"><%= request_data["body"].empty? ? "(empty)" : request_data["body"] %></pre>
91
- </section>
92
- </td>
93
- </tr>
123
+ <div class="col-span-12 bg-white rounded-md p-4 md:col-span-4">
124
+ <h2 class="text-base font-bold">Outbound Requests</h2>
125
+
126
+ <% if saved_requests.empty? %>
127
+ <p class="mt-4 text-sm text-gray-500">No saved requests found. Create JSON files in <code>.requestkit/requests/:namespace/:name.json</code></p>
128
+ <% else %>
129
+ <ul class="mt-4">
130
+ <% saved_requests.each do |namespace, requests| %>
131
+ <li class="mb-4">
132
+ <h3 class="text-sm font-semibold text-gray-700"><%= namespace %></h3>
133
+
134
+ <div class="mt-2 space-y-2">
135
+ <% requests.each do |request| %>
136
+ <div class="flex items-center justify-between px-2 py-0.5 bg-gray-50 rounded-md">
137
+ <div class="flex-1">
138
+ <div class="text-sm font-medium"><%= request["name"] %></div>
139
+
140
+ <div class="text-xs text-gray-500"><%= request["url"] %></div>
141
+ </div>
142
+
143
+ <form method="post" action="/send?namespace=<%= namespace %>&name=<%= request["name"] %>" class="inline">
144
+ <button type="submit" class="px-3 py-1 text-[.675rem] font-medium text-white bg-blue-500 rounded-sm hover:bg-blue-600">
145
+ <%= request["method"] %>
146
+ </button>
147
+ </form>
148
+ </div>
149
+ <% end %>
150
+ </div>
151
+ </li>
94
152
  <% end %>
95
- </tbody>
96
- </table>
97
- <% end %>
153
+ </ul>
154
+ <% end %>
155
+ </div>
98
156
  </div>
99
157
 
100
158
  <script>
@@ -144,3 +202,5 @@
144
202
  </script>
145
203
  </body>
146
204
  </html>
205
+
206
+ <!-- Icons by https://phosphoricons.com/ -->
@@ -1,3 +1,3 @@
1
1
  module Requestkit
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: requestkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rails Designer Developers
@@ -79,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
79
  - !ruby/object:Gem::Version
80
80
  version: '0'
81
81
  requirements: []
82
- rubygems_version: 3.6.9
82
+ rubygems_version: 4.0.0
83
83
  specification_version: 4
84
84
  summary: Local HTTP request toolkit
85
85
  test_files: []