mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-31 01:52:30 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e984f376c | ||
|
|
7470125a23 | ||
|
|
469d254e2b | ||
|
|
7fde7ee19a | ||
|
|
d543508a23 | ||
|
|
f440069912 |
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -293,7 +293,7 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bichon-admin"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"bichon-core",
|
||||
"console",
|
||||
@@ -312,7 +312,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bichon-cli"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bichon-core",
|
||||
@@ -338,7 +338,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bichon-core"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"async-imap",
|
||||
"base64 0.22.1",
|
||||
@@ -396,7 +396,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bichon-server"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"bichon-core",
|
||||
"bichon-smtp",
|
||||
@@ -421,7 +421,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bichon-smtp"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bichon-core",
|
||||
|
||||
@@ -11,7 +11,7 @@ members = [
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
edition = "2021"
|
||||
|
||||
[workspace.dependencies]
|
||||
|
||||
@@ -43,7 +43,7 @@ async fn run_interactive() {
|
||||
|
||||
let main_options = vec![
|
||||
"Reset Admin Password",
|
||||
"Migrate Legacy v0.3.7 Storage to v1.0.0",
|
||||
"Migrate Legacy v0.3.7 Storage to v1.0.x",
|
||||
"Exit",
|
||||
];
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use indicatif::{ProgressBar, ProgressStyle};
|
||||
pub fn handle_migration(theme: &ColorfulTheme) {
|
||||
println!(
|
||||
"\n{}",
|
||||
style("MIGRATION: Bichon v0.3.7 Storage Architecture → v1.0.0")
|
||||
style("MIGRATION: Bichon v0.3.7 Storage Architecture → v1.0.x")
|
||||
.bold()
|
||||
.yellow()
|
||||
);
|
||||
@@ -20,7 +20,7 @@ pub fn handle_migration(theme: &ColorfulTheme) {
|
||||
"{}",
|
||||
style(
|
||||
"This tool migrates data from the legacy v0.3.7 Tantivy-based storage \
|
||||
architecture to the new v1.0.0 \
|
||||
architecture to the new v1.0.x \
|
||||
separated index and Fjall-backed storage format."
|
||||
)
|
||||
.dim()
|
||||
@@ -32,7 +32,7 @@ pub fn handle_migration(theme: &ColorfulTheme) {
|
||||
"Legacy v0.3.7 architecture:\n\
|
||||
• envelope metadata stored in Tantivy\n\
|
||||
• message data stored in Tantivy\n\n\
|
||||
New v1.0.0 architecture:\n\
|
||||
New v1.0.x architecture:\n\
|
||||
• mail indexes stored in Tantivy\n\
|
||||
• attachment indexes stored in Tantivy\n\
|
||||
• raw message data stored in Fjall\n\
|
||||
@@ -172,7 +172,7 @@ pub fn handle_migration(theme: &ColorfulTheme) {
|
||||
println!(
|
||||
"{} {}",
|
||||
style("✔").green(),
|
||||
style("Legacy v0.x Tantivy-based storage detected. Migration to v1.0 is required.")
|
||||
style("Legacy v0.3.7 Tantivy-based storage detected. Migration to v1.0 is required.")
|
||||
.yellow()
|
||||
);
|
||||
}
|
||||
@@ -180,7 +180,7 @@ pub fn handle_migration(theme: &ColorfulTheme) {
|
||||
println!(
|
||||
"{} {}",
|
||||
style("✔").green(),
|
||||
style("No legacy v0.x storage layout was detected at the specified paths.").green()
|
||||
style("No legacy v0.3.7 storage layout was detected at the specified paths.").green()
|
||||
);
|
||||
|
||||
println!(
|
||||
|
||||
@@ -27,19 +27,17 @@ use bichon_core::{
|
||||
users::{permissions::Permission, view::UserView},
|
||||
};
|
||||
|
||||
use crate::BichonCliConfig as BichonCliConfig;
|
||||
|
||||
pub async fn verify_user_and_get_account(
|
||||
config: &BichonCliConfig,
|
||||
theme: &ColorfulTheme,
|
||||
only_nosync: bool,
|
||||
) -> MinimalAccount {
|
||||
let client = Client::new();
|
||||
let url = format!("{}/api/v1/current-user", config.base_url);
|
||||
use crate::BichonCliConfig;
|
||||
|
||||
async fn fetch_json<T: serde::de::DeserializeOwned>(
|
||||
client: &Client,
|
||||
url: &str,
|
||||
token: &str,
|
||||
label: &str,
|
||||
) -> T {
|
||||
let response = match client
|
||||
.get(&url)
|
||||
.header("Authorization", format!("Bearer {}", config.api_token))
|
||||
.get(url)
|
||||
.header("Authorization", format!("Bearer {}", token))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
@@ -59,19 +57,15 @@ pub async fn verify_user_and_get_account(
|
||||
}
|
||||
};
|
||||
|
||||
if !response.status().is_success() {
|
||||
let status = response.status();
|
||||
let error_body = response
|
||||
.text()
|
||||
.await
|
||||
.unwrap_or_else(|_| "No error detail provided".to_string());
|
||||
let status = response.status();
|
||||
let body = response.text().await.unwrap_or_else(|_| String::new());
|
||||
|
||||
if !status.is_success() {
|
||||
eprintln!(
|
||||
"\n{} Server returned an error (Status: {})",
|
||||
style("✘ API Error:").red().bold(),
|
||||
style(status).yellow()
|
||||
);
|
||||
|
||||
if status == 401 {
|
||||
eprintln!(
|
||||
"{} Your API Token seems to be invalid or expired.",
|
||||
@@ -83,36 +77,66 @@ pub async fn verify_user_and_get_account(
|
||||
style("Context:").dim()
|
||||
);
|
||||
}
|
||||
|
||||
eprintln!("{} {}", style("Response:").dim(), error_body);
|
||||
eprintln!("{} {}", style("Response:").dim(), body);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
let user: UserView = response.json().await.expect("Failed to parse user data");
|
||||
println!("Welcome, {}!", style(&user.username).cyan());
|
||||
|
||||
let account_list_url = format!(
|
||||
"{}/api/v1/minimal-account-list?only_nosync={only_nosync}",
|
||||
config.base_url
|
||||
);
|
||||
let acc_response = client
|
||||
.get(&account_list_url)
|
||||
.header("Authorization", format!("Bearer {}", config.api_token))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to fetch account list");
|
||||
|
||||
if !acc_response.status().is_success() {
|
||||
panic!(
|
||||
"Failed to retrieve accounts. Status: {}",
|
||||
acc_response.status()
|
||||
if body.is_empty() {
|
||||
eprintln!(
|
||||
"\n{} Server returned an empty response for [{}] (Status: {})",
|
||||
style("✘ Empty Response:").red().bold(),
|
||||
label,
|
||||
status
|
||||
);
|
||||
eprintln!(
|
||||
"{} This may be caused by a reverse proxy or middleware issue.",
|
||||
style("Tip:").cyan()
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
let accounts: Vec<MinimalAccount> = acc_response
|
||||
.json()
|
||||
.await
|
||||
.expect("Failed to parse minimal account list");
|
||||
match serde_json::from_str::<T>(&body) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"\n{} Failed to parse response for [{}]: {}",
|
||||
style("✘ Parse Error:").red().bold(),
|
||||
label,
|
||||
e
|
||||
);
|
||||
eprintln!("{} Raw body: {}", style("Debug:").dim(), body);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn verify_user_and_get_account(
|
||||
config: &BichonCliConfig,
|
||||
theme: &ColorfulTheme,
|
||||
only_nosync: bool,
|
||||
) -> MinimalAccount {
|
||||
let client = Client::new();
|
||||
|
||||
let user: UserView = fetch_json(
|
||||
&client,
|
||||
&format!("{}/api/v1/current-user", config.base_url),
|
||||
&config.api_token,
|
||||
"current-user",
|
||||
)
|
||||
.await;
|
||||
|
||||
println!("Welcome, {}!", style(&user.username).cyan());
|
||||
|
||||
let accounts: Vec<MinimalAccount> = fetch_json(
|
||||
&client,
|
||||
&format!(
|
||||
"{}/api/v1/minimal-account-list?only_nosync={only_nosync}",
|
||||
config.base_url
|
||||
),
|
||||
&config.api_token,
|
||||
"minimal-account-list",
|
||||
)
|
||||
.await;
|
||||
|
||||
if accounts.is_empty() {
|
||||
println!(
|
||||
@@ -129,6 +153,7 @@ pub async fn verify_user_and_get_account(
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
let required_permission = Permission::DATA_IMPORT_BATCH;
|
||||
let mut selectable_accounts = Vec::new();
|
||||
let mut options = Vec::new();
|
||||
|
||||
136
web/src/api/mailbox/mock-mailboxes.ts
Normal file
136
web/src/api/mailbox/mock-mailboxes.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
// Temporary mock data for download-folders responsive testing
|
||||
import { MailboxData, MailboxListResponse } from './api';
|
||||
|
||||
let _id = 1;
|
||||
const m = (overrides: Partial<MailboxData>): MailboxData => ({
|
||||
account_id: 1,
|
||||
attributes: [],
|
||||
delimiter: '/',
|
||||
exists: Math.floor(Math.random() * 5000) + 100,
|
||||
id: _id++,
|
||||
name: '',
|
||||
uid_next: null,
|
||||
uid_validity: null,
|
||||
unseen: null,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const SHORT = [
|
||||
m({ name: 'INBOX' }),
|
||||
m({ name: 'INBOX/Drafts' }),
|
||||
m({ name: 'INBOX/Sent' }),
|
||||
m({ name: 'INBOX/Trash' }),
|
||||
m({ name: 'INBOX/Archive' }),
|
||||
m({ name: 'INBOX/Spam' }),
|
||||
m({ name: 'INBOX/Templates' }),
|
||||
];
|
||||
|
||||
const PROJECTS = [
|
||||
m({ name: 'INBOX/Projects' }),
|
||||
m({ name: 'INBOX/Projects/Q4 2025 Financial Reports and Annual Budget Planning Review' }),
|
||||
m({ name: 'INBOX/Projects/Q4 2025 Financial Reports and Annual Budget Planning Review/Drafts' }),
|
||||
m({
|
||||
name: 'INBOX/Projects/Q4 2025 Financial Reports and Annual Budget Planning Review/Drafts/Revision 3 - Updated Forecast Models and Department Sign-off Required',
|
||||
attributes: [{ attr: 'HasChildren', extension: null }],
|
||||
}),
|
||||
m({
|
||||
name: 'INBOX/Projects/Q4 2025 Financial Reports and Annual Budget Planning Review/Drafts/Revision 3 - Updated Forecast Models and Department Sign-off Required/Comments from CFO',
|
||||
}),
|
||||
m({
|
||||
name: 'INBOX/Projects/Q4 2025 Financial Reports and Annual Budget Planning Review/Drafts/Revision 3 - Updated Forecast Models and Department Sign-off Required/Attachments',
|
||||
}),
|
||||
m({ name: 'INBOX/Projects/Q4 2025 Financial Reports and Annual Budget Planning Review/Final' }),
|
||||
m({ name: 'INBOX/Projects/Q4 2025 Financial Reports and Annual Budget Planning Review/Final/Approved with Amendments' }),
|
||||
m({ name: 'INBOX/Projects/Q1 2026 Strategic Initiative - Cloud Infrastructure Migration Assessment and Vendor Selection' }),
|
||||
m({ name: 'INBOX/Projects/Q1 2026 Strategic Initiative - Cloud Infrastructure Migration Assessment and Vendor Selection/Kickoff' }),
|
||||
m({ name: 'INBOX/Projects/Q1 2026 Strategic Initiative - Cloud Infrastructure Migration Assessment and Vendor Selection/Kickoff/Meeting Minutes and Action Items' }),
|
||||
m({
|
||||
name: 'INBOX/Projects/Q1 2026 Strategic Initiative - Cloud Infrastructure Migration Assessment and Vendor Selection/Vendor Proposals',
|
||||
attributes: [{ attr: 'HasNoChildren', extension: null }],
|
||||
}),
|
||||
m({ name: 'INBOX/Projects/Q1 2026 Strategic Initiative - Cloud Infrastructure Migration Assessment and Vendor Selection/Vendor Proposals/AWS Proposal Package' }),
|
||||
m({ name: 'INBOX/Projects/Q1 2026 Strategic Initiative - Cloud Infrastructure Migration Assessment and Vendor Selection/Vendor Proposals/Azure Proposal Package' }),
|
||||
m({ name: 'INBOX/Projects/Q1 2026 Strategic Initiative - Cloud Infrastructure Migration Assessment and Vendor Selection/Vendor Proposals/GCP Proposal Package' }),
|
||||
m({ name: 'INBOX/Projects/Q1 2026 Strategic Initiative - Cloud Infrastructure Migration Assessment and Vendor Selection/Internal Review and Scoring Committee' }),
|
||||
m({ name: 'INBOX/Projects/HR Portal Redesign - Employee Self-Service Platform Modernization' }),
|
||||
m({ name: 'INBOX/Projects/HR Portal Redesign - Employee Self-Service Platform Modernization/Wireframes and Mockups' }),
|
||||
m({ name: 'INBOX/Projects/HR Portal Redesign - Employee Self-Service Platform Modernization/Wireframes and Mockups/Iteration 1' }),
|
||||
m({ name: 'INBOX/Projects/HR Portal Redesign - Employee Self-Service Platform Modernization/Wireframes and Mockups/Iteration 2' }),
|
||||
m({ name: 'INBOX/Projects/HR Portal Redesign - Employee Self-Service Platform Modernization/Usability Testing Results and Feedback Compilation' }),
|
||||
];
|
||||
|
||||
const CLIENTS = [
|
||||
m({ name: 'INBOX/Clients' }),
|
||||
m({
|
||||
name: 'INBOX/Clients/Acme Corporation - Enterprise Software Licensing and Support Agreement Renewal 2026',
|
||||
attributes: [{ attr: 'HasChildren', extension: null }],
|
||||
}),
|
||||
m({ name: 'INBOX/Clients/Acme Corporation - Enterprise Software Licensing and Support Agreement Renewal 2026/Contract Documents' }),
|
||||
m({ name: 'INBOX/Clients/Acme Corporation - Enterprise Software Licensing and Support Agreement Renewal 2026/Contract Documents/Redlined Versions' }),
|
||||
m({
|
||||
name: 'INBOX/Clients/Acme Corporation - Enterprise Software Licensing and Support Agreement Renewal 2026/Contract Documents/Redlined Versions/Legal Review Round 1',
|
||||
}),
|
||||
m({
|
||||
name: 'INBOX/Clients/Acme Corporation - Enterprise Software Licensing and Support Agreement Renewal 2026/Contract Documents/Redlined Versions/Legal Review Round 2 - Final',
|
||||
}),
|
||||
m({
|
||||
name: 'INBOX/Clients/Acme Corporation - Enterprise Software Licensing and Support Agreement Renewal 2026/Invoices and Payment Records',
|
||||
}),
|
||||
m({ name: 'INBOX/Clients/Acme Corporation - Enterprise Software Licensing and Support Agreement Renewal 2026/Support Tickets and Correspondence' }),
|
||||
m({
|
||||
name: 'INBOX/Clients/Globex Industries - Multi-Year Digital Transformation Consulting Engagement',
|
||||
attributes: [{ attr: 'HasChildren', extension: null }],
|
||||
}),
|
||||
m({ name: 'INBOX/Clients/Globex Industries - Multi-Year Digital Transformation Consulting Engagement/Phase 1 Discovery and Assessment' }),
|
||||
m({ name: 'INBOX/Clients/Globex Industries - Multi-Year Digital Transformation Consulting Engagement/Phase 1 Discovery and Assessment/Stakeholder Interviews' }),
|
||||
m({ name: 'INBOX/Clients/Globex Industries - Multi-Year Digital Transformation Consulting Engagement/Phase 1 Discovery and Assessment/Current State Architecture Documentation' }),
|
||||
m({ name: 'INBOX/Clients/Globex Industries - Multi-Year Digital Transformation Consulting Engagement/Phase 2 Implementation Roadmap' }),
|
||||
m({ name: 'INBOX/Clients/Globex Industries - Multi-Year Digital Transformation Consulting Engagement/Phase 2 Implementation Roadmap/Sprint Planning and Resource Allocation' }),
|
||||
m({ name: 'INBOX/Clients/Globex Industries - Multi-Year Digital Transformation Consulting Engagement/Phase 2 Implementation Roadmap/Risk Assessment and Mitigation Strategies' }),
|
||||
m({ name: 'INBOX/Clients/Initech Solutions - Cybersecurity Audit and Compliance Remediation Program 2026' }),
|
||||
m({ name: 'INBOX/Clients/Initech Solutions - Cybersecurity Audit and Compliance Remediation Program 2026/Penetration Testing Reports' }),
|
||||
m({ name: 'INBOX/Clients/Initech Solutions - Cybersecurity Audit and Compliance Remediation Program 2026/Penetration Testing Reports/External Network Assessment' }),
|
||||
m({ name: 'INBOX/Clients/Initech Solutions - Cybersecurity Audit and Compliance Remediation Program 2026/Penetration Testing Reports/Internal Network Assessment' }),
|
||||
m({ name: 'INBOX/Clients/Initech Solutions - Cybersecurity Audit and Compliance Remediation Program 2026/Penetration Testing Reports/Web Application Security Scan Results' }),
|
||||
m({ name: 'INBOX/Clients/Initech Solutions - Cybersecurity Audit and Compliance Remediation Program 2026/Compliance Gap Analysis and Remediation Tracking' }),
|
||||
m({ name: 'INBOX/Clients/Massive Dynamic - Research Collaboration on Advanced Machine Learning Applications in Healthcare Informatics' }),
|
||||
m({ name: 'INBOX/Clients/Massive Dynamic - Research Collaboration on Advanced Machine Learning Applications in Healthcare Informatics/Data Sharing Agreements and Ethics Board Approvals' }),
|
||||
m({ name: 'INBOX/Clients/Massive Dynamic - Research Collaboration on Advanced Machine Learning Applications in Healthcare Informatics/Literature Review and Prior Art Analysis' }),
|
||||
m({ name: 'INBOX/Clients/Massive Dynamic - Research Collaboration on Advanced Machine Learning Applications in Healthcare Informatics/Model Training Datasets and Validation Results' }),
|
||||
];
|
||||
|
||||
const NOTIFICATIONS = [
|
||||
m({ name: 'INBOX/Notifications' }),
|
||||
m({ name: 'INBOX/Notifications/GitHub Enterprise - Pull Request Reviews and CI/CD Pipeline Status Updates' }),
|
||||
m({ name: 'INBOX/Notifications/Jira Service Management - Incident Response Alerts and Escalation Notifications' }),
|
||||
m({ name: 'INBOX/Notifications/Confluence - Documentation Updates and Page Modification Summaries' }),
|
||||
m({ name: 'INBOX/Notifications/Slack Workspace - Channel Highlights and Direct Message Digest Compilation' }),
|
||||
m({ name: 'INBOX/Notifications/Datadog - Application Performance Monitoring Alerts and Anomaly Detection Reports' }),
|
||||
m({ name: 'INBOX/Notifications/PagerDuty - On-Call Rotation Schedule and Incident Acknowledgment Confirmations' }),
|
||||
m({ name: 'INBOX/Notifications/Microsoft 365 - Calendar Invitations and Meeting Room Booking Confirmations' }),
|
||||
];
|
||||
|
||||
const NEWSLETTERS = [
|
||||
m({ name: 'INBOX/Newsletters' }),
|
||||
m({ name: 'INBOX/Newsletters/Rust Weekly - Community Updates, Crate Highlights, and RFC Progress Tracking Digest' }),
|
||||
m({
|
||||
name: 'INBOX/Newsletters/Systems Programming Insider - Deep Dive Articles on Memory Management and Concurrency Patterns',
|
||||
attributes: [{ attr: 'HasNoChildren', extension: null }],
|
||||
}),
|
||||
m({ name: 'INBOX/Newsletters/Cloud Native Computing Foundation - Kubernetes Ecosystem Updates and Project Maturity Reports' }),
|
||||
m({ name: 'INBOX/Newsletters/Software Architecture Monthly - Case Studies in Distributed Systems Design and Microservices Patterns' }),
|
||||
m({ name: 'INBOX/Newsletters/DevOps Weekly Digest - Tool Reviews, Pipeline Optimization Techniques, and Platform Engineering Insights' }),
|
||||
m({ name: 'INBOX/Newsletters/Information Security Briefing - CVE Disclosures, Threat Intelligence Reports, and Zero-Day Advisories' }),
|
||||
m({ name: 'INBOX/Newsletters/Tech Leadership Forum - Engineering Management Best Practices and Organizational Scaling Strategies' }),
|
||||
];
|
||||
|
||||
export const MOCK_MAILBOX_LIST: MailboxListResponse = {
|
||||
status: 'ready',
|
||||
mailboxes: [
|
||||
...SHORT,
|
||||
...PROJECTS,
|
||||
...CLIENTS,
|
||||
...NOTIFICATIONS,
|
||||
...NEWSLETTERS,
|
||||
],
|
||||
};
|
||||
104
web/src/api/system/mock-dashboard.ts
Normal file
104
web/src/api/system/mock-dashboard.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
// Temporary mock data for dashboard responsive testing
|
||||
import { DashboardStats } from './api';
|
||||
|
||||
const NOW = Date.now();
|
||||
const DAY = 86400000;
|
||||
|
||||
export const MOCK_DASHBOARD_STATS: DashboardStats = {
|
||||
account_count: 12,
|
||||
email_count: 145892,
|
||||
attachment_count: 34201,
|
||||
total_size_bytes: 128_849_018_880, // ~120 GB logical
|
||||
storage_usage_bytes: 85_899_345_920, // ~80 GB blob
|
||||
index_usage_bytes: 12_884_901_888, // ~12 GB index
|
||||
recent_activity: Array.from({ length: 30 }, (_, i) => ({
|
||||
timestamp_ms: NOW - (29 - i) * DAY,
|
||||
count: Math.floor(Math.random() * 2000) + 200,
|
||||
})),
|
||||
top_senders: [
|
||||
{ key: 'alexander.hamilton@verylongemaildomain-truncation-test.com', count: 4523 },
|
||||
{ key: 'noreply@github-enterprise-notifications.system.example.org', count: 3891 },
|
||||
{ key: 'jane.doe+project-alpha-beta-gamma@company-with-long-name.io', count: 3102 },
|
||||
{ key: 'newsletter-subscriptions@really-long-marketing-domain.co.uk', count: 2845 },
|
||||
{ key: 'support-tickets+priority-high@helpdesk.corporate.example.com', count: 2100 },
|
||||
{ key: 'bot-pipeline-ci-cd-failures@devops.internal.long-subdomain.net', count: 1789 },
|
||||
{ key: 'short@s.dev', count: 1500 },
|
||||
{ key: 'alerts-monitoring-productions-east-us@observability-platform.com', count: 1256 },
|
||||
{ key: 'weekly-digest-no-reply@newsletter.huge-media-conglomerate.org', count: 980 },
|
||||
{ key: 'invitations-events-calendar-reminders@social-network-app.io', count: 760 },
|
||||
],
|
||||
top_accounts: [
|
||||
{ key: 'primary.work.mailbox@enterprise-long-domain-name.com', count: 78500 },
|
||||
{ key: 'personal.archive+all@very-lengthy-personal-domain.me', count: 42300 },
|
||||
{ key: 'secondary.backup@another-extremely-long-domain.co', count: 15100 },
|
||||
{ key: 'team-leads@department-of-engineering.corp.example.org', count: 8992 },
|
||||
{ key: 'short@x.co', count: 1000 },
|
||||
],
|
||||
with_attachment_count: 18500,
|
||||
without_attachment_count: 15701,
|
||||
top_largest_emails: [
|
||||
{
|
||||
id: 'msg-001',
|
||||
subject: 'RE: [EXTERNAL] Q4 Financial Reports & Budget Planning Documents for Review - Please Provide Feedback by EOD Friday with Department Head Sign-off Required',
|
||||
size_bytes: 52_428_800,
|
||||
},
|
||||
{
|
||||
id: 'msg-002',
|
||||
subject: 'Fwd: Urgent: Client Presentation Draft - Version 7 Final (With Legal Team Amendments and Compliance Review Attached)',
|
||||
size_bytes: 48_234_496,
|
||||
},
|
||||
{
|
||||
id: 'msg-003',
|
||||
subject: 'Meeting Minutes: Cross-Functional Architecture Review Session - Microservices Migration Strategy and Timeline Discussion (Part 3 of 5)',
|
||||
size_bytes: 41_943_040,
|
||||
},
|
||||
{
|
||||
id: 'msg-004',
|
||||
subject: 'Invoice #INV-2026-04582 - Professional Services Engagement: Cloud Infrastructure Assessment and Remediation Planning Phase II Deliverables',
|
||||
size_bytes: 38_797_312,
|
||||
},
|
||||
{
|
||||
id: 'msg-005',
|
||||
subject: '[ACTION REQUIRED] Security Incident Response: Post-Mortem Analysis and Remediation Steps for CVE-2026-12345 - Department-Wide Mandatory Review',
|
||||
size_bytes: 35_651_584,
|
||||
},
|
||||
{
|
||||
id: 'msg-006',
|
||||
subject: 'Monthly Newsletter: Engineering Blog Digest - Articles on Distributed Systems, Rust Async Runtime Internals, and Performance Optimization Techniques',
|
||||
size_bytes: 31_457_280,
|
||||
},
|
||||
{
|
||||
id: 'msg-007',
|
||||
subject: 'Contract Review: Master Service Agreement Amendment #7 with Third-Party Vendor Integration Services for Payment Processing Platform',
|
||||
size_bytes: 28_311_552,
|
||||
},
|
||||
{
|
||||
id: 'msg-008',
|
||||
subject: 'Travel Itinerary & Expense Report: International Conference on Systems Programming - Accommodation, Flight, and Per Diem Documentation Package',
|
||||
size_bytes: 25_165_824,
|
||||
},
|
||||
{
|
||||
id: 'msg-009',
|
||||
subject: 'Re: [INTERNAL] Employee Onboarding Documentation Package - Benefits Enrollment, Tax Forms, Direct Deposit Setup, and IT Access Request Forms Bundle',
|
||||
size_bytes: 22_020_096,
|
||||
},
|
||||
{
|
||||
id: 'msg-010',
|
||||
subject: 'Data Export Request: Complete Transaction History 2024-2026 with Audit Trail and Compliance Certification for External Regulatory Review Board',
|
||||
size_bytes: 18_874_368,
|
||||
},
|
||||
],
|
||||
top_largest_attachments: [
|
||||
{ id: 'att-001', name: 'Q4_2025_Financial_Statements_Audited_with_Supporting_Schedules_and_Notes_v3_FINAL.xlsx', size_bytes: 45_254_100 },
|
||||
{ id: 'att-002', name: 'project_deliverables_package_phase_2_with_test_results_coverage_report_and_deployment_guide.zip', size_bytes: 38_900_500 },
|
||||
{ id: 'att-003', name: '2026-01-15_production_database_backup_full_with_transaction_logs_and_stored_procedures.sql.gz', size_bytes: 35_200_000 },
|
||||
{ id: 'att-004', name: 'client_presentation_deck_v7_final_approved_with_speaker_notes_and_embedded_video_demo.pptx', size_bytes: 31_000_000 },
|
||||
{ id: 'att-005', name: 'system_architecture_diagrams_microservices_v2_with_sequence_flows_and_deployment_topology.pdf', size_bytes: 28_500_000 },
|
||||
{ id: 'att-006', name: 'annual_company_event_photograph_high_resolution_group_photo_panorama_2026.jpg', size_bytes: 25_000_000 },
|
||||
{ id: 'att-007', name: 'complete_source_code_archive_feature_branch_refactor_auth_module_2026_01_15.tar.gz', size_bytes: 22_800_000 },
|
||||
{ id: 'att-008', name: 'product_demo_screencast_walkthrough_new_features_2026_release_candidate.mp4', size_bytes: 19_500_000 },
|
||||
{ id: 'att-009', name: 'legal_contract_review_package_with_redlined_amendments_and_counsel_opinion_letters.pdf', size_bytes: 16_200_000 },
|
||||
{ id: 'att-010', name: 'employee_training_module_compliance_and_security_awareness_2026_v2_interactive.iso', size_bytes: 12_800_000 },
|
||||
],
|
||||
system_version: '1.0.1',
|
||||
};
|
||||
@@ -31,13 +31,14 @@ import { Loader2, CheckSquare, Square } from 'lucide-react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { toast } from '@/hooks/use-toast'
|
||||
import { list_mailboxes, MailboxData } from '@/api/mailbox/api'
|
||||
//import { MOCK_MAILBOX_LIST } from '@/api/mailbox/mock-mailboxes'
|
||||
import { buildTree, ExtendedTreeItemProps } from '@/lib/build-tree'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { AccountModel, update_account } from '@/api/account/api'
|
||||
import { ToastAction } from '@/components/ui/toast'
|
||||
import axios, { AxiosError } from 'axios'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
|
||||
import { useTheme } from '@/context/theme-context'
|
||||
import React from 'react'
|
||||
@@ -108,7 +109,7 @@ function CustomLabel({
|
||||
</div>
|
||||
{exists !== undefined && (
|
||||
<span
|
||||
className="text-sm opacity-60 min-w-[40px] text-right text-inherit"
|
||||
className="text-sm opacity-60 min-w-[40px] text-right text-inherit mr-4"
|
||||
>
|
||||
{exists}
|
||||
</span>
|
||||
@@ -181,7 +182,7 @@ export function DownloadFoldersDialog({ currentRow, open, onOpenChange }: Props)
|
||||
setItemsWithChildren(itemsWithChildren);
|
||||
setExpandedItems(itemsWithChildren);
|
||||
const download_folders = data
|
||||
.filter(mailbox => currentRow.download_folders.includes(mailbox.name))
|
||||
.filter(mailbox => currentRow.download_folders?.includes(mailbox.name))
|
||||
.map(mailbox => mailbox.id.toString());
|
||||
setSelectedItems(download_folders);
|
||||
};
|
||||
@@ -404,7 +405,7 @@ export function DownloadFoldersDialog({ currentRow, open, onOpenChange }: Props)
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-3xl max-h-[90vh] flex flex-col">
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-3xl max-h-[90vh] flex flex-col overflow-hidden">
|
||||
<DialogHeader className="flex-shrink-0">
|
||||
<DialogTitle>{t('accounts.selectMailboxes')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -412,8 +413,8 @@ export function DownloadFoldersDialog({ currentRow, open, onOpenChange }: Props)
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col pt-2 gap-2">
|
||||
<div className="flex-1 min-h-0 grid gap-4" style={{ gridTemplateRows: 'auto 1fr' }}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -479,7 +480,7 @@ export function DownloadFoldersDialog({ currentRow, open, onOpenChange }: Props)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="h-[32rem] flex-1 min-h-0 w-full pr-4 -mr-4 py-1">
|
||||
<ScrollArea className="min-h-0 w-full py-1">
|
||||
{isLoading && (
|
||||
<div className="p-8 space-y-8">
|
||||
<div className="flex flex-col items-center gap-3 text-muted-foreground">
|
||||
|
||||
@@ -17,9 +17,11 @@ import { Mail, Users, Inbox, Zap, Paperclip } from 'lucide-react';
|
||||
import { formatBytes, formatNumber } from '@/lib/utils';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { get_dashboard_stats, INITIAL_DASHBOARD_STATS, TimeBucket } from '@/api/system/api';
|
||||
//import { MOCK_DASHBOARD_STATS } from '@/api/system/mock-dashboard';
|
||||
import { Main } from '@/components/layout/main';
|
||||
import { FixedHeader } from '@/components/layout/fixed-header';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import LongText from '@/components/long-text';
|
||||
import { getToken } from '@/stores/authStore';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import useMinimalAccountList from '@/hooks/use-minimal-account-list';
|
||||
@@ -346,18 +348,18 @@ export default function MailArchiveDashboard() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="grid gap-6 grid-cols-1 md:grid-cols-2 lg:grid-cols-4">
|
||||
<Card>
|
||||
<div className="grid gap-6 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
<Card className="overflow-hidden">
|
||||
<CardHeader className="!px-4 !pt-4 !pb-1">
|
||||
<CardTitle className="text-xs font-bold uppercase tracking-wider">{t('dashboard.top10Senders')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
{hasTopSenders ? (
|
||||
<Table>
|
||||
<Table className="table-fixed">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="text-xs">{t('dashboard.sender')}</TableHead>
|
||||
<TableHead className="text-right text-xs">{t('dashboard.count')}</TableHead>
|
||||
<TableHead className="w-[96px] text-right text-xs">{t('dashboard.count')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -368,16 +370,18 @@ export default function MailArchiveDashboard() {
|
||||
<div className="absolute left-0 top-0 bottom-0 w-[2px] bg-primary opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
<div className="text-xs flex flex-wrap gap-x-1 min-w-0 flex-1">
|
||||
<span className="flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleQuickSearch({ from: s.key })
|
||||
}}
|
||||
className="hover:text-primary hover:underline transition-colors truncate max-w-[258px]"
|
||||
>
|
||||
{s.key}
|
||||
</button>
|
||||
<LongText className="max-w-[180px] md:max-w-[160px] lg:max-w-[200px] xl:max-w-[220px]">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleQuickSearch({ from: s.key })
|
||||
}}
|
||||
className="hover:text-primary hover:underline transition-colors"
|
||||
>
|
||||
{s.key}
|
||||
</button>
|
||||
</LongText>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -392,17 +396,17 @@ export default function MailArchiveDashboard() {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<Card className="overflow-hidden">
|
||||
<CardHeader className="!px-4 !pt-4 !pb-1">
|
||||
<CardTitle className="text-xs font-bold uppercase tracking-wider">{t('dashboard.top10LargestEmails')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
{hasTopEmails ? (
|
||||
<Table>
|
||||
<Table className="table-fixed">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="text-xs">{t('dashboard.subject')}</TableHead>
|
||||
<TableHead className="text-right text-xs">{t('dashboard.size')}</TableHead>
|
||||
<TableHead className="w-[96px] text-right text-xs">{t('dashboard.size')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -413,16 +417,18 @@ export default function MailArchiveDashboard() {
|
||||
<div className="absolute left-0 top-0 bottom-0 w-[2px] bg-primary opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
<div className="text-xs flex flex-wrap gap-x-1 min-w-0 flex-1">
|
||||
<span className="flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleQuickSearch({ id: m.id })
|
||||
}}
|
||||
className="hover:text-primary hover:underline transition-colors truncate max-w-[258px]"
|
||||
>
|
||||
{m.subject || t('dashboard.noSubject')}
|
||||
</button>
|
||||
<LongText className="max-w-[180px] md:max-w-[160px] lg:max-w-[200px] xl:max-w-[220px]">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleQuickSearch({ id: m.id })
|
||||
}}
|
||||
className="hover:text-primary hover:underline transition-colors"
|
||||
>
|
||||
{m.subject || t('dashboard.noSubject')}
|
||||
</button>
|
||||
</LongText>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -437,7 +443,7 @@ export default function MailArchiveDashboard() {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<Card className="overflow-hidden">
|
||||
<CardHeader className="!px-4 !pt-4 !pb-1">
|
||||
<CardTitle className="text-xs font-bold uppercase tracking-wider">
|
||||
{t('dashboard.top10LargestAttachments')}
|
||||
@@ -445,11 +451,11 @@ export default function MailArchiveDashboard() {
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
{stats?.top_largest_attachments?.length ? (
|
||||
<Table>
|
||||
<Table className="table-fixed">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="text-xs">{t('attachment.name')}</TableHead>
|
||||
<TableHead className="text-right text-xs">{t('dashboard.size')}</TableHead>
|
||||
<TableHead className="w-[96px] text-right text-xs">{t('dashboard.size')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -463,16 +469,18 @@ export default function MailArchiveDashboard() {
|
||||
<div className="absolute left-0 top-0 bottom-0 w-[2px] bg-primary opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
<div className="text-xs flex flex-wrap gap-x-1 min-w-0 flex-1">
|
||||
<span className="flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleQuickAttachmentSearch({ id: a.id })
|
||||
}}
|
||||
className="hover:text-primary hover:underline transition-colors truncate max-w-[238px]"
|
||||
>
|
||||
{a.name || 'Unnamed'}
|
||||
</button>
|
||||
<LongText className="max-w-[160px] md:max-w-[140px] lg:max-w-[180px] xl:max-w-[200px]">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleQuickAttachmentSearch({ id: a.id })
|
||||
}}
|
||||
className="hover:text-primary hover:underline transition-colors"
|
||||
>
|
||||
{a.name || 'Unnamed'}
|
||||
</button>
|
||||
</LongText>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -489,17 +497,17 @@ export default function MailArchiveDashboard() {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<Card className="overflow-hidden">
|
||||
<CardHeader className="!px-4 !pt-4 !pb-1">
|
||||
<CardTitle className="text-xs font-bold uppercase tracking-wider">{t('dashboard.top10Accounts')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
{hasTopAccounts ? (
|
||||
<Table>
|
||||
<Table className="table-fixed">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="text-xs">{t('dashboard.account')}</TableHead>
|
||||
<TableHead className="text-right text-xs">{t('dashboard.emails')}</TableHead>
|
||||
<TableHead className="w-[96px] text-right text-xs">{t('dashboard.emails')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -510,16 +518,18 @@ export default function MailArchiveDashboard() {
|
||||
<div className="absolute left-0 top-0 bottom-0 w-[2px] bg-primary opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
<div className="text-xs flex flex-wrap gap-x-1 min-w-0 flex-1">
|
||||
<span className="flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleQuickSearch({ account_ids: [getAccountIdByEmail(acc.key) || 0] })
|
||||
}}
|
||||
className="hover:text-primary hover:underline transition-colors truncate max-w-[258px]"
|
||||
>
|
||||
{acc.key}
|
||||
</button>
|
||||
<LongText className="max-w-[180px] md:max-w-[160px] lg:max-w-[200px] xl:max-w-[220px]">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleQuickSearch({ account_ids: [getAccountIdByEmail(acc.key) || 0] })
|
||||
}}
|
||||
className="hover:text-primary hover:underline transition-colors"
|
||||
>
|
||||
{acc.key}
|
||||
</button>
|
||||
</LongText>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user