Skip to content

Commit 29ce32b

Browse files
authored
Merge pull request #91 from amazeeio/dev
Bring main in line with Dev
2 parents 378fc68 + 3ea54c8 commit 29ce32b

File tree

9 files changed

+816
-41
lines changed

9 files changed

+816
-41
lines changed

app/api/audit.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
from sqlalchemy.orm import Session
33
from typing import List, Optional
44
from datetime import datetime
5+
from sqlalchemy import distinct, text, cast, String
56
from app.db.database import get_db
67
from app.api.auth import get_current_user_from_auth
78
from app.schemas.models import AuditLogResponse, PaginatedAuditLogResponse, AuditLogMetadata
89
from app.db.models import DBAuditLog, DBUser
9-
from sqlalchemy import distinct
1010
import logging
1111

1212
logger = logging.getLogger(__name__)
@@ -25,11 +25,12 @@ async def get_audit_logs(
2525
user_email: Optional[str] = None,
2626
from_date: Optional[datetime] = None,
2727
to_date: Optional[datetime] = None,
28+
status_code: Optional[str] = None,
2829
):
2930
"""
3031
Retrieve audit logs with optional filtering.
3132
Only accessible by admin users.
32-
event_type and resource_type can be comma-separated lists for multiple values.
33+
event_type, resource_type, and status_code can be comma-separated lists for multiple values.
3334
"""
3435
if not current_user.is_admin:
3536
logger.warning(f"Non-admin user {current_user.id} attempted to access audit logs")
@@ -56,6 +57,9 @@ async def get_audit_logs(
5657
query = query.filter(DBAuditLog.timestamp >= from_date)
5758
if to_date:
5859
query = query.filter(DBAuditLog.timestamp <= to_date)
60+
if status_code:
61+
status_codes = [sc.strip() for sc in status_code.split(',')]
62+
query = query.filter(cast(DBAuditLog.details['status_code'], String).in_(status_codes))
5963

6064
# Get total count
6165
total = query.count()
@@ -96,7 +100,7 @@ async def get_audit_logs_metadata(
96100
current_user: DBUser = Depends(get_current_user_from_auth),
97101
):
98102
"""
99-
Retrieve distinct event types and resource types from audit logs.
103+
Retrieve distinct event types, resource types, and status codes from audit logs.
100104
Only accessible by admin users.
101105
"""
102106
if not current_user.is_admin:
@@ -117,9 +121,16 @@ async def get_audit_logs_metadata(
117121
.filter(DBAuditLog.resource_type != '')
118122
.all()]
119123

124+
# Get distinct status codes from the details JSON field
125+
status_codes = [sc[0] for sc in db.query(distinct(cast(DBAuditLog.details['status_code'], String)))
126+
.filter(cast(DBAuditLog.details['status_code'], String).isnot(None))
127+
.filter(cast(DBAuditLog.details['status_code'], String) != '')
128+
.all()]
129+
120130
return {
121131
"event_types": sorted(event_types),
122-
"resource_types": sorted(resource_types)
132+
"resource_types": sorted(resource_types),
133+
"status_codes": sorted(status_codes, key=lambda x: int(x) if x.isdigit() else 0)
123134
}
124135

125136
except Exception as e:

app/schemas/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ class PaginatedAuditLogResponse(BaseModel):
229229
class AuditLogMetadata(BaseModel):
230230
event_types: List[str]
231231
resource_types: List[str]
232+
status_codes: List[str]
232233
model_config = ConfigDict(from_attributes=True)
233234

234235
# Team schemas

frontend/src/app/admin/audit-logs/page.tsx

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,37 @@ interface AuditLogFilters {
4545
user_email?: string;
4646
from_date?: string;
4747
to_date?: string;
48+
status_code?: string[];
4849
}
4950

5051
const ITEMS_PER_PAGE = 20;
5152

53+
const getStatusCodeDescription = (code: number): string => {
54+
const descriptions: Record<number, string> = {
55+
200: 'OK',
56+
201: 'Created',
57+
204: 'No Content',
58+
301: 'Moved Permanently',
59+
302: 'Found',
60+
304: 'Not Modified',
61+
307: 'Temporary Redirect',
62+
308: 'Permanent Redirect',
63+
400: 'Bad Request',
64+
401: 'Unauthorized',
65+
403: 'Forbidden',
66+
404: 'Not Found',
67+
405: 'Method Not Allowed',
68+
409: 'Conflict',
69+
422: 'Unprocessable Entity',
70+
429: 'Too Many Requests',
71+
500: 'Internal Server Error',
72+
502: 'Bad Gateway',
73+
503: 'Service Unavailable',
74+
504: 'Gateway Timeout',
75+
};
76+
return descriptions[code] || 'Unknown';
77+
};
78+
5279
export default function AuditLogsPage() {
5380
const { toast } = useToast();
5481
const [filters, setFilters] = useState<AuditLogFilters>({
@@ -61,6 +88,7 @@ export default function AuditLogsPage() {
6188
const [itemsPerPage] = useState<number>(ITEMS_PER_PAGE);
6289
const [eventTypeOptions, setEventTypeOptions] = useState<{ value: string; label: string }[]>([]);
6390
const [resourceTypeOptions, setResourceTypeOptions] = useState<{ value: string; label: string }[]>([]);
91+
const [statusCodeOptions, setStatusCodeOptions] = useState<{ value: string; label: string }[]>([]);
6492

6593
const fetchMetadata = useCallback(async () => {
6694
try {
@@ -85,6 +113,15 @@ export default function AuditLogsPage() {
85113
label: type.charAt(0).toUpperCase() + type.slice(1).toLowerCase(),
86114
}))
87115
);
116+
117+
setStatusCodeOptions(
118+
(data.status_codes || [])
119+
.filter(Boolean)
120+
.map((code: string) => ({
121+
value: code,
122+
label: `${code} - ${getStatusCodeDescription(parseInt(code))}`,
123+
}))
124+
);
88125
} catch (error) {
89126
console.error('Error fetching audit logs metadata:', error);
90127
toast({
@@ -107,6 +144,7 @@ export default function AuditLogsPage() {
107144
...(filters.event_type?.length && { event_type: filters.event_type.join(',') }),
108145
...(filters.resource_type?.length && { resource_type: filters.resource_type.join(',') }),
109146
...(filters.user_email && { user_email: filters.user_email }),
147+
...(filters.status_code?.length && { status_code: filters.status_code.join(',') }),
110148
}).toString();
111149

112150
const response = await get(`audit/logs?${queryParams}`, { credentials: 'include' });
@@ -179,7 +217,7 @@ export default function AuditLogsPage() {
179217
<CardTitle>Filters</CardTitle>
180218
</CardHeader>
181219
<CardContent>
182-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4">
220+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-4">
183221
<div className="space-y-2">
184222
<label className="text-sm font-medium">Event Type</label>
185223
<MultiSelect
@@ -197,6 +235,18 @@ export default function AuditLogsPage() {
197235
onValueChange={(value) => handleFilterChange('resource_type', value)}
198236
defaultValue={filters.resource_type || []}
199237
placeholder="Select Resources"
238+
variant="default"
239+
/>
240+
</div>
241+
242+
<div className="space-y-2">
243+
<label className="text-sm font-medium">Status Code</label>
244+
<MultiSelect
245+
options={statusCodeOptions}
246+
onValueChange={(value) => handleFilterChange('status_code', value)}
247+
defaultValue={filters.status_code || []}
248+
placeholder="Select Status Codes"
249+
variant="default"
200250
/>
201251
</div>
202252

frontend/src/app/upgrade/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ export default function PricingTokenPage() {
4545
isConfigLoading,
4646
reset,
4747
} = useUpgrade();
48-
console.log('upgrade page', config);
4948

5049
// Initialize upgrade flow when component mounts or token changes
5150
useEffect(() => {

frontend/src/components/ui/multi-select.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,19 +248,19 @@ export const MultiSelect = React.forwardRef<
248248
</Badge>
249249
)}
250250
</div>
251-
<div className="flex items-center justify-between">
251+
<div className="flex items-center justify-between gap-1">
252252
<XIcon
253-
className="h-4 mx-2 cursor-pointer text-muted-foreground"
253+
className="h-4 mx-2 cursor-pointer text-muted-foreground flex items-center justify-center"
254254
onClick={(event) => {
255255
event.stopPropagation();
256256
handleClear();
257257
}}
258258
/>
259259
<Separator
260260
orientation="vertical"
261-
className="flex min-h-6 h-full"
261+
className="flex min-h-6 h-full mx-1"
262262
/>
263-
<ChevronDown className="h-4 mx-2 cursor-pointer text-muted-foreground" />
263+
<ChevronDown className="h-4 mx-2 cursor-pointer text-muted-foreground flex items-center justify-center" />
264264
</div>
265265
</div>
266266
) : (

frontend/src/stores/use-config.ts

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,62 +12,64 @@ interface ConfigState {
1212
config: Config | null;
1313
loading: boolean;
1414
error: string | null;
15+
isLoaded: boolean;
1516
setConfig: (config: Config) => void;
1617
setLoading: (loading: boolean) => void;
1718
setError: (error: string | null) => void;
18-
loadConfig: () => Promise<void>;
19+
setIsLoaded: (isLoaded: boolean) => void;
20+
loadConfig: () => Promise<Config | null>;
1921
getApiUrl: () => string;
2022
}
2123

22-
const fallbackConfig: Config = {
23-
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8800',
24-
PASSWORDLESS_SIGN_IN: process.env.PASSWORDLESS_SIGN_IN === 'true',
25-
STRIPE_PUBLISHABLE_KEY: process.env.STRIPE_PUBLISHABLE_KEY || '',
26-
};
2724

2825
export const useConfig = create<ConfigState>((set, get) => ({
29-
config: fallbackConfig,
26+
config: null,
3027
loading: false,
3128
error: null,
29+
isLoaded: false,
3230

3331
setConfig: (config) => set({ config, error: null }),
3432
setLoading: (loading) => set({ loading }),
3533
setError: (error) => set({ error, loading: false }),
34+
setIsLoaded: (isLoaded) => set({ isLoaded }),
3635

3736
loadConfig: async () => {
3837
const state = get();
39-
if (state.loading) return; // Prevent multiple simultaneous requests
38+
39+
if (state.isLoaded && state.config) {
40+
return state.config; // Return cached config if already loaded successfully
41+
}
42+
43+
if (state.loading) {
44+
return null; // Return null if already loading
45+
}
4046

4147
set({ loading: true, error: null });
4248

4349
try {
4450
const response = await fetch('/api/config');
45-
console.log(response);
4651
if (!response.ok) {
4752
throw new Error('Failed to load configuration');
4853
}
4954

5055
const config: Config = await response.json();
51-
console.log(config);
52-
set({ config, loading: false, error: null });
56+
set({ config, loading: false, error: null, isLoaded: true });
57+
return config;
5358
} catch (error) {
5459
console.error('Error loading configuration:', error);
55-
console.log('error', fallbackConfig);
5660
set({
57-
config: fallbackConfig,
61+
config: null,
5862
loading: false,
59-
error: error instanceof Error ? error.message : 'Failed to load configuration'
63+
error: error instanceof Error ? error.message : 'Failed to load configuration',
64+
isLoaded: false
6065
});
66+
return null;
6167
}
6268
},
6369

6470
getApiUrl: () => {
6571
const state = get();
66-
return state.config?.NEXT_PUBLIC_API_URL || fallbackConfig.NEXT_PUBLIC_API_URL;
72+
return state.config?.NEXT_PUBLIC_API_URL || 'http://localhost:8800';
6773
},
6874
}));
6975

70-
// Initialize config on store creation (only in browser environment)
71-
if (typeof window !== 'undefined') {
72-
useConfig.getState().loadConfig();
73-
}

frontend/src/stores/use-upgrade.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,13 @@ export const useUpgrade = create<UpgradeState>((set, get) => ({
9191
try {
9292
const configStore = useConfig.getState();
9393

94-
// Always try to load config from API to ensure we have the latest
95-
await configStore.loadConfig();
94+
// Load config and get it directly from the return value
95+
const config = await configStore.loadConfig();
9696

97-
// Get the config from the config store after loading
98-
const updatedConfigStore = useConfig.getState();
99-
const config = updatedConfigStore.config;
100-
console.log('load config', config);
101-
102-
if (config) {
97+
if (config && config.STRIPE_PUBLISHABLE_KEY) {
10398
set({ config, error: null });
10499
} else {
100+
console.error('Config missing or invalid:', config);
105101
set({
106102
error: 'Failed to load configuration. Please try again later.',
107103
config: null

helm/README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,16 +194,21 @@ helm install amazee-ai . -n amazee-ai --create-namespace \
194194

195195
| Parameter | Description | Default |
196196
|-----------|-------------|---------|
197+
| `global.environment` | Global environment setting | `production` |
197198
| `postgresql.enabled` | Deploy Bitnami PostgreSQL chart | `true` |
199+
| `postgresql.external.enabled` | Use external managed PostgreSQL | `false` |
200+
| `postgresql.external.url` | External PostgreSQL connection URL | `postgresql://user:password@host:port/database` |
198201
| `postgresql.auth.postgresPassword` | PostgreSQL password | `postgres` |
199202
| `postgresql.auth.database` | PostgreSQL database name | `postgres_service` |
200203
| `postgresql.primary.persistence.enabled` | Enable persistence for PostgreSQL | `true` |
204+
| `postgresql.primary.persistence.storageClass` | Storage class for PostgreSQL | `standard` |
201205
| `postgresql.primary.persistence.size` | Storage size for PostgreSQL | `10Gi` |
202206
| `backend.enabled` | Deploy backend subchart | `true` |
203207
| `backend.replicas` | Number of backend replicas | `1` |
204208
| `backend.image.repository` | Backend image repository | `ghcr.io/amazeeio/amazee.ai-backend` |
205209
| `backend.image.tag` | Backend image tag | `dev` |
206-
| `backend.database.url` | Database connection URL | `postgresql://postgres:postgres@amazee-ai-postgresql:5432/postgres_service` |
210+
| `backend.image.pullPolicy` | Backend image pull policy | `IfNotPresent` |
211+
| `backend.database.url` | Database connection URL (auto-generated if empty) | `""` |
207212
| `backend.secretKey` | Key used to hash passwords stored in the database | `my-secret-key` |
208213
| `backend.stripeSecretKey` | Stripe secret key | `sk_test_your_stripe_secret_key` |
209214
| `backend.webhookSig` | Webhook signature (only needed for local development with Stripe CLI) | `""` |
@@ -224,17 +229,24 @@ helm install amazee-ai . -n amazee-ai --create-namespace \
224229
| `frontend.replicas` | Number of frontend replicas | `1` |
225230
| `frontend.image.repository` | Frontend image repository | `ghcr.io/amazeeio/amazee.ai-frontend` |
226231
| `frontend.image.tag` | Frontend image tag | `dev` |
227-
| `frontend.apiUrl` | Backend API URL | `http://backend:8800` |
232+
| `frontend.image.pullPolicy` | Frontend image pull policy | `IfNotPresent` |
233+
| `frontend.apiUrl` | Backend API URL (auto-generated if empty) | `""` |
228234
| `frontend.stripePublishableKey` | Stripe publishable key | `pk_test_your_stripe_publishable_key` |
229235
| `frontend.passwordlessSignIn` | Enable passwordless sign-in | `true` |
230-
| `frontend.resources.requests.memory` | Frontend memory request | `256Mi` |
236+
| `frontend.resources.requests.memory` | Frontend memory request | `512Mi` |
231237
| `frontend.resources.requests.cpu` | Frontend CPU request | `250m` |
232-
| `frontend.resources.limits.memory` | Frontend memory limit | `512Mi` |
238+
| `frontend.resources.limits.memory` | Frontend memory limit | `1Gi` |
233239
| `frontend.resources.limits.cpu` | Frontend CPU limit | `500m` |
234240
| `ingress.enabled` | Enable backend API ingress | `true` |
235241
| `ingress.className` | Backend ingress class name | `nginx` |
242+
| `ingress.annotations` | Backend ingress annotations | See values.yaml |
243+
| `ingress.hosts` | Backend ingress hosts | `api.amazee-ai.local` |
244+
| `ingress.tls` | Backend ingress TLS configuration | `[]` |
236245
| `frontendIngress.enabled` | Enable frontend web interface ingress | `true` |
237246
| `frontendIngress.className` | Frontend ingress class name | `nginx` |
247+
| `frontendIngress.annotations` | Frontend ingress annotations | See values.yaml |
248+
| `frontendIngress.hosts` | Frontend ingress hosts | `amazee-ai.local` |
249+
| `frontendIngress.tls` | Frontend ingress TLS configuration | `[]` |
238250

239251
## Environment Variables
240252

0 commit comments

Comments
 (0)