Commit 4b5d694e authored by ThinhNC's avatar ThinhNC

feat(api-keys): add API key management interface

parent 060f545a
......@@ -10,6 +10,7 @@ import { JobDetail } from './pages/JobDetail';
import { Users } from './pages/Users';
import { Logs } from './pages/Logs';
import { Profile } from './pages/Profile';
import { ApiKeys } from './pages/ApiKeys';
import { ForgotPassword } from './pages/ForgotPassword';
import { ResetPassword } from './pages/ResetPassword';
import { VerifyEmail } from './pages/VerifyEmail';
......@@ -59,6 +60,7 @@ export default function App() {
{/* Profile */}
<Route path="profile" element={<Profile />} />
<Route path="api-keys" element={<ApiKeys />} />
{/* Admin Only Routes */}
<Route
......
......@@ -9,6 +9,7 @@ const TITLES: Record<string, string> = {
'/reset-password': 'Đặt lại mật khẩu | Data Crawler',
'/verify-email': 'Xác minh email | Data Crawler',
'/profile': 'Hồ sơ cá nhân | Data Crawler',
'/api-keys': 'Quản lý API Key | Data Crawler',
'/users': 'Quản lý người dùng | Data Crawler',
'/logs': 'Nhật ký hệ thống | Data Crawler',
};
......
import React from 'react';
import { Link, useNavigate, useLocation, Outlet } from 'react-router-dom';
import { useAuth } from '../context/auth';
import { Database, Users, History, User, LogOut, Compass } from 'lucide-react';
import { Database, Users, History, User, LogOut, Compass, KeyRound } from 'lucide-react';
export const DashboardLayout: React.FC = () => {
const { user, logout, isAdmin } = useAuth();
......@@ -20,6 +20,12 @@ export const DashboardLayout: React.FC = () => {
icon: <Database className="h-5 w-5" />,
allowed: true,
},
{
name: 'Quản lý API Key',
path: '/api-keys',
icon: <KeyRound className="h-5 w-5" />,
allowed: true,
},
{
name: 'Quản lý Users',
path: '/users',
......
This diff is collapsed.
......@@ -42,6 +42,9 @@ const ACTION_COLORS: Record<string, string> = {
RESET_PASSWORD: 'bg-sky-100 text-sky-700',
VERIFY_EMAIL: 'bg-teal-100 text-teal-700',
RESEND_VERIFICATION: 'bg-teal-100 text-teal-700',
CREATE_API_KEY: 'bg-emerald-100 text-emerald-700',
UPDATE_API_KEY_STATUS: 'bg-indigo-100 text-indigo-700',
REVOKE_API_KEY: 'bg-rose-100 text-rose-700',
};
const ACTION_OPTIONS = [
......@@ -61,6 +64,7 @@ const ACTION_OPTIONS = [
'ADMIN_UPDATE_USER',
'ADMIN_DELETE_USER',
'CREATE_API_KEY',
'UPDATE_API_KEY_STATUS',
'REVOKE_API_KEY',
'CREATE_WEBHOOK_CONFIG',
'DELETE_WEBHOOK_CONFIG',
......
import axios from 'axios';
export interface ApiKeyRecord {
id: string;
userId: string;
name: string;
keyPrefix: string;
isActive: boolean;
expiresAt: string | null;
lastUsedAt: string | null;
createdAt: string;
updatedAt: string;
}
export interface CreatedApiKey extends ApiKeyRecord {
rawKey: string;
}
interface ApiResponse<T> {
success: boolean;
data: T;
}
export const API_BASE_URL = (import.meta.env.VITE_API_BASE_URL || 'http://171.247.68.96:4011/api/v1')
.replace(/\/$/, '');
......@@ -73,6 +94,27 @@ api.interceptors.response.use(
export const getApiUrl = (path: string) =>
`${API_BASE_URL}/${path.replace(/^\/+/, '')}`;
export const apiKeysApi = {
async list(): Promise<ApiKeyRecord[]> {
const response = await api.get<ApiResponse<ApiKeyRecord[]>>('/api-keys');
return response.data.data;
},
async create(input: { name: string; expiresAt: string | null }): Promise<CreatedApiKey> {
const response = await api.post<ApiResponse<CreatedApiKey>>('/api-keys', input);
return response.data.data;
},
async setActive(id: string, isActive: boolean): Promise<ApiKeyRecord> {
const response = await api.patch<ApiResponse<ApiKeyRecord>>(`/api-keys/${id}`, { isActive });
return response.data.data;
},
async revoke(id: string): Promise<void> {
await api.delete(`/api-keys/${id}`);
},
};
function notifyAuthExpired() {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
......
......@@ -15,6 +15,11 @@ const MESSAGE_TRANSLATIONS: Record<string, string> = {
'Password must contain at least one uppercase letter': 'Mật khẩu phải có ít nhất một chữ hoa.',
'Password must contain at least one number': 'Mật khẩu phải có ít nhất một chữ số.',
'Password must contain at least one special character': 'Mật khẩu phải có ít nhất một ký tự đặc biệt.',
'API Key expiration must be in the future': 'Thời điểm hết hạn của API Key phải ở trong tương lai.',
'Invalid ISO datetime format for expiration': 'Thời điểm hết hạn của API Key không đúng định dạng.',
'API Key status is required': 'Vui lòng chọn trạng thái cho API Key.',
'API Key status must be a boolean': 'Trạng thái API Key không hợp lệ.',
'API key not found': 'Không tìm thấy API Key.',
};
interface ApiErrorShape {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment