mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-08 11:06:37 +08:00
usagestats v0
This commit is contained in:
parent
f871e2b8bb
commit
595cf6bf46
76
app/components/usage-stats/UsageStats.module.scss
Normal file
76
app/components/usage-stats/UsageStats.module.scss
Normal file
@ -0,0 +1,76 @@
|
||||
// UsageStats.module.scss
|
||||
|
||||
.usageStatsContainer {
|
||||
position: fixed; // Fixed position to overlay on top of the content
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0.5); // Semi-transparent background to obscure the content
|
||||
z-index: 1000; // High z-index to ensure it's on top of other elements
|
||||
}
|
||||
|
||||
.usageStatsModal {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
width: 80%;
|
||||
max-width: 600px; // Maximum width of the modal
|
||||
z-index: 1001; // Ensure the modal is above the semi-transparent background
|
||||
}
|
||||
|
||||
// ... Add more styles for headings, buttons, tables, etc.
|
||||
.closeButton {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
.dropdown {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 10px 20px;
|
||||
margin-right: 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
// ... Add more styles as needed
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
th, td {
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.usageStatsModal {
|
||||
width: 95%;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
79
app/components/usage-stats/UsageStats.tsx
Normal file
79
app/components/usage-stats/UsageStats.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
// UsageStats.tsx
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { getAvailableDateKeys, getSignInCountForPeriod, getDetailsByUser } from './app/utils/cloud/redisRestClient';
|
||||
import styles from './UsageStats.module.scss'; // Assume you have a separate SCSS module for UsageStats
|
||||
|
||||
const UsageStats: React.FC<{ onClose: () => void }> = ({ onClose }) => {
|
||||
const [dateKeys, setDateKeys] = useState<string[]>([]);
|
||||
const [selectedDateKey, setSelectedDateKey] = useState<string>('');
|
||||
const [signInCount, setSignInCount] = useState<number>(0);
|
||||
const [userDetails, setUserDetails] = useState<Record<string, number>>({});
|
||||
const [showDrillDown, setShowDrillDown] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const availableDateKeys = await getAvailableDateKeys();
|
||||
setDateKeys(availableDateKeys);
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const handleDateChange = async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const dateKey = event.target.value;
|
||||
setSelectedDateKey(dateKey);
|
||||
const count = await getSignInCountForPeriod(dateKey);
|
||||
setSignInCount(count);
|
||||
setShowDrillDown(false);
|
||||
};
|
||||
|
||||
const handleDrillDown = async () => {
|
||||
const details = await getDetailsByUser(selectedDateKey);
|
||||
setUserDetails(details);
|
||||
setShowDrillDown(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.usageStatsContainer}>
|
||||
<div className={styles.usageStatsModal}>
|
||||
<h1>Usage Stats</h1>
|
||||
<select value={selectedDateKey} onChange={handleDateChange}>
|
||||
{dateKeys.map(dateKey => (
|
||||
<option key={dateKey} value={dateKey}>
|
||||
{dateKey}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p>Number of events: {signInCount}</p>
|
||||
<button onClick={handleDrillDown}>Drill-down</button>
|
||||
{showDrillDown && (
|
||||
|
||||
|
||||
{/* ... other UI elements ... */}
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<th>Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(userDetails).map(([email, count]) => (
|
||||
<tr key={email}>
|
||||
<td>{email}</td>
|
||||
<td>{count}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
<button className={styles.closeButton} onClick={onClose}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsageStats;
|
@ -60,3 +60,35 @@ export const incrementTokenCounts = async (
|
||||
console.error('Failed to increment token counts in Redis via Upstash', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const getAvailableDateKeys = async (): Promise<string[]> => {
|
||||
try {
|
||||
const keys = await redis.keys('signin_count:*');
|
||||
return keys.map(key => key.split(':')[1]);
|
||||
} catch (error) {
|
||||
console.error('Failed to get keys from Redis', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const getSignInCountForPeriod = async (dateKey: string): Promise<number> => {
|
||||
try {
|
||||
const counts = await redis.hgetall(`signin_count:${dateKey}`);
|
||||
return Object.values(counts).reduce((total, count) => total + parseInt(count, 10), 0);
|
||||
} catch (error) {
|
||||
console.error(`Failed to get sign-in count for period ${dateKey}`, error);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
export const getDetailsByUser = async (dateKey: string): Promise<Record<string, number>> => {
|
||||
try {
|
||||
const counts = await redis.hgetall(`signin_count:${dateKey}`);
|
||||
return counts;
|
||||
} catch (error) {
|
||||
console.error(`Failed to get details by user for period ${dateKey}`, error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user