agentgate 0.3.2 → 0.5.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.
- package/README.md +118 -4
- package/package.json +3 -2
- package/public/mobile.css +178 -0
- package/public/style.css +129 -0
- package/src/index.js +304 -51
- package/src/lib/agentNotifier.js +82 -1
- package/src/lib/db.js +868 -9
- package/src/lib/socketManager.js +73 -0
- package/src/routes/agents.js +117 -3
- package/src/routes/linkedin.js +30 -10
- package/src/routes/memento.js +106 -0
- package/src/routes/queue.js +238 -4
- package/src/routes/services.js +87 -0
- package/src/routes/ui/access.js +290 -0
- package/src/routes/ui/auth.js +149 -0
- package/src/routes/ui/calendar.js +65 -14
- package/src/routes/ui/fitbit.js +63 -14
- package/src/routes/ui/home.js +313 -0
- package/src/routes/ui/index.js +52 -35
- package/src/routes/ui/keys.js +852 -0
- package/src/routes/ui/linkedin.js +75 -19
- package/src/routes/ui/mastodon.js +70 -18
- package/src/routes/ui/mementos.js +363 -0
- package/src/routes/ui/messages.js +588 -0
- package/src/routes/ui/queue.js +599 -0
- package/src/routes/ui/reddit.js +63 -14
- package/src/routes/ui/services.js +46 -0
- package/src/routes/ui/settings.js +59 -0
- package/src/routes/ui/shared.js +269 -0
- package/src/routes/ui/youtube.js +63 -14
- package/src/routes/ui-new.js +196 -0
- package/src/routes/webhooks.js +247 -0
- package/src/routes/ui.js +0 -1901
package/README.md
CHANGED
|
@@ -135,6 +135,108 @@ agentgate can notify your agent when queue items are completed, failed, or rejec
|
|
|
135
135
|
|
|
136
136
|
Compatible with OpenClaw's `/hooks/wake` endpoint. See [OpenClaw webhook docs](https://docs.openclaw.ai/automation/webhook).
|
|
137
137
|
|
|
138
|
+
## Service Access Control
|
|
139
|
+
|
|
140
|
+
Control which agents can access specific services. Each service/account can be configured with one of three access modes:
|
|
141
|
+
|
|
142
|
+
- **All** (default) - All agents can access
|
|
143
|
+
- **Allowlist** - Only listed agents can access
|
|
144
|
+
- **Denylist** - All agents except listed ones can access
|
|
145
|
+
|
|
146
|
+
**Setup in Admin UI:**
|
|
147
|
+
1. Go to **Services** in the admin navigation
|
|
148
|
+
2. Click on a service to configure access
|
|
149
|
+
3. Set access mode and add/remove agents from the list
|
|
150
|
+
|
|
151
|
+
**API Endpoint:**
|
|
152
|
+
```bash
|
|
153
|
+
# Check your access to services
|
|
154
|
+
GET /api/services
|
|
155
|
+
Authorization: Bearer rms_your_key
|
|
156
|
+
|
|
157
|
+
# Response shows only services you can access
|
|
158
|
+
{ "services": [{ "service": "github", "account_name": "monteslu", "access_mode": "all" }, ...] }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
When an agent lacks access to a service, API calls return `403 Forbidden` with a clear error message.
|
|
162
|
+
|
|
163
|
+
## Auth Bypass (Trusted Agents)
|
|
164
|
+
|
|
165
|
+
For highly trusted agents, you can bypass the write queue entirely. Agents with `bypass_auth` enabled execute write operations immediately without human approval.
|
|
166
|
+
|
|
167
|
+
> ⚠️ **Use with extreme caution.** Only enable for agents you completely trust with unsupervised write access.
|
|
168
|
+
|
|
169
|
+
**Setup in Admin UI:**
|
|
170
|
+
1. Go to **API Keys**
|
|
171
|
+
2. Click **Configure** next to the agent
|
|
172
|
+
3. Enable **Auth Bypass**
|
|
173
|
+
|
|
174
|
+
**Behavior:**
|
|
175
|
+
- All write operations (POST/PUT/DELETE) execute immediately
|
|
176
|
+
- No queue entries are created
|
|
177
|
+
- The agent is effectively operating unsupervised
|
|
178
|
+
- Reads work the same as before
|
|
179
|
+
|
|
180
|
+
This is useful for automation agents that need to perform routine operations without human bottlenecks.
|
|
181
|
+
|
|
182
|
+
## GitHub Webhooks
|
|
183
|
+
|
|
184
|
+
agentgate can receive GitHub webhook events and forward them to agent webhooks, enabling reactive workflows.
|
|
185
|
+
|
|
186
|
+
**Setup:**
|
|
187
|
+
1. In GitHub repo settings, add a webhook:
|
|
188
|
+
- URL: `https://your-agentgate.com/webhooks/github`
|
|
189
|
+
- Content type: `application/json`
|
|
190
|
+
- Secret: (configure in agentgate settings)
|
|
191
|
+
- Events: Choose which events to receive
|
|
192
|
+
|
|
193
|
+
2. Configure webhook forwarding in agentgate Admin UI under **Settings**
|
|
194
|
+
|
|
195
|
+
**Supported Events:**
|
|
196
|
+
- Push events
|
|
197
|
+
- Pull request events (opened, closed, merged, review requested)
|
|
198
|
+
- Issue events
|
|
199
|
+
- Workflow run events
|
|
200
|
+
|
|
201
|
+
Agents receive webhooks in the same format as other notifications, enabling them to react to repository changes in real-time.
|
|
202
|
+
|
|
203
|
+
## Agent Avatars
|
|
204
|
+
|
|
205
|
+
Agents can have custom avatars displayed in the admin UI for easy identification.
|
|
206
|
+
|
|
207
|
+
**Setup in Admin UI:**
|
|
208
|
+
1. Go to **API Keys**
|
|
209
|
+
2. Click the avatar placeholder next to an agent name
|
|
210
|
+
3. Upload an image (PNG, JPG, GIF, WebP supported)
|
|
211
|
+
|
|
212
|
+
Avatars are stored locally and displayed throughout the UI wherever the agent is referenced.
|
|
213
|
+
|
|
214
|
+
## Memento (Agent Memory)
|
|
215
|
+
|
|
216
|
+
Durable memory storage for agents. Store and retrieve memory snapshots tagged with keywords for long-term context preservation.
|
|
217
|
+
|
|
218
|
+
📖 **[See full documentation](docs/memento.md)** (coming soon)
|
|
219
|
+
|
|
220
|
+
**Key Features:**
|
|
221
|
+
- **Append-only** - Mementos are immutable once stored
|
|
222
|
+
- **Keyword tagging** - Each memento has 1-10 keywords (stemmed for matching)
|
|
223
|
+
- **Two-step retrieval** - Search returns metadata, fetch returns full content
|
|
224
|
+
|
|
225
|
+
**Quick API Overview:**
|
|
226
|
+
```bash
|
|
227
|
+
# Store a memento
|
|
228
|
+
POST /api/agents/memento
|
|
229
|
+
{ "content": "Your memory...", "keywords": ["project", "decision"] }
|
|
230
|
+
|
|
231
|
+
# Search by keyword
|
|
232
|
+
GET /api/agents/memento/search?keywords=project&limit=10
|
|
233
|
+
|
|
234
|
+
# Fetch full content
|
|
235
|
+
GET /api/agents/memento/42,38,15
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Each agent sees only their own mementos. Maximum 12KB per memento.
|
|
239
|
+
|
|
138
240
|
## Inter-Agent Messaging
|
|
139
241
|
|
|
140
242
|
Agents can communicate with each other through agentgate, with optional human oversight.
|
|
@@ -146,14 +248,30 @@ Agents can communicate with each other through agentgate, with optional human ov
|
|
|
146
248
|
- Configure agent webhooks in Admin UI under **API Keys > Configure**
|
|
147
249
|
- Endpoints: `/api/agents/messageable`, `/api/agents/message`, `/api/agents/messages`, `/api/agents/status`
|
|
148
250
|
|
|
251
|
+
### Broadcasts
|
|
252
|
+
|
|
253
|
+
Send messages to all agents at once:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
POST /api/agents/broadcast
|
|
257
|
+
{ "message": "Team announcement: deployment starting" }
|
|
258
|
+
|
|
259
|
+
# Response
|
|
260
|
+
{ "delivered": ["Agent1", "Agent2"], "failed": [], "total": 2 }
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Broadcasts are stored in the database and appear in each agent's message history. View broadcast history in the Admin UI under **Messages → Broadcast**.
|
|
264
|
+
|
|
149
265
|
## Agent Registry
|
|
150
266
|
|
|
151
267
|
Manage your agents in the admin UI at `/ui/keys`. Each agent has:
|
|
152
268
|
|
|
153
269
|
- **Name** - Unique identifier (case-insensitive)
|
|
154
270
|
- **API Key** - Bearer token for agent → agentgate authentication (shown once at creation)
|
|
271
|
+
- **Avatar** - Optional image for visual identification
|
|
155
272
|
- **Webhook URL** (optional) - Endpoint for agentgate → agent notifications
|
|
156
273
|
- **Webhook Token** (optional) - Bearer token for webhook authentication
|
|
274
|
+
- **Auth Bypass** (optional) - Skip write queue for trusted agents
|
|
157
275
|
|
|
158
276
|
When an agent's webhook is configured, agentgate will POST notifications for:
|
|
159
277
|
- Queue item status changes (approved/rejected/completed/failed)
|
|
@@ -291,12 +409,8 @@ BASE_URL=https://agentgate.yourdomain.com npm start
|
|
|
291
409
|
|
|
292
410
|
## TODO
|
|
293
411
|
|
|
294
|
-
- [ ] Per-agent service access control - different agents can access different services/accounts
|
|
295
412
|
- [ ] Fine-grained endpoint control per service - whitelist/blacklist individual endpoints (even for read operations)
|
|
296
413
|
|
|
297
414
|
## License
|
|
298
415
|
|
|
299
416
|
ISC
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "API gateway for AI agents with human-in-the-loop write approval",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
"express": "^4.18.2",
|
|
53
53
|
"hsync": "^0.33.0",
|
|
54
54
|
"nanoid": "^5.0.0",
|
|
55
|
-
"socket.io": "^4.8.3"
|
|
55
|
+
"socket.io": "^4.8.3",
|
|
56
|
+
"stemmer": "^2.0.1"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"eslint": "^9.0.0",
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/* Mobile Responsive Styles for agentgate */
|
|
2
|
+
/* Issue #39 */
|
|
3
|
+
|
|
4
|
+
/* Tablet breakpoint */
|
|
5
|
+
@media (max-width: 768px) {
|
|
6
|
+
body {
|
|
7
|
+
padding: 20px;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
h1 {
|
|
11
|
+
font-size: 1.75rem;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.card {
|
|
15
|
+
padding: 20px;
|
|
16
|
+
margin: 16px 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Nav buttons wrap */
|
|
20
|
+
[style*="display: flex"][style*="gap: 12px"] {
|
|
21
|
+
flex-wrap: wrap;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.nav-btn {
|
|
25
|
+
padding: 12px 16px;
|
|
26
|
+
font-size: 14px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Tables scroll horizontally */
|
|
30
|
+
table {
|
|
31
|
+
display: block;
|
|
32
|
+
overflow-x: auto;
|
|
33
|
+
white-space: nowrap;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Full width inputs */
|
|
37
|
+
input[type="text"],
|
|
38
|
+
input[type="password"],
|
|
39
|
+
input[type="url"],
|
|
40
|
+
textarea {
|
|
41
|
+
width: 100% !important;
|
|
42
|
+
min-width: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Stack filter bar */
|
|
46
|
+
.filter-bar {
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
align-items: stretch;
|
|
49
|
+
gap: 12px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.filter-bar .clear-section {
|
|
53
|
+
margin-left: 0;
|
|
54
|
+
margin-top: 8px;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Mobile breakpoint */
|
|
59
|
+
@media (max-width: 480px) {
|
|
60
|
+
body {
|
|
61
|
+
padding: 12px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
h1 {
|
|
65
|
+
font-size: 1.4rem;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
h2 {
|
|
69
|
+
font-size: 1.1rem;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.card {
|
|
73
|
+
padding: 16px;
|
|
74
|
+
margin: 12px 0;
|
|
75
|
+
border-radius: 12px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Header stacks vertically */
|
|
79
|
+
[style*="display: flex"][style*="justify-content: space-between"] {
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
gap: 16px;
|
|
82
|
+
align-items: stretch;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Nav buttons full width */
|
|
86
|
+
.nav-btn {
|
|
87
|
+
width: 100%;
|
|
88
|
+
justify-content: center;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Touch-friendly buttons */
|
|
92
|
+
button,
|
|
93
|
+
.btn-primary,
|
|
94
|
+
.btn-secondary,
|
|
95
|
+
.btn-danger,
|
|
96
|
+
input[type="submit"] {
|
|
97
|
+
min-height: 44px;
|
|
98
|
+
padding: 12px 20px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.btn-sm {
|
|
102
|
+
min-height: 40px;
|
|
103
|
+
padding: 10px 16px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Form actions stack */
|
|
107
|
+
.queue-actions,
|
|
108
|
+
.message-actions {
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
gap: 12px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.queue-actions input,
|
|
114
|
+
.message-actions input {
|
|
115
|
+
width: 100% !important;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.queue-actions button,
|
|
119
|
+
.message-actions button {
|
|
120
|
+
width: 100%;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Copyable text wraps */
|
|
124
|
+
.copyable {
|
|
125
|
+
flex-wrap: wrap;
|
|
126
|
+
word-break: break-all;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
pre {
|
|
130
|
+
padding: 16px;
|
|
131
|
+
font-size: 12px;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
code {
|
|
135
|
+
font-size: 12px;
|
|
136
|
+
word-break: break-all;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Hide nav divider on mobile */
|
|
140
|
+
.nav-divider {
|
|
141
|
+
display: none;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Badge position adjustment */
|
|
145
|
+
.badge {
|
|
146
|
+
top: -6px;
|
|
147
|
+
right: -6px;
|
|
148
|
+
width: 20px;
|
|
149
|
+
height: 20px;
|
|
150
|
+
font-size: 10px;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Account items stack */
|
|
154
|
+
.account-item {
|
|
155
|
+
flex-direction: column;
|
|
156
|
+
align-items: flex-start;
|
|
157
|
+
gap: 8px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Login card */
|
|
161
|
+
.login-card {
|
|
162
|
+
margin: 40px auto;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* Prevent horizontal scroll */
|
|
167
|
+
html, body {
|
|
168
|
+
overflow-x: hidden;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Safe area padding for notched devices */
|
|
172
|
+
@media (max-width: 480px) {
|
|
173
|
+
body {
|
|
174
|
+
padding-left: env(safe-area-inset-left, 12px);
|
|
175
|
+
padding-right: env(safe-area-inset-right, 12px);
|
|
176
|
+
padding-bottom: env(safe-area-inset-bottom, 12px);
|
|
177
|
+
}
|
|
178
|
+
}
|
package/public/style.css
CHANGED
|
@@ -258,6 +258,7 @@ button, input[type="submit"] {
|
|
|
258
258
|
display: inline-flex;
|
|
259
259
|
align-items: center;
|
|
260
260
|
gap: 8px;
|
|
261
|
+
white-space: nowrap;
|
|
261
262
|
}
|
|
262
263
|
|
|
263
264
|
.nav-btn-default {
|
|
@@ -285,6 +286,19 @@ button, input[type="submit"] {
|
|
|
285
286
|
transform: translateY(-2px);
|
|
286
287
|
}
|
|
287
288
|
|
|
289
|
+
.nav-btn-danger {
|
|
290
|
+
background: rgba(239, 68, 68, 0.1);
|
|
291
|
+
color: #f87171;
|
|
292
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.nav-btn-danger:hover {
|
|
296
|
+
background: rgba(239, 68, 68, 0.2);
|
|
297
|
+
color: #fca5a5;
|
|
298
|
+
border-color: rgba(239, 68, 68, 0.5);
|
|
299
|
+
transform: translateY(-2px);
|
|
300
|
+
}
|
|
301
|
+
|
|
288
302
|
.account-item {
|
|
289
303
|
display: flex;
|
|
290
304
|
justify-content: space-between;
|
|
@@ -557,6 +571,40 @@ body > p:first-of-type {
|
|
|
557
571
|
margin: 0 8px;
|
|
558
572
|
}
|
|
559
573
|
|
|
574
|
+
/* Status badges for account auth state */
|
|
575
|
+
.badge-success {
|
|
576
|
+
display: inline-block;
|
|
577
|
+
padding: 2px 8px;
|
|
578
|
+
border-radius: 4px;
|
|
579
|
+
font-size: 12px;
|
|
580
|
+
font-weight: 500;
|
|
581
|
+
background: rgba(16, 185, 129, 0.15);
|
|
582
|
+
color: #34d399;
|
|
583
|
+
border: 1px solid rgba(16, 185, 129, 0.3);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.badge-error {
|
|
587
|
+
display: inline-block;
|
|
588
|
+
padding: 2px 8px;
|
|
589
|
+
border-radius: 4px;
|
|
590
|
+
font-size: 12px;
|
|
591
|
+
font-weight: 500;
|
|
592
|
+
background: rgba(239, 68, 68, 0.15);
|
|
593
|
+
color: #f87171;
|
|
594
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.badge-warning {
|
|
598
|
+
display: inline-block;
|
|
599
|
+
padding: 2px 8px;
|
|
600
|
+
border-radius: 4px;
|
|
601
|
+
font-size: 12px;
|
|
602
|
+
font-weight: 500;
|
|
603
|
+
background: rgba(245, 158, 11, 0.15);
|
|
604
|
+
color: #fbbf24;
|
|
605
|
+
border: 1px solid rgba(245, 158, 11, 0.3);
|
|
606
|
+
}
|
|
607
|
+
|
|
560
608
|
/* Scrollbar styling */
|
|
561
609
|
::-webkit-scrollbar {
|
|
562
610
|
width: 8px;
|
|
@@ -591,3 +639,84 @@ body > p:first-of-type {
|
|
|
591
639
|
border-radius: 8px;
|
|
592
640
|
border: 1px solid rgba(16, 185, 129, 0.3);
|
|
593
641
|
}
|
|
642
|
+
|
|
643
|
+
/* Avatar component */
|
|
644
|
+
.avatar {
|
|
645
|
+
display: inline-flex;
|
|
646
|
+
align-items: center;
|
|
647
|
+
justify-content: center;
|
|
648
|
+
border-radius: 50%;
|
|
649
|
+
overflow: hidden;
|
|
650
|
+
flex-shrink: 0;
|
|
651
|
+
position: relative;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.avatar img {
|
|
655
|
+
width: 100%;
|
|
656
|
+
height: 100%;
|
|
657
|
+
object-fit: cover;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
.avatar-initials {
|
|
661
|
+
color: white;
|
|
662
|
+
font-weight: 600;
|
|
663
|
+
font-size: 0.875em;
|
|
664
|
+
text-transform: uppercase;
|
|
665
|
+
display: flex;
|
|
666
|
+
align-items: center;
|
|
667
|
+
justify-content: center;
|
|
668
|
+
width: 100%;
|
|
669
|
+
height: 100%;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.avatar-sm {
|
|
673
|
+
width: 24px;
|
|
674
|
+
height: 24px;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
.avatar-md {
|
|
678
|
+
width: 32px;
|
|
679
|
+
height: 32px;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
.avatar-lg {
|
|
683
|
+
width: 48px;
|
|
684
|
+
height: 48px;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/* Agent name with avatar */
|
|
688
|
+
.agent-with-avatar {
|
|
689
|
+
display: inline-flex;
|
|
690
|
+
align-items: center;
|
|
691
|
+
gap: 8px;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/* Small and extra-small buttons */
|
|
695
|
+
.btn-xs {
|
|
696
|
+
padding: 2px 8px;
|
|
697
|
+
font-size: 11px;
|
|
698
|
+
border-radius: 4px;
|
|
699
|
+
background: rgba(99, 102, 241, 0.15);
|
|
700
|
+
color: #818cf8;
|
|
701
|
+
border: 1px solid rgba(99, 102, 241, 0.3);
|
|
702
|
+
cursor: pointer;
|
|
703
|
+
transition: all 0.2s;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
.btn-xs:hover {
|
|
707
|
+
background: rgba(99, 102, 241, 0.25);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.btn-danger {
|
|
711
|
+
background: rgba(239, 68, 68, 0.15);
|
|
712
|
+
color: #f87171;
|
|
713
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
714
|
+
padding: 8px 16px;
|
|
715
|
+
border-radius: 6px;
|
|
716
|
+
cursor: pointer;
|
|
717
|
+
transition: all 0.2s;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
.btn-danger:hover {
|
|
721
|
+
background: rgba(239, 68, 68, 0.25);
|
|
722
|
+
}
|