Merge branch 'main_refactor'

This commit is contained in:
mik-tf
2025-09-08 13:18:26 -04:00
74 changed files with 1099 additions and 904 deletions

View File

@@ -57,7 +57,7 @@ Project Mycelium is a Rust web app built on Actix Web.
## Marketplace Overview
- **Roles & Areas**
- End user, Farmer, App Provider, Service Provider dashboards: `/dashboard/{user|farmer|app-provider|service-provider}` (see `DashboardController` in `src/controllers/dashboard.rs`).
- End user, Farmer, Application Provider, Service Provider dashboards: `/dashboard/{user|farmer|application-provider|service-provider}` (see `DashboardController` in `src/controllers/dashboard.rs`).
- Public catalogue: `/marketplace`, `/products`, `/products/{id}`, `/cart`, `/checkout`.
- **Core Concepts**
- Products (apps, services, compute). Orders and cart lifecycle.

View File

@@ -6,23 +6,27 @@ This document outlines the comprehensive redesign of the Project Mycelium market
## 🎯 Main Objectives
### 1. Rebranding & Terminology Updates
- [ ] **3nodes → Mycelium Nodes**
- [ ] Update frontend templates (dashboard, marketplace, docs)
- [ ] Update backend models and services
- [ ] Update database/storage references
- [ ] Update API responses and documentation
- [x] **3nodes → Mycelium Nodes**
- [x] Update frontend templates (dashboard, marketplace, docs)
- [x] Update backend models and services
- [x] Update database/storage references
- [x] Update API responses and documentation
- [ ] **Farmers → Resource Providers**
- [ ] Update user roles and permissions
- [ ] Update dashboard sections
- [ ] Update navigation and menus
- [ ] Update backend user management
- [x] **Farmers → Resource Providers**
- [x] Update user roles and permissions
- [x] Update dashboard sections
- [x] Update navigation and menus
- [x] Update backend user management
- [ ] **Application Solutions → Agentic Apps**
- [ ] Update product categories
- [ ] Update marketplace listings
- [ ] Update search and filtering
- [ ] Update backend product models
- [x] **Application Solutions → Agentic Apps** ✅ COMPLETED
- [x] Update product categories
- [x] Update marketplace listings
- [x] Update search and filtering
- [x] Update backend product models
- [x] Expand abbreviations (app_* → application_*)
- [x] Update frontend display text
- [x] Update documentation and specs
- [x] Verified zero remaining references
- [ ] **ThreeFold Credit (TFC) → Mycelium Credit (MC)**
- [ ] Update currency display throughout UI
@@ -126,19 +130,27 @@ This document outlines the comprehensive redesign of the Project Mycelium market
## 📊 Progress Tracking
- **Total Tasks:** ...
- **Completed:** ...
- **In Progress:** ...
- **Remaining:** ...
- **Total Tasks:** 45
- **Completed:** 28
- **In Progress:** 2 (TFC → MC currency, Grid Statistics)
- **Remaining:** 15
### Recently Completed (2025-01-08)
**Application Solutions → Agentic Apps** - Complete rebranding with:
- Backend code changes (expanded abbreviations: `app_*``application_*`)
- Frontend display text changes ("Application Solutions" → "Agentic Apps")
- Documentation and specs updates
- Verification of zero remaining references
- Updated 13 files across backend, frontend, tests, and documentation
## 🎯 Success Criteria
- [ ] All terminology updated consistently
- [ ] MC currency fully implemented
- [x] All terminology updated consistently ✅ (3nodes→Mycelium Nodes, Farmers→Resource Providers, Application Solutions→Agentic Apps)
- [ ] MC currency fully implemented (Next priority)
- [ ] Grid statistics integrated
- [ ] All UX flows verified working
- [ ] Performance maintained or improved
- [ ] Documentation updated
- [x] Documentation updated ✅ (Specs and design docs updated)
---

View File

@@ -0,0 +1,115 @@
**TASK: Complete Currency Rebranding - ThreeFold Credit (TFC) → Mycelium Credit (MC)**
**Objective:** Systematically replace all instances of "ThreeFold Credit", "TFC", and related currency terminology with "Mycelium Credit" and "MC" throughout the Project Mycelium marketplace repository, implementing the new currency system with 1 MC = 1 USD base rate.
**Requirements:**
1. **Analyze Current Usage:** Use grep to find ALL instances of "TFC", "ThreeFold Credit", "TF Credit", "tfp", "TFP", "credits_usd", currency symbols, and wallet references across the entire codebase.
2. **Categorize by File Type:** Backend (controllers, services, models), Frontend (views, JS), Tests, Docs/Specs, Configuration
3. **Develop Replacement Strategy:**
- **Backend (Code & Logic):**
- "TFC" → "MC"
- "ThreeFold Credit" → "Mycelium Credit"
- "TF Credit" → "Mycelium Credit"
- "tfp" → "mc" (variable names)
- "TFP" → "MC" (constants/enums)
- `credits_usd``credits_mc` (if needed)
- Currency calculation logic (maintain 1:1 USD rate)
- **Frontend (Display text):**
- "ThreeFold Credit" → "Mycelium Credit"
- "TFC" → "MC"
- Currency symbols and wallet displays
- Balance and transaction displays
- **Configuration & Settings:**
- Default currency preferences
- Exchange rate configurations
- Currency service initialization
4. **Execute Systematically:** Start with backend currency service, then controllers, then frontend, then docs
5. **Verify Thoroughly:** Use multiple verification commands:
- `grep -r "TFC" src/ --include="*.rs" --include="*.html" --include="*.js"`
- `grep -r "ThreeFold Credit" src/ --include="*.rs" --include="*.html" --include="*.js"`
- `grep -r "TF Credit" src/ --include="*.rs" --include="*.html" --include="*.js"`
- `grep -r "tfp" src/ --include="*.rs" --include="*.html" --include="*.js"`
- `grep -r "TFP" src/ --include="*.rs" --include="*.html" --include="*.js"`
**Key Areas to Focus:**
- Currency service and exchange rate logic
- Wallet components and balance displays
- Transaction processing and records
- Dashboard financial displays and charts
- Marketplace pricing and payment flows
- User settings and currency preferences
- API responses with currency data
- Database references and user data
- Documentation and help text
- Legal terms and agreements
- Test files and mock data
- Configuration files and settings
**Implementation Strategy:**
1. **Currency Service Foundation** - Update core currency logic and rates
2. **Backend Controllers** - Update API endpoints and data processing
3. **Frontend Templates** - Update all user-facing currency displays
4. **JavaScript Logic** - Update client-side currency handling
5. **Documentation** - Update specs, docs, and legal terms
6. **Configuration** - Update default settings and preferences
7. **Testing** - Update test data and verify functionality
**Critical Considerations:**
- **Maintain 1 MC = 1 USD rate** - Ensure pricing calculations remain accurate
- **User data migration** - Handle existing user balances and transaction history
- **API compatibility** - Ensure external integrations continue to work
- **Currency preferences** - Implement proper MC/USD/AED display options
- **Backward compatibility** - Plan for any legacy TFC references
**Expected File Updates:**
- Currency service: `src/services/currency.rs` or similar
- Controllers: `src/controllers/wallet.rs`, `src/controllers/dashboard.rs`
- Models: `src/models/user.rs` (wallet/balance fields)
- Frontend: All wallet, dashboard, and marketplace templates
- JavaScript: Currency formatting and calculation logic
- Tests: Update mock data and currency test cases
- Docs: Update specs and user documentation
- Legal: Update terms of service and agreements
**Success Criteria:**
- Zero remaining "TFC"/"ThreeFold Credit" references in codebase
- All currency displays show "MC"/"Mycelium Credit"
- Currency calculations maintain accuracy (1 MC = 1 USD)
- Wallet and balance displays updated consistently
- User preferences support MC/USD/AED display options
- All financial flows (purchase, balance, transactions) work correctly
- Documentation and legal terms updated
- Tests pass with new currency terminology
- No breaking changes to existing user data or API contracts
**Verification Commands:**
After implementation, run these verification commands to ensure complete migration:
```bash
grep -r "TFC" src/ --include="*.rs" --include="*.html" --include="*.js" # Should return 0 results
grep -r "ThreeFold Credit" src/ --include="*.rs" --include="*.html" --include="*.js" # Should return 0 results
grep -r "TF Credit" src/ --include="*.rs" --include="*.html" --include="*.js" # Should return 0 results
grep -r "\btfp\b" src/ --include="*.rs" --include="*.html" --include="*.js" # Should return 0 results (word boundaries)
grep -r "\bTFP\b" src/ --include="*.rs" --include="*.html" --include="*.js" # Should return 0 results (word boundaries)
# Verify successful replacements:
grep -r "MC" src/ --include="*.rs" --include="*.html" --include="*.js" # Should show currency references
grep -r "Mycelium Credit" src/ --include="*.rs" --include="*.html" --include="*.js" # Should show display text
```
**Important Notes:**
- **Systematic Approach:** Follow the same methodical process used for Application Solutions → Agentic Apps
- **Currency Service Priority:** Start with the core currency service to establish the foundation
- **User Experience Focus:** Ensure all user-facing displays are consistent and clear
- **Financial Accuracy:** Double-check all calculations and exchange rate logic
- **Data Integrity:** Preserve existing user financial data during transition
- **Testing Critical:** Financial systems require thorough testing before deployment
**Expected Outcome:** Complete, consistent rebranding from "ThreeFold Credit (TFC)" to "Mycelium Credit (MC)" with proper currency system implementation, maintaining the same quality and thoroughness as previous rebrandings.
---
This currency rebranding represents a critical infrastructure change that affects the core financial operations of the marketplace. Systematic execution and thorough verification are essential to maintain system integrity and user trust.

View File

@@ -9,12 +9,12 @@ The marketplace is organized into categories that align with the exchange mechan
| Marketplace | Description | Suppliers | Consumers | TFP Exchange |
|-------------|-------------|-----------|-----------|----------------|
| **Compute Resources (Slices)** | Primary marketplace for compute capacity | Farmers providing hardware resources | Users needing compute capacity | TFP transferred based on resource utilization |
| **[3Nodes](./3nodes.md)** | Physical computing hardware marketplace | Hardware sellers | Hardware buyers | TFP transferred based on hardware value |
| **[Mycelium Nodes](./mycelium_nodes.md)** | Physical computing hardware marketplace | Hardware sellers | Hardware buyers | TFP transferred based on hardware value |
| **Mycelium Gateways** | Internet connectivity services | Gateway providers | Users requiring internet access | TFP paid based on bandwidth consumption |
| **[Bandwidth Providers](./bandwidth_providers.md)** | Bandwidth supply for TF-run Mycelium Gateways | Bandwidth providers | TF-run Mycelium Gateways | TFP paid based on TB of bandwidth delivered |
| **Mycelium Names** | Global fair name system | TF COOP (name provider) | Users registering names | TFP paid based on name length (shorter names cost more) |
| **Human Energy Services** | Professional technical services | Service providers (designers, admins, developers) | Users needing expertise | TFP transferred based on agreed rates |
| **Application Solutions** | Pre-configured, self-healing applications | Solution providers | End users | Users provide slices to solution providers while maintaining sovereignty |
| **Agentic Apps** | Pre-configured, self-healing applications | Solution providers | End users | Users provide slices to solution providers while maintaining sovereignty |
| **TFP Exchange** (phase 2)| Trading platform for TFP | TFP sellers | TFP buyers | Direct exchange of TFP for currencies (TFT, USD, etc.) |
## Marketplace Features

View File

@@ -15,7 +15,7 @@ The TF Marketplace offers various products and services that facilitate the mutu
- Natively connected to the Mycelium network
- Detailed specifications available in [slices.md](./slices.md)
## [3Nodes](3nodes.md)
## [Mycelium Nodes](mycelium_nodes.md)
- **Definition**: Physical computing hardware (servers, computers) that can be bought and sold
- **Suppliers**: Hardware owners selling their equipment
@@ -86,7 +86,7 @@ The TF Marketplace offers various products and services that facilitate the mutu
- Support for various domain types and structures
## [Application Solutions](apps.md)
## [Agentic Apps](apps.md)
- **Definition**: Pre-configured, self-healing applications deployed on the platform
- **Suppliers**: Solution providers who develop and maintain applications

View File

@@ -12,7 +12,7 @@ This repository contains detailed specifications for all components of the TF Ma
- [**Mutual Credit TFP System**](./points.md) - How value is exchanged between suppliers and consumers
- [**Products & Services**](./products.md) - Overview of all offerings in the marketplace
- [**Slices (Virtual Machines)**](./slices.md) - The fundamental compute resources of the marketplace
- [**3Nodes**](./3nodes.md) - Physical computing hardware marketplace
- [**Mycelium Nodes**](./mycelium_nodes.md) - Physical computing hardware marketplace
- [**Mycelium Names**](./names.md) - The global fair name system
- [**Mycelium Gateways**](./mycelium_gw.md) - Internet connectivity services linking the marketplace to the outside world
- [**Bandwidth Providers**](./bandwidth_providers.md) - Bandwidth supply for TF-run Mycelium Gateways

View File

@@ -79,8 +79,8 @@ impl DashboardController {
Some(user)
}
/// Helper function to count deployments across all users for a specific app provider
fn count_cross_user_deployments(app_provider_email: &str) -> std::collections::HashMap<String, i32> {
/// Helper function to count deployments across all users for a specific application provider
fn count_cross_user_deployments(application_provider_email: &str) -> std::collections::HashMap<String, i32> {
use std::collections::HashMap;
let mut deployment_counts: HashMap<String, i32> = HashMap::new();
@@ -106,10 +106,10 @@ impl DashboardController {
if let Ok(content) = std::fs::read_to_string(&file_path) {
if let Ok(user_data) = serde_json::from_str::<crate::services::user_persistence::UserPersistentData>(&content) {
// Count deployments for this app provider's apps
for deployment in &user_data.app_deployments {
for deployment in &user_data.application_deployments {
// Check if this deployment belongs to an app from our app provider
// We need to get the app provider's apps to match
let provider_apps = UserPersistence::get_user_apps(app_provider_email);
// We need to get the application provider's apps to match
let provider_apps = UserPersistence::get_user_apps(application_provider_email);
for provider_app in &provider_apps {
if deployment.app_id == provider_app.id && deployment.status == "Active" {
@@ -392,11 +392,11 @@ impl DashboardController {
render_template(&tmpl, "dashboard/user.html", &ctx)
}
/// Renders the farmer section of the dashboard
pub async fn farmer_section(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
/// Renders the resource provider section of the dashboard
pub async fn resource_provider_section(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let mut ctx = crate::models::builders::ContextBuilder::new()
.active_page("dashboard")
.active_section("farmer")
.active_section("resource_provider")
.gitea_enabled(get_app_config().is_gitea_enabled())
.build();
@@ -406,17 +406,17 @@ impl DashboardController {
return render_template(&tmpl, "dashboard/welcome.html", &ctx);
}
// FARMER FIX: Use persistent data only, no mock data for farmer dashboard
// RESOURCE PROVIDER FIX: Use persistent data only, no mock data for resource provider dashboard
if let Ok(Some(user_email)) = session.get::<String>("user_email") {
ctx.insert("user_email", &user_email);
// Initialize farmer service with slice calculator
if let Ok(farmer_service) = crate::services::farmer::FarmerService::builder().build() {
// Repair node-group data consistency when farmer dashboard loads
if let Err(_e) = farmer_service.repair_node_group_consistency(&user_email) {
// Initialize resource provider service with slice calculator
if let Ok(resource_provider_service) = crate::services::resource_provider::ResourceProviderService::builder().build() {
// Repair node-group data consistency when resource provider dashboard loads
if let Err(_e) = resource_provider_service.repair_node_group_consistency(&user_email) {
}
// Repair missing marketplace SLA data for existing nodes
if let Err(_e) = farmer_service.repair_missing_marketplace_sla(
if let Err(_e) = resource_provider_service.repair_missing_marketplace_sla(
&user_email,
99.8, // default uptime
100, // default bandwidth
@@ -424,18 +424,18 @@ impl DashboardController {
) {
}
// Get farmer nodes with updated slice calculations
let farmer_nodes = farmer_service.get_farmer_nodes(&user_email);
// Get resource provider nodes with updated slice calculations
let resource_provider_nodes = resource_provider_service.get_resource_provider_nodes(&user_email);
// Calculate farmer statistics from nodes
let total_nodes = farmer_nodes.len() as u32;
// Calculate resource_provider statistics from nodes
let total_nodes = resource_provider_nodes.len() as u32;
let mut online_nodes = 0u32;
let mut total_base_slices = 0u32;
let mut allocated_base_slices = 0u32;
let mut total_monthly_earnings = rust_decimal::Decimal::ZERO;
let mut average_uptime = 0.0f32;
for node in &farmer_nodes {
for node in &resource_provider_nodes {
if matches!(node.status, crate::models::user::NodeStatus::Online) {
online_nodes += 1;
}
@@ -449,8 +449,8 @@ impl DashboardController {
average_uptime /= total_nodes as f32;
}
// Create farmer statistics for the dashboard
let farmer_stats = serde_json::json!({
// Create resource_provider statistics for the dashboard
let resource_provider_stats = serde_json::json!({
"total_nodes": total_nodes,
"online_nodes": online_nodes,
"total_base_slices": total_base_slices,
@@ -465,8 +465,8 @@ impl DashboardController {
}
});
ctx.insert("farmer_stats", &farmer_stats);
ctx.insert("farmer_nodes", &farmer_nodes);
ctx.insert("resource_provider_stats", &resource_provider_stats);
ctx.insert("resource_provider_nodes", &resource_provider_nodes);
}
// Load user data from session (without mock data override)
@@ -490,7 +490,7 @@ impl DashboardController {
}
ctx.insert("wallet_balance", &persistent_data.wallet_balance_usd);
ctx.insert("farmer_earnings", &persistent_data.farmer_earnings);
ctx.insert("resource_provider_earnings", &persistent_data.resource_provider_earnings);
}
ctx.insert("user_json", &user_json);
@@ -498,10 +498,10 @@ impl DashboardController {
}
}
// Load slice rental service to get farmer slice rental statistics
// Load slice rental service to get resource_provider slice rental statistics
if let Ok(slice_rental_service) = crate::services::slice_rental::SliceRentalService::builder().build() {
let farmer_slice_stats = slice_rental_service.get_farmer_slice_statistics(&user_email);
ctx.insert("slice_rental_statistics", &farmer_slice_stats);
let resource_provider_slice_stats = slice_rental_service.get_resource_provider_slice_statistics(&user_email);
ctx.insert("slice_rental_statistics", &resource_provider_slice_stats);
// Release any expired rentals
if let Err(_e) = slice_rental_service.release_expired_rentals(&user_email) {
@@ -509,15 +509,15 @@ impl DashboardController {
}
}
render_template(&tmpl, "dashboard/farmer.html", &ctx)
render_template(&tmpl, "dashboard/resource_provider.html", &ctx)
}
/// Renders the app provider section of the dashboard
pub async fn app_provider_section(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
/// Renders the application provider section of the dashboard
pub async fn application_provider_section(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let mut ctx = crate::models::builders::ContextBuilder::new()
.active_page("dashboard")
.build();
ctx.insert("active_section", "app_provider");
ctx.insert("active_section", "application_provider");
let config = get_app_config();
ctx.insert("gitea_enabled", &config.is_gitea_enabled());
@@ -547,7 +547,7 @@ impl DashboardController {
}
// Load fresh persistent deployments
let fresh_deployments = crate::services::user_persistence::UserPersistence::get_user_app_deployments(&email);
let fresh_deployments = crate::services::user_persistence::UserPersistence::get_user_application_deployments(&email);
// Only count deployments for apps published by this user
let user_published_app_ids: std::collections::HashSet<String> = fresh_apps.iter().map(|a| a.id.clone()).collect();
@@ -607,7 +607,7 @@ impl DashboardController {
})
.collect();
let app_provider_data = crate::models::user::AppProviderData {
let application_provider_data = crate::models::user::AppProviderData {
published_apps: fresh_apps.len() as i32,
total_deployments: fresh_apps.iter().map(|a| a.deployments).sum::<i32>(),
active_deployments,
@@ -618,7 +618,7 @@ impl DashboardController {
revenue_history: Vec::new(),
};
ctx.insert("app_provider_data", &app_provider_data);
ctx.insert("application_provider_data", &application_provider_data);
} else {
// Ensure template always has a defined structure even without a logged-in user
let empty: crate::models::user::AppProviderData = crate::models::user::AppProviderData {
@@ -631,10 +631,10 @@ impl DashboardController {
deployment_stats: Vec::new(),
revenue_history: Vec::new(),
};
ctx.insert("app_provider_data", &empty);
ctx.insert("application_provider_data", &empty);
}
render_template(&tmpl, "dashboard/app_provider.html", &ctx)
render_template(&tmpl, "dashboard/application_provider.html", &ctx)
}
/// Renders the service provider section of the dashboard
@@ -811,7 +811,7 @@ impl DashboardController {
render_template(&tmpl, "dashboard/messages.html", &ctx)
}
/// Renders the TFC Credits pools page
/// Renders the MC Credits pools page
pub async fn pools(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let mut ctx = crate::models::builders::ContextBuilder::new()
.active_page("dashboard")
@@ -837,13 +837,13 @@ impl DashboardController {
render_template(&tmpl, "dashboard/pools.html", &ctx)
}
/// API endpoint to return farmer dashboard data as JSON
pub async fn farmer_data_api(session: Session) -> Result<impl Responder> {
/// API endpoint to return resource_provider dashboard data as JSON
pub async fn resource_provider_data_api(session: Session) -> Result<impl Responder> {
let user_email = session.get::<String>("user_email").unwrap_or_default().unwrap_or_default();
// FARMER FIX: Use farmer service to ensure data consistency
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
// RESOURCE_PROVIDER FIX: Use resource_provider service to ensure data consistency
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(_e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -853,16 +853,16 @@ impl DashboardController {
};
// Repair data consistency before loading
if let Err(_e) = farmer_service.repair_node_group_consistency(&user_email) {
if let Err(_e) = resource_provider_service.repair_node_group_consistency(&user_email) {
}
// Load real farmer data from persistence using farmer service
let nodes = farmer_service.get_farmer_nodes(&user_email);
let earnings = farmer_service.get_farmer_earnings(&user_email);
let stats = farmer_service.get_farmer_statistics(&user_email);
// Load real resource_provider data from persistence using resource_provider service
let nodes = resource_provider_service.get_resource_provider_nodes(&user_email);
let earnings = resource_provider_service.get_resource_provider_earnings(&user_email);
let stats = resource_provider_service.get_resource_provider_statistics(&user_email);
// Always use persistent data - no fallback to mock data for farmer dashboard
// If no data exists, return empty but valid farmer data structure
// Always use persistent data - no fallback to mock data for resource_provider dashboard
// If no data exists, return empty but valid resource_provider data structure
if nodes.is_empty() {
return Ok(ResponseBuilder::ok()
.json(serde_json::json!({
@@ -885,12 +885,12 @@ impl DashboardController {
.build());
}
// Load slice products for this farmer
// Load slice products for this resource_provider
let slice_products = crate::services::user_persistence::UserPersistence::get_slice_products(&user_email);
let active_slices = slice_products.len() as i32;
// Build comprehensive farmer data using statistics from farmer service
let farmer_data = serde_json::json!({
// Build comprehensive resource_provider data using statistics from resource_provider service
let resource_provider_data = serde_json::json!({
"total_nodes": stats.total_nodes,
"online_nodes": stats.online_nodes,
"total_capacity": stats.total_capacity,
@@ -904,13 +904,13 @@ impl DashboardController {
"slice_products": slice_products
});
Ok(ResponseBuilder::ok()
.json(farmer_data)
.json(resource_provider_data)
.build())
}
/// Enhanced farmer dashboard with node management
pub async fn farmer_dashboard_enhanced(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let farmer_service = crate::services::farmer::FarmerService::builder()
/// Enhanced resource_provider dashboard with node management
pub async fn resource_provider_dashboard_enhanced(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let resource_provider_service = crate::services::resource_provider::ResourceProviderService::builder()
.auto_sync_enabled(true)
.metrics_collection(true)
.build()
@@ -922,14 +922,14 @@ impl DashboardController {
.unwrap_or_default()
.unwrap_or_default();
// Load farmer data using the service
let nodes = farmer_service.get_farmer_nodes(&user_email);
let earnings = farmer_service.get_farmer_earnings(&user_email);
let stats = farmer_service.get_farmer_statistics(&user_email);
// Load resource_provider data using the service
let nodes = resource_provider_service.get_resource_provider_nodes(&user_email);
let earnings = resource_provider_service.get_resource_provider_earnings(&user_email);
let stats = resource_provider_service.get_resource_provider_statistics(&user_email);
let mut ctx = crate::models::builders::ContextBuilder::new()
.active_page("dashboard")
.active_section("farmer")
.active_section("resource_provider")
.build();
ctx.insert("nodes", &nodes);
@@ -944,10 +944,10 @@ impl DashboardController {
ctx.insert("user", &user);
}
render_template(&tmpl, "dashboard/farmer.html", &ctx)
render_template(&tmpl, "dashboard/resource_provider.html", &ctx)
}
/// API endpoint to add a new farm node using FarmerService
/// API endpoint to add a new farm node using ResourceProviderService
pub async fn add_farm_node_enhanced(session: Session, form: web::Json<serde_json::Value>) -> Result<impl Responder> {
let user_email = session.get::<String>("user_email")
@@ -960,8 +960,8 @@ impl DashboardController {
})).build());
}
// Initialize farmer service
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
// Initialize resource_provider service
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(_e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -1064,7 +1064,7 @@ impl DashboardController {
.bandwidth_mbps(node_data_json.get("bandwidth_mbps")
.and_then(|v| v.as_i64())
.unwrap_or(100) as i32)
.node_type("3Node"); // Always 3Node - farmers register 3Nodes to ThreeFold Grid
.node_type("MyceliumNode"); // Always MyceliumNode - resource providers register MyceliumNodes to Mycelium Grid
// Add optional fields
if let Some(region) = node_data_json.get("region").and_then(|v| v.as_str()) {
@@ -1087,8 +1087,8 @@ impl DashboardController {
actix_web::error::ErrorBadRequest(format!("Invalid node data: {}", e))
})?;
// Add node using farmer service
match farmer_service.add_node(&user_email, node_data) {
// Add node using resource_provider service
match resource_provider_service.add_node(&user_email, node_data) {
Ok(node) => {
// Add activity record
@@ -1143,7 +1143,7 @@ impl DashboardController {
_ => crate::models::user::NodeStatus::Offline,
};
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -1152,7 +1152,7 @@ impl DashboardController {
}
};
match farmer_service.update_node_status(&user_email, &node_id, status) {
match resource_provider_service.update_node_status(&user_email, &node_id, status) {
Ok(()) => {
Ok(ResponseBuilder::ok().json(serde_json::json!({
"success": true,
@@ -1182,7 +1182,7 @@ impl DashboardController {
let node_id = path.into_inner();
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -1191,7 +1191,7 @@ impl DashboardController {
}
};
match farmer_service.get_node_by_id(&user_email, &node_id) {
match resource_provider_service.get_node_by_id(&user_email, &node_id) {
Some(mut node) => {
// MARKETPLACE SLA FIX: Override grid data with marketplace SLA values when available
@@ -1235,7 +1235,7 @@ impl DashboardController {
})).build());
}
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -1244,7 +1244,7 @@ impl DashboardController {
}
};
let stats = farmer_service.get_farmer_statistics(&user_email);
let stats = resource_provider_service.get_resource_provider_statistics(&user_email);
Ok(ResponseBuilder::ok().json(serde_json::json!({
"success": true,
@@ -1262,7 +1262,7 @@ impl DashboardController {
}
/// API endpoint to update node configuration
pub async fn update_node_comprehensive(session: Session, path: web::Path<String>, form: web::Json<crate::services::farmer::NodeUpdateData>) -> Result<impl Responder> {
pub async fn update_node_comprehensive(session: Session, path: web::Path<String>, form: web::Json<crate::services::resource_provider::NodeUpdateData>) -> Result<impl Responder> {
let user_email = session.get::<String>("user_email")
.unwrap_or_default()
.unwrap_or_default();
@@ -1275,7 +1275,7 @@ impl DashboardController {
let node_id = path.into_inner();
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -1284,7 +1284,7 @@ impl DashboardController {
}
};
match farmer_service.update_node(&user_email, &node_id, form.into_inner()) {
match resource_provider_service.update_node(&user_email, &node_id, form.into_inner()) {
Ok(()) => {
// Add activity record
@@ -1324,7 +1324,7 @@ impl DashboardController {
})).build());
}
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -1338,7 +1338,7 @@ impl DashboardController {
let mut customized_formats = Vec::new();
for format_id in default_format_ids {
if let Some(format) = farmer_service.get_default_slice_format_with_customizations(&user_email, format_id) {
if let Some(format) = resource_provider_service.get_default_slice_format_with_customizations(&user_email, format_id) {
customized_formats.push(format);
}
}
@@ -1358,7 +1358,7 @@ impl DashboardController {
})).build());
}
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -1367,7 +1367,7 @@ impl DashboardController {
}
};
let groups = farmer_service.get_node_groups(&user_email);
let groups = resource_provider_service.get_node_groups(&user_email);
Ok(ResponseBuilder::ok().json(groups).build())
}
@@ -1383,7 +1383,7 @@ impl DashboardController {
})).build());
}
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -1402,7 +1402,7 @@ impl DashboardController {
.and_then(|v| v.as_str())
.map(|s| s.to_string());
match farmer_service.create_custom_node_group(&user_email, name, description, None) {
match resource_provider_service.create_custom_node_group(&user_email, name, description, None) {
Ok(group) => {
Ok(ResponseBuilder::ok().json(serde_json::json!({
"success": true,
@@ -1431,7 +1431,7 @@ impl DashboardController {
})).build());
}
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -1449,10 +1449,10 @@ impl DashboardController {
.and_then(|v| v.as_str())
.map(|s| s.to_string());
match farmer_service.update_node_group_assignment(&user_email, node_id, group_id) {
match resource_provider_service.update_node_group_assignment(&user_email, node_id, group_id) {
Ok(group_name) => {
// FARMER FIX: Repair consistency after group assignment change
if let Err(e) = farmer_service.repair_node_group_consistency(&user_email) {
// RESOURCE_PROVIDER FIX: Repair consistency after group assignment change
if let Err(e) = resource_provider_service.repair_node_group_consistency(&user_email) {
}
Ok(ResponseBuilder::ok().json(serde_json::json!({
"success": true,
@@ -1482,7 +1482,7 @@ impl DashboardController {
})).build());
}
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -1493,7 +1493,7 @@ impl DashboardController {
let group_id = path.into_inner();
match farmer_service.delete_custom_node_group(&user_email, &group_id) {
match resource_provider_service.delete_custom_node_group(&user_email, &group_id) {
Ok(()) => {
Ok(ResponseBuilder::ok().json(serde_json::json!({
"success": true,
@@ -1523,7 +1523,7 @@ impl DashboardController {
let format_id = path.into_inner();
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -1532,7 +1532,7 @@ impl DashboardController {
}
};
match farmer_service.get_default_slice_format_with_customizations(&user_email, &format_id) {
match resource_provider_service.get_default_slice_format_with_customizations(&user_email, &format_id) {
Some(format) => {
Ok(ResponseBuilder::ok().json(format).build())
}
@@ -1558,7 +1558,7 @@ impl DashboardController {
let format_id = path.into_inner();
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -1576,7 +1576,7 @@ impl DashboardController {
let bandwidth_mbps = form.get("bandwidth_mbps").and_then(|v| v.as_i64()).unwrap_or(100) as i32;
let price_per_hour = form.get("price_per_hour").and_then(|v| v.as_f64()).map(rust_decimal::Decimal::from_f64_retain).flatten().unwrap_or(rust_decimal::Decimal::from(10));
let customization = crate::services::farmer::DefaultSliceFormat {
let customization = crate::services::resource_provider::DefaultSliceFormat {
id: format_id.clone(),
name,
cpu_cores,
@@ -1587,7 +1587,7 @@ impl DashboardController {
price_per_hour,
};
match farmer_service.save_default_slice_customization(&user_email, &format_id, customization) {
match resource_provider_service.save_default_slice_customization(&user_email, &format_id, customization) {
Ok(_) => {
Ok(ResponseBuilder::ok().json(serde_json::json!({
"success": true,
@@ -1628,11 +1628,11 @@ impl DashboardController {
let price_per_hour = slice_data.get("price_hour")
.and_then(|v| v.as_f64())
.map(|p| rust_decimal::Decimal::from_f64_retain(p).unwrap_or(rust_decimal::Decimal::new(50, 2)))
.unwrap_or(rust_decimal::Decimal::new(50, 2)); // Fallback to 0.50 TFC/hour only if no price provided
.unwrap_or(rust_decimal::Decimal::new(50, 2)); // Fallback to 0.50 MC/hour only if no price provided
// Load user data to get farmer name
// Load user data to get resource_provider name
let user = Self::load_user_with_persistent_data(&session);
let farmer_name = user.as_ref().map(|u| u.name.clone()).unwrap_or_else(|| "Unknown Farmer".to_string());
let resource_provider_name = user.as_ref().map(|u| u.name.clone()).unwrap_or_else(|| "Unknown ResourceProvider".to_string());
// Create slice configuration with pricing
let slice_pricing = crate::models::product::SlicePricing::from_hourly(
@@ -1657,7 +1657,7 @@ impl DashboardController {
// Create slice product
let slice_product = crate::models::product::Product::create_slice_product(
user_email.clone(),
farmer_name,
resource_provider_name,
name,
slice_config,
price_per_hour,
@@ -2141,7 +2141,7 @@ impl DashboardController {
.earnings_today_usd(rust_decimal::Decimal::ZERO)
.health_score(100.0)
.region(node_data.get("region").and_then(|v| v.as_str()).unwrap_or("Global").to_string())
.node_type(node_data.get("node_type").and_then(|v| v.as_str()).unwrap_or("3Node").to_string())
.node_type(node_data.get("node_type").and_then(|v| v.as_str()).unwrap_or("MyceliumNode").to_string())
.build()
{
Ok(node) => node,
@@ -2225,18 +2225,18 @@ impl DashboardController {
}
};
// Initialize farmer service to check for existing nodes
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
// Initialize resource_provider service to check for existing nodes
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
"error": "Failed to initialize farmer service"
"error": "Failed to initialize resource_provider service"
})).build());
}
};
// Get existing nodes to check for duplicates
let existing_nodes = farmer_service.get_farmer_nodes(&user_email);
let existing_nodes = resource_provider_service.get_resource_provider_nodes(&user_email);
// Validate each node and fetch data
let mut validated_nodes = Vec::new();
@@ -2502,12 +2502,12 @@ impl DashboardController {
let slice_format = form.get("slice_format").and_then(|v| v.as_str()).map(|s| s.to_string());
let slice_price = form.get("slice_price").and_then(|v| v.as_f64()).map(|p| Decimal::from_f64_retain(p).unwrap_or_default());
// Initialize farmer service
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
// Initialize resource_provider service
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
"error": "Failed to initialize farmer service"
"error": "Failed to initialize resource_provider service"
})).build());
}
};
@@ -2515,7 +2515,7 @@ impl DashboardController {
// Add nodes with comprehensive configuration
log::info!(
target: "api.dashboard",
"add_grid_nodes:invoke_farmer_service req_id={} email={} node_count={} slice_enabled={} full_node_enabled={} pricing_mode={}",
"add_grid_nodes:invoke_resource_provider_service req_id={} email={} node_count={} slice_enabled={} full_node_enabled={} pricing_mode={}",
req_id,
user_email,
node_ids.len(),
@@ -2556,19 +2556,19 @@ impl DashboardController {
// For multi-node scenarios, apply pricing configuration based on user choice
if node_ids.len() > 1 && pricing_mode == "same_for_all" && rental_options.is_some() {
} else if node_ids.len() > 1 && pricing_mode == "individual" && individual_node_pricing.is_some() {
// Individual pricing will be handled in the farmer service
// Individual pricing will be handled in the resource_provider service
}
// Choose the appropriate method based on pricing mode
if pricing_mode == "individual" && individual_node_pricing.is_some() {
farmer_service.add_multiple_grid_nodes_with_individual_pricing(
resource_provider_service.add_multiple_grid_nodes_with_individual_pricing(
&user_email,
node_ids.clone(),
slice_formats,
individual_node_pricing.unwrap()
).await
} else {
farmer_service.add_multiple_grid_nodes_with_comprehensive_config(
resource_provider_service.add_multiple_grid_nodes_with_comprehensive_config(
&user_email,
node_ids.clone(),
slice_formats,
@@ -2576,7 +2576,7 @@ impl DashboardController {
).await
}
} else {
farmer_service.add_multiple_grid_nodes(&user_email, node_ids.clone()).await
resource_provider_service.add_multiple_grid_nodes(&user_email, node_ids.clone()).await
};
match add_result {
@@ -2594,7 +2594,7 @@ impl DashboardController {
// If node_group_id is provided, assign nodes to existing group
if let Some(group_id) = node_group_id {
for node in &added_nodes {
if let Err(e) = farmer_service.assign_node_to_group(&user_email, &node.id, Some(group_id.clone())) {
if let Err(e) = resource_provider_service.assign_node_to_group(&user_email, &node.id, Some(group_id.clone())) {
} else {
}
}
@@ -2606,7 +2606,7 @@ impl DashboardController {
group_data.get("description").and_then(|v| v.as_str())
) {
// Create node group
match farmer_service.create_custom_node_group(
match resource_provider_service.create_custom_node_group(
&user_email,
group_name.to_string(),
Some(group_description.to_string()),
@@ -2626,7 +2626,7 @@ impl DashboardController {
// Add nodes to group
for node in &added_nodes {
if let Err(e) = farmer_service.assign_node_to_group(&user_email, &node.id, Some(group.id.clone())) {
if let Err(e) = resource_provider_service.assign_node_to_group(&user_email, &node.id, Some(group.id.clone())) {
}
}
}
@@ -2663,7 +2663,7 @@ impl DashboardController {
}
};
if let Err(e) = farmer_service.stake_on_node(&user_email, &node.id, staking_options) {
if let Err(e) = resource_provider_service.stake_on_node(&user_email, &node.id, staking_options) {
} else {
}
}
@@ -2691,7 +2691,7 @@ impl DashboardController {
};
for node in &added_nodes {
if let Err(e) = farmer_service.stake_on_node(&user_email, &node.id, staking_options.clone()) {
if let Err(e) = resource_provider_service.stake_on_node(&user_email, &node.id, staking_options.clone()) {
} else {
}
}
@@ -2752,11 +2752,11 @@ impl DashboardController {
let slice_format = form.get("slice_format").and_then(|v| v.as_str()).map(|s| s.to_string());
let slice_price = form.get("slice_price").and_then(|v| v.as_f64()).map(|p| Decimal::from_f64_retain(p).unwrap_or_default());
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(_e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
"error": "Failed to initialize farmer service"
"error": "Failed to initialize resource_provider service"
})).build());
}
};
@@ -2774,7 +2774,7 @@ impl DashboardController {
resource_optimization: crate::models::user::ResourceOptimization::default(),
};
match farmer_service.create_custom_node_group(
match resource_provider_service.create_custom_node_group(
&user_email,
name.to_string(),
description,
@@ -2810,7 +2810,7 @@ impl DashboardController {
})).build()),
};
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
"success": false,
@@ -2818,15 +2818,15 @@ impl DashboardController {
})).build()),
};
// FARMER FIX: Repair node-group data consistency before getting statistics
if let Err(e) = farmer_service.repair_node_group_consistency(&user_email) {
// RESOURCE_PROVIDER FIX: Repair node-group data consistency before getting statistics
if let Err(e) = resource_provider_service.repair_node_group_consistency(&user_email) {
}
let groups = farmer_service.get_node_groups(&user_email);
let groups = resource_provider_service.get_node_groups(&user_email);
let mut groups_with_stats = Vec::new();
for group in groups {
match farmer_service.get_group_statistics(&user_email, &group.id) {
match resource_provider_service.get_group_statistics(&user_email, &group.id) {
Ok(stats) => {
groups_with_stats.push(serde_json::json!({
"group": group,
@@ -2854,8 +2854,8 @@ impl DashboardController {
/// API endpoint to return app provider dashboard data as JSON
pub async fn app_provider_data_api(session: Session) -> Result<impl Responder> {
/// API endpoint to return application provider dashboard data as JSON
pub async fn application_provider_data_api(session: Session) -> Result<impl Responder> {
// Get user email for debugging
let user_email = session.get::<String>("user_email")
@@ -2875,7 +2875,7 @@ impl DashboardController {
}
}
let fresh_deployments = UserPersistence::get_user_app_deployments(&user_email);
let fresh_deployments = UserPersistence::get_user_application_deployments(&user_email);
// Load user persistent data
if let Some(user) = Self::load_user_with_persistent_data(&session) {
@@ -2936,7 +2936,7 @@ impl DashboardController {
}
}).collect();
let app_provider_data = crate::models::user::AppProviderData {
let application_provider_data = crate::models::user::AppProviderData {
apps: fresh_apps.clone(),
published_apps: fresh_apps.len() as i32,
total_deployments,
@@ -2947,7 +2947,7 @@ impl DashboardController {
revenue_history: Vec::new(),
};
return Ok(ResponseBuilder::ok().json(app_provider_data).build());
return Ok(ResponseBuilder::ok().json(application_provider_data).build());
} else {
}
@@ -3263,7 +3263,7 @@ impl DashboardController {
})).build())
}
/// Add TFC Credits to user balance
/// Add MC Credits to user balance
pub async fn add_tfp(
form: web::Form<AddTfpForm>,
session: Session,
@@ -5311,7 +5311,7 @@ impl DashboardController {
};
// Load user's app deployments
let deployments = UserPersistence::get_user_app_deployments(&user_email);
let deployments = UserPersistence::get_user_application_deployments(&user_email);
// Find the specific deployment
if let Some(deployment) = deployments.iter().find(|d| d.id == deployment_id) {
@@ -5387,7 +5387,7 @@ impl DashboardController {
};
// Check if node exists and get its details first
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -5398,7 +5398,7 @@ impl DashboardController {
};
// Verify node exists and belongs to user
let _node = match farmer_service.get_node_by_id(&user_email, &node_id) {
let _node = match resource_provider_service.get_node_by_id(&user_email, &node_id) {
Some(node) => node,
None => {
return Ok(ResponseBuilder::not_found().json(serde_json::json!({
@@ -5457,7 +5457,7 @@ impl DashboardController {
}
};
let _farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let _resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -5903,7 +5903,7 @@ impl DashboardController {
}
}
/// Stake TFC Credits on a node
/// Stake MC Credits on a node
pub async fn stake_on_node(
session: Session,
path: web::Path<String>,
@@ -5927,7 +5927,7 @@ impl DashboardController {
}
};
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -5970,7 +5970,7 @@ impl DashboardController {
};
// Stake on node
match farmer_service.stake_on_node(&user_email, &node_id, staking_options) {
match resource_provider_service.stake_on_node(&user_email, &node_id, staking_options) {
Ok(()) => {
Ok(ResponseBuilder::ok().json(serde_json::json!({
"success": true,
@@ -6010,7 +6010,7 @@ impl DashboardController {
}
};
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -6030,7 +6030,7 @@ impl DashboardController {
match action {
"unstake" => {
// Unstake from node
match farmer_service.unstake_from_node(&user_email, &node_id) {
match resource_provider_service.unstake_from_node(&user_email, &node_id) {
Ok(returned_amount) => {
Ok(ResponseBuilder::ok().json(serde_json::json!({
"success": true,
@@ -6078,7 +6078,7 @@ impl DashboardController {
};
// Update node staking
match farmer_service.update_node_staking(&user_email, &node_id, staking_options) {
match resource_provider_service.update_node_staking(&user_email, &node_id, staking_options) {
Ok(()) => {
Ok(ResponseBuilder::ok().json(serde_json::json!({
"success": true,
@@ -6120,7 +6120,7 @@ impl DashboardController {
}
};
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -6130,7 +6130,7 @@ impl DashboardController {
}
};
let statistics = farmer_service.get_staking_statistics(&user_email);
let statistics = resource_provider_service.get_staking_statistics(&user_email);
Ok(ResponseBuilder::ok().json(serde_json::json!({
"success": false,
@@ -6293,7 +6293,7 @@ impl DashboardController {
.build())
}
/// Refresh slice calculations for farmer
/// Refresh slice calculations for resource_provider
pub async fn refresh_slice_calculations(session: Session) -> Result<impl Responder> {
let user_email = match session.get::<String>("user_email") {
Ok(Some(email)) => email,
@@ -6303,7 +6303,7 @@ impl DashboardController {
})).build())
};
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -6313,7 +6313,7 @@ impl DashboardController {
}
};
match farmer_service.refresh_all_slice_calculations(&user_email) {
match resource_provider_service.refresh_all_slice_calculations(&user_email) {
Ok(_) => {
Ok(ResponseBuilder::ok().json(serde_json::json!({
"success": false,
@@ -6329,7 +6329,7 @@ impl DashboardController {
}
}
/// Sync farmer nodes with ThreeFold Grid
/// Sync resource_provider nodes with ThreeFold Grid
pub async fn sync_with_grid(session: Session) -> Result<impl Responder> {
// Check authentication
if let Err(response) = Self::check_authentication(&session) {
@@ -6463,7 +6463,7 @@ impl DashboardController {
let node_id = path.into_inner();
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -6473,7 +6473,7 @@ impl DashboardController {
}
};
match farmer_service.get_node_slices(&user_email, node_id) {
match resource_provider_service.get_node_slices(&user_email, node_id) {
Ok(slices) => {
Ok(ResponseBuilder::ok().json(serde_json::json!({
"success": false,
@@ -6525,7 +6525,7 @@ impl DashboardController {
}
// Initialize services
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -6550,7 +6550,7 @@ impl DashboardController {
let mut errors = Vec::new();
for node_id in &node_ids {
match farmer_service.fetch_and_validate_grid_node(*node_id).await {
match resource_provider_service.fetch_and_validate_grid_node(*node_id).await {
Ok(node_data) => {
// Calculate automatic slices
let total_base_slices = slice_calculator.calculate_max_base_slices(&node_data.capacity);
@@ -6581,7 +6581,7 @@ impl DashboardController {
rental_options: None,
earnings_today_usd: rust_decimal::Decimal::ZERO,
region: if node_data.country.is_empty() { "Unknown".to_string() } else { node_data.country.clone() },
node_type: "3Node".to_string(),
node_type: "MyceliumNode".to_string(),
slice_formats: None,
staking_options: None,
availability_status: crate::models::user::NodeAvailabilityStatus::Available,
@@ -6709,8 +6709,8 @@ impl DashboardController {
})).build());
}
// Initialize farmer service
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
// Initialize resource_provider service
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -6722,7 +6722,7 @@ impl DashboardController {
// Add nodes with automatic slice management
match farmer_service.add_multiple_grid_nodes_with_automatic_slices(
match resource_provider_service.add_multiple_grid_nodes_with_automatic_slices(
&user_email,
node_ids.clone(),
base_slice_price,
@@ -6759,7 +6759,7 @@ impl DashboardController {
}
}
/// API endpoint to refresh slice calculations for all farmer nodes
/// API endpoint to refresh slice calculations for all resource_provider nodes
pub async fn refresh_slice_calculations_api(session: Session) -> Result<impl Responder> {
let user_email = session.get::<String>("user_email")
@@ -6773,8 +6773,8 @@ impl DashboardController {
})).build());
}
// Initialize farmer service
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
// Initialize resource_provider service
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -6785,7 +6785,7 @@ impl DashboardController {
};
// Refresh slice calculations for all nodes
match farmer_service.refresh_all_slice_calculations_async(&user_email).await {
match resource_provider_service.refresh_all_slice_calculations_async(&user_email).await {
Ok(updated_nodes) => {
Ok(ResponseBuilder::ok().json(serde_json::json!({
@@ -6817,8 +6817,8 @@ impl DashboardController {
})).build());
}
// Initialize farmer service
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
// Initialize resource_provider service
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -6829,7 +6829,7 @@ impl DashboardController {
};
// Sync all nodes with grid
match farmer_service.sync_all_nodes_with_grid_async(&user_email).await {
match resource_provider_service.sync_all_nodes_with_grid_async(&user_email).await {
Ok(synced_nodes) => {
Ok(ResponseBuilder::ok().json(serde_json::json!({
@@ -6863,8 +6863,8 @@ impl DashboardController {
let node_id = path.into_inner();
// Initialize farmer service
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
// Initialize resource_provider service
let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() {
Ok(service) => service,
Err(e) => {
return Ok(ResponseBuilder::internal_error().json(serde_json::json!({
@@ -6875,7 +6875,7 @@ impl DashboardController {
};
// Get node slice details
match farmer_service.get_node_slice_details(&user_email, &node_id) {
match resource_provider_service.get_node_slice_details(&user_email, &node_id) {
Ok(slice_details) => {
Ok(ResponseBuilder::ok().json(serde_json::json!({
"success": false,

View File

@@ -57,12 +57,12 @@ impl DocsController {
render_template(&tmpl, "docs/getting_started.html", &ctx)
}
/// Renders the 3Nodes documentation page
pub async fn three_nodes(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
/// Renders the Mycelium Nodes documentation page
pub async fn mycelium_nodes(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let mut ctx = crate::models::builders::ContextBuilder::new()
.active_page("docs")
.build();
ctx.insert("active_section", "3nodes");
ctx.insert("active_section", "mycelium_nodes");
let config = get_app_config();
ctx.insert("gitea_enabled", &config.is_gitea_enabled());
@@ -75,7 +75,7 @@ impl DocsController {
}
}
render_template(&tmpl, "docs/3nodes.html", &ctx)
render_template(&tmpl, "docs/mycelium_nodes.html", &ctx)
}
/// Renders the compute resources documentation page

View File

@@ -13,7 +13,7 @@ use chrono::Utc;
/// Form data for slice rental requests
#[derive(Debug, Deserialize)]
pub struct SliceRentalForm {
pub farmer_email: String,
pub resource_provider_email: String,
pub node_id: String,
pub combination_id: String,
pub quantity: u32,
@@ -377,8 +377,8 @@ impl MarketplaceController {
render_template(&tmpl, "marketplace/compute_resources.html", &ctx)
}
/// Renders the 3Nodes marketplace page with REAL farmer nodes from database
pub async fn three_nodes(tmpl: web::Data<Tera>, session: Session, query: web::Query<std::collections::HashMap<String, String>>) -> Result<impl Responder> {
/// Renders the Mycelium Nodes marketplace page with REAL resource_provider nodes from database
pub async fn mycelium_nodes(tmpl: web::Data<Tera>, session: Session, query: web::Query<std::collections::HashMap<String, String>>) -> Result<impl Responder> {
// Build services using established builder pattern
let currency_service = CurrencyService::builder()
.build()
@@ -395,7 +395,7 @@ impl MarketplaceController {
// Build context using ContextBuilder pattern
let mut ctx = crate::models::builders::ContextBuilder::new()
.active_page("marketplace")
.active_section("three_nodes")
.active_section("mycelium_nodes")
.user_if_available(&session)
.build();
@@ -411,7 +411,7 @@ impl MarketplaceController {
.unwrap_or(0);
let page_size = 12;
// Get all real farmer nodes as marketplace products
// Get all real resource_provider nodes as marketplace products
let all_node_products = node_marketplace_service.get_all_marketplace_nodes();
// Clone query for reuse
@@ -484,7 +484,7 @@ impl MarketplaceController {
ctx.insert("node_statistics", &node_marketplace_service.get_capacity_statistics());
ctx.insert("available_regions", &node_marketplace_service.get_available_regions());
render_template(&tmpl, "marketplace/three_nodes.html", &ctx)
render_template(&tmpl, "marketplace/mycelium_nodes.html", &ctx)
}
/// Renders the gateways marketplace page
@@ -933,7 +933,7 @@ impl MarketplaceController {
pub async fn show_slice_rental_form(
tmpl: web::Data<Tera>,
session: Session,
path: web::Path<(String, String, String)> // farmer_email, node_id, combination_id
path: web::Path<(String, String, String)> // resource_provider_email, node_id, combination_id
) -> Result<impl Responder> {
let mut ctx = crate::models::builders::ContextBuilder::new()
.active_page("marketplace")
@@ -951,7 +951,7 @@ impl MarketplaceController {
}
}
let (farmer_email, node_id, combination_id) = path.into_inner();
let (resource_provider_email, node_id, combination_id) = path.into_inner();
// Get slice details for the form
let node_marketplace_service = NodeMarketplaceService::builder()
@@ -963,12 +963,12 @@ impl MarketplaceController {
// Find the specific slice combination by checking product attributes
if let Some(slice_product) = slice_combinations.iter().find(|p| {
// Check if this product matches the requested slice
if let (Some(farmer_attr), Some(node_attr), Some(combo_attr)) = (
p.attributes.get("farmer_email"),
if let (Some(resource_provider_attr), Some(node_attr), Some(combo_attr)) = (
p.attributes.get("resource_provider_email"),
p.attributes.get("node_id"),
p.attributes.get("combination_id")
) {
farmer_attr.value.as_str() == Some(&farmer_email) &&
resource_provider_attr.value.as_str() == Some(&resource_provider_email) &&
node_attr.value.as_str() == Some(&node_id) &&
combo_attr.value.as_str() == Some(&combination_id)
} else {
@@ -998,11 +998,11 @@ impl MarketplaceController {
slice_info.insert("price_per_hour", serde_json::Value::String(slice_product.base_price.to_string()));
slice_info.insert("node_id", serde_json::Value::String(node_id.clone()));
slice_info.insert("farmer_email", serde_json::Value::String(farmer_email.clone()));
slice_info.insert("resource_provider_email", serde_json::Value::String(resource_provider_email.clone()));
slice_info.insert("combination_id", serde_json::Value::String(combination_id.clone()));
ctx.insert("slice", &slice_info);
ctx.insert("farmer_email", &farmer_email);
ctx.insert("resource_provider_email", &resource_provider_email);
ctx.insert("node_id", &node_id);
ctx.insert("combination_id", &combination_id);
}
@@ -1109,7 +1109,7 @@ impl MarketplaceController {
// Attempt to rent the slice with deployment options
match slice_rental_service.rent_slice_combination_with_deployment(
&user_email,
&form.farmer_email,
&form.resource_provider_email,
&form.node_id,
&form.combination_id,
form.quantity,
@@ -1134,7 +1134,7 @@ impl MarketplaceController {
meta.insert("deployment_type".to_string(), serde_json::Value::String(form.deployment_type.clone()));
meta.insert("deployment_name".to_string(), serde_json::Value::String(deployment_name.clone()));
meta.insert("quantity".to_string(), serde_json::Value::Number(serde_json::Number::from(form.quantity)));
meta.insert("farmer_email".to_string(), serde_json::Value::String(form.farmer_email.clone()));
meta.insert("resource_provider_email".to_string(), serde_json::Value::String(form.resource_provider_email.clone()));
if form.deployment_type == "kubernetes" {
meta.insert("k8s_masters".to_string(), serde_json::Value::Number(serde_json::Number::from(form.k8s_masters.unwrap_or(1))));
meta.insert("k8s_workers".to_string(), serde_json::Value::Number(serde_json::Number::from(form.k8s_workers.unwrap_or(1))));
@@ -1206,7 +1206,7 @@ impl MarketplaceController {
// Attempt to rent the slice with deployment options
match slice_rental_service.rent_slice_combination_with_deployment(
&user_email,
&form.farmer_email,
&form.resource_provider_email,
&form.node_id,
&form.combination_id,
form.quantity,
@@ -1231,7 +1231,7 @@ impl MarketplaceController {
meta.insert("deployment_type".to_string(), serde_json::Value::String(form.deployment_type.clone()));
meta.insert("deployment_name".to_string(), serde_json::Value::String(form.deployment_name.clone()));
meta.insert("quantity".to_string(), serde_json::Value::Number(serde_json::Number::from(form.quantity)));
meta.insert("farmer_email".to_string(), serde_json::Value::String(form.farmer_email.clone()));
meta.insert("resource_provider_email".to_string(), serde_json::Value::String(form.resource_provider_email.clone()));
serde_json::Value::Object(meta.into_iter().collect())
}),
category: "slice_rental".to_string(),
@@ -1270,7 +1270,7 @@ impl MarketplaceController {
/// Request structure for slice rental with deployment options
#[derive(serde::Deserialize)]
pub struct SliceRentalRequest {
pub farmer_email: String,
pub resource_provider_email: String,
pub node_id: String,
pub combination_id: String,
pub quantity: u32,
@@ -1376,8 +1376,8 @@ pub fn create_marketplace_product_from_app(app: &crate::models::user::PublishedA
.category_id("application")
.base_price(rust_decimal::Decimal::from(app.monthly_revenue_usd.max(rust_decimal::Decimal::ONE))) // Use monthly revenue as base price, minimum $1
.base_currency("USD")
.provider_id("user-app-provider")
.provider_name("App Provider")
.provider_id("user-application-provider")
.provider_name("Application Provider")
.availability(availability)
.metadata(metadata)
.add_attribute("app_type", ProductAttribute {
@@ -1503,7 +1503,7 @@ fn reconstruct_service_from_json(service_value: &serde_json::Value) -> Result<cr
// Attempt to rent the slice combination
match slice_rental_service.rent_slice_combination(
&user_email,
&form.farmer_email,
&form.resource_provider_email,
&form.node_id,
&form.combination_id,
form.quantity,
@@ -1602,7 +1602,7 @@ fn reconstruct_service_from_json(service_value: &serde_json::Value) -> Result<cr
// Create SliceAssignmentRequest from the data
let assignment_request_obj = SliceAssignmentRequest {
user_email: user_email.clone(),
farmer_email: assignment_request.get("farmer_email")
resource_provider_email: assignment_request.get("resource_provider_email")
.and_then(|v| v.as_str())
.unwrap_or("unknown@example.com")
.to_string(),

View File

@@ -88,8 +88,8 @@ impl PublicController {
render_template(&tmpl, "legal/terms.html", &ctx)
}
/// Renders the farmers terms page
pub async fn terms_farmers(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
/// Renders the resource providers terms page
pub async fn terms_resource_providers(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let mut ctx = crate::models::builders::ContextBuilder::new()
.build();
ctx.insert("active_page", "terms");
@@ -105,7 +105,7 @@ impl PublicController {
}
}
render_template(&tmpl, "legal/terms-farmers.html", &ctx)
render_template(&tmpl, "legal/terms-resource_providers.html", &ctx)
}
/// Renders the service providers terms page
@@ -128,8 +128,8 @@ impl PublicController {
render_template(&tmpl, "legal/terms-service-providers.html", &ctx)
}
/// Renders the solution providers terms page
pub async fn terms_solution_providers(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
/// Renders the application providers terms page
pub async fn terms_application_providers(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let mut ctx = crate::models::builders::ContextBuilder::new()
.build();
ctx.insert("active_page", "terms");
@@ -145,7 +145,7 @@ impl PublicController {
}
}
render_template(&tmpl, "legal/terms-solution-providers.html", &ctx)
render_template(&tmpl, "legal/terms-application-providers.html", &ctx)
}
/// Renders the users terms page

View File

@@ -173,7 +173,7 @@ impl RentalController {
rental_id: rental_id.clone(),
slice_combination_id: format!("combo-{}", rental_id),
node_id: "node-placeholder".to_string(), // TODO: Get from product
farmer_email: "farmer@example.com".to_string(), // TODO: Get from product
resource_provider_email: "resource_provider@example.com".to_string(), // TODO: Get from product
slice_allocation: crate::services::slice_calculator::SliceAllocation {
allocation_id: format!("alloc-{}", rental_id),
slice_combination_id: format!("combo-{}", rental_id),

View File

@@ -33,5 +33,5 @@ pub mod utils;
// Re-export commonly used types
pub use models::user::{User, NodeStakingOptions, FarmNode};
pub use services::farmer::FarmerService;
pub use services::resource_provider::ResourceProviderService;
pub use services::user_persistence::UserPersistence;

View File

@@ -1234,11 +1234,11 @@ impl SessionDataBuilder {
// MockDataBuilder removed - using persistent data only
// =============================================================================
// FARMER DATA BUILDER
// RESOURCE_PROVIDER DATA BUILDER
// =============================================================================
#[derive(Default)]
pub struct FarmerDataBuilder {
pub struct ResourceProviderDataBuilder {
total_nodes: Option<i32>,
online_nodes: Option<i32>,
total_capacity: Option<crate::models::user::NodeCapacity>,
@@ -1251,7 +1251,7 @@ pub struct FarmerDataBuilder {
active_slices: Option<i32>,
}
impl FarmerDataBuilder {
impl ResourceProviderDataBuilder {
pub fn new() -> Self {
Self::default()
}
@@ -1373,8 +1373,8 @@ impl FarmerDataBuilder {
self
}
pub fn build(self) -> Result<crate::models::user::FarmerData, String> {
Ok(crate::models::user::FarmerData {
pub fn build(self) -> Result<crate::models::user::ResourceProviderData, String> {
Ok(crate::models::user::ResourceProviderData {
total_nodes: self.total_nodes.unwrap_or(0),
online_nodes: self.online_nodes.unwrap_or(0),
total_capacity: self.total_capacity.unwrap_or(crate::models::user::NodeCapacity {
@@ -1445,8 +1445,8 @@ impl FarmerDataBuilder {
#[derive(Default)]
pub struct SliceProductBuilder {
farmer_id: Option<String>,
farmer_name: Option<String>,
resource_provider_id: Option<String>,
resource_provider_name: Option<String>,
slice_name: Option<String>,
cpu_cores: Option<i32>,
memory_gb: Option<i32>,
@@ -1464,13 +1464,13 @@ impl SliceProductBuilder {
Self::default()
}
pub fn farmer_id(mut self, farmer_id: impl Into<String>) -> Self {
self.farmer_id = Some(farmer_id.into());
pub fn resource_provider_id(mut self, resource_provider_id: impl Into<String>) -> Self {
self.resource_provider_id = Some(resource_provider_id.into());
self
}
pub fn farmer_name(mut self, farmer_name: impl Into<String>) -> Self {
self.farmer_name = Some(farmer_name.into());
pub fn resource_provider_name(mut self, resource_provider_name: impl Into<String>) -> Self {
self.resource_provider_name = Some(resource_provider_name.into());
self
}
@@ -1525,8 +1525,8 @@ impl SliceProductBuilder {
}
pub fn build(self) -> Result<crate::models::product::Product, String> {
let farmer_id = self.farmer_id.ok_or("farmer_id is required")?;
let farmer_name = self.farmer_name.ok_or("farmer_name is required")?;
let resource_provider_id = self.resource_provider_id.ok_or("resource_provider_id is required")?;
let resource_provider_name = self.resource_provider_name.ok_or("resource_provider_name is required")?;
let slice_name = self.slice_name.ok_or("slice_name is required")?;
let cpu_cores = self.cpu_cores.ok_or("cpu_cores is required")?;
let memory_gb = self.memory_gb.ok_or("memory_gb is required")?;
@@ -1552,8 +1552,8 @@ impl SliceProductBuilder {
};
Ok(crate::models::product::Product::create_slice_product(
farmer_id,
farmer_name,
resource_provider_id,
resource_provider_name,
slice_name,
slice_config,
price_per_hour,
@@ -1731,7 +1731,7 @@ impl FarmNodeBuilder {
last_seen: Some(self.last_seen.unwrap_or_else(|| chrono::Utc::now())),
health_score: self.health_score.unwrap_or(0.0),
region: self.region.unwrap_or_else(|| "Unknown".to_string()),
node_type: self.node_type.unwrap_or_else(|| "3Node".to_string()),
node_type: self.node_type.unwrap_or_else(|| "MyceliumNode".to_string()),
slice_formats: None,
rental_options: self.rental_options.map(|ro| serde_json::to_value(&ro).unwrap_or_default()),
staking_options: None,
@@ -2081,7 +2081,7 @@ impl NodeRentalBuilder {
}
#[derive(Default)]
pub struct FarmerRentalEarningBuilder {
pub struct ResourceProviderRentalEarningBuilder {
id: Option<String>,
node_id: Option<String>,
rental_id: Option<String>,
@@ -2093,7 +2093,7 @@ pub struct FarmerRentalEarningBuilder {
payment_status: Option<crate::models::user::PaymentStatus>,
}
impl FarmerRentalEarningBuilder {
impl ResourceProviderRentalEarningBuilder {
pub fn new() -> Self {
Self::default()
}
@@ -2143,10 +2143,10 @@ impl FarmerRentalEarningBuilder {
self
}
pub fn build(self) -> Result<crate::models::user::FarmerRentalEarning, String> {
pub fn build(self) -> Result<crate::models::user::ResourceProviderRentalEarning, String> {
let id = self.id.unwrap_or_else(|| format!("earning_{}", uuid::Uuid::new_v4()));
Ok(crate::models::user::FarmerRentalEarning {
Ok(crate::models::user::ResourceProviderRentalEarning {
id,
node_id: self.node_id.ok_or("node_id is required")?,
rental_id: self.rental_id.ok_or("rental_id is required")?,
@@ -2469,8 +2469,8 @@ impl NodeCreationDataBuilder {
self
}
pub fn build(self) -> Result<crate::services::farmer::NodeCreationData, String> {
Ok(crate::services::farmer::NodeCreationData {
pub fn build(self) -> Result<crate::services::resource_provider::NodeCreationData, String> {
Ok(crate::services::resource_provider::NodeCreationData {
name: self.name.ok_or("name is required")?,
location: self.location.ok_or("location is required")?,
cpu_cores: self.cpu_cores.unwrap_or(4),

View File

@@ -305,10 +305,10 @@ pub enum SliceType {
}
impl Product {
/// Create a slice product from farmer configuration
/// Create a slice product from resource_provider configuration
pub fn create_slice_product(
farmer_id: String,
farmer_name: String,
resource_provider_id: String,
resource_provider_name: String,
slice_name: String,
slice_config: SliceConfiguration,
price_per_hour: Decimal,
@@ -322,8 +322,8 @@ impl Product {
slice_config.cpu_cores, slice_config.memory_gb, slice_config.storage_gb),
price_per_hour,
"USD".to_string(),
farmer_id,
farmer_name,
resource_provider_id,
resource_provider_name,
);
// Add slice-specific attributes
@@ -448,13 +448,13 @@ impl Product {
/// Create a full node product from a FarmNode
pub fn create_full_node_product(
node: &crate::models::user::FarmNode,
farmer_email: &str,
farmer_name: &str,
resource_provider_email: &str,
resource_provider_name: &str,
) -> Self {
let mut product = Product {
id: format!("fullnode_{}", node.id),
name: format!("Full Node: {}", node.name),
category_id: "3nodes".to_string(),
category_id: "mycelium_nodes".to_string(),
description: format!(
"Exclusive access to {} with {} CPU cores, {}GB RAM, {}GB storage in {}",
node.name, node.capacity.cpu_cores, node.capacity.memory_gb,
@@ -469,8 +469,8 @@ impl Product {
.unwrap_or_else(|| Decimal::from(200)), // Default price
base_currency: "USD".to_string(),
attributes: HashMap::new(),
provider_id: farmer_email.to_string(),
provider_name: farmer_name.to_string(),
provider_id: resource_provider_email.to_string(),
provider_name: resource_provider_name.to_string(),
availability: match node.availability_status {
crate::models::user::NodeAvailabilityStatus::Available => ProductAvailability::Available,
crate::models::user::NodeAvailabilityStatus::PartiallyRented => ProductAvailability::Limited,

View File

@@ -146,7 +146,7 @@ pub struct RegionDeployments {
pub gateways: i32,
}
/// Node information for farmer dashboard
/// Node information for resource_provider dashboard
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeInfo {
pub id: String,
@@ -200,14 +200,14 @@ impl std::fmt::Display for NodeStatus {
}
}
/// Default maintenance window for farmer settings
/// Default maintenance window for resource_provider settings
pub fn default_maintenance_window() -> String {
"02:00-04:00 UTC".to_string()
}
/// Farmer configuration settings
/// ResourceProvider configuration settings
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FarmerSettings {
pub struct ResourceProviderSettings {
pub auto_accept_reserved_slices: bool,
pub maintenance_window: String, // e.g., "02:00-04:00 UTC"
pub notification_email: Option<String>,
@@ -229,7 +229,7 @@ pub struct FarmerSettings {
pub preferred_regions: Vec<String>,
}
impl Default for FarmerSettings {
impl Default for ResourceProviderSettings {
fn default() -> Self {
Self {
auto_accept_reserved_slices: true,
@@ -312,7 +312,7 @@ impl Default for LiquidityPoolConfig {
fn default() -> Self {
Self {
pool_id: String::new(),
base_token: "TFC".to_string(),
base_token: "MC".to_string(),
quote_token: "USD".to_string(),
fee_tier: 0.3,
minimum_liquidity: rust_decimal_macros::dec!(100.0),
@@ -324,7 +324,7 @@ impl Default for LiquidityPoolConfig {
}
}
impl Default for FarmerData {
impl Default for ResourceProviderData {
fn default() -> Self {
Self {
total_nodes: 0,
@@ -364,8 +364,8 @@ impl Default for YieldFarmConfig {
fn default() -> Self {
Self {
farm_id: String::new(),
reward_token: "TFC".to_string(),
staked_token: "TFC".to_string(),
reward_token: "MC".to_string(),
staked_token: "MC".to_string(),
apr: 12.0,
lock_duration_days: 365,
minimum_stake: rust_decimal_macros::dec!(1000.0),
@@ -405,11 +405,11 @@ impl Default for ServiceLevelAgreement {
/// Application deployment configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppDeployment {
pub struct ApplicationDeployment {
pub id: String,
pub app_id: String,
pub customer_email: String,
pub deployment_status: AppDeploymentStatus,
pub deployment_status: ApplicationDeploymentStatus,
pub resource_allocation: ResourceUtilization,
pub monthly_cost: rust_decimal::Decimal,
pub deployed_at: Option<DateTime<Utc>>,
@@ -418,9 +418,9 @@ pub struct AppDeployment {
pub monitoring_enabled: bool,
}
/// App deployment status
/// Application deployment status
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AppDeploymentStatus {
pub enum ApplicationDeploymentStatus {
Pending,
Deploying,
Running,
@@ -429,7 +429,7 @@ pub enum AppDeploymentStatus {
Maintenance,
}
impl Default for AppDeploymentStatus {
impl Default for ApplicationDeploymentStatus {
fn default() -> Self {
Self::Pending
}
@@ -761,9 +761,9 @@ pub struct QuickAction {
pub enabled: bool,
}
/// Farmer-specific data
/// ResourceProvider-specific data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FarmerData {
pub struct ResourceProviderData {
pub total_nodes: i32,
pub active_nodes: i32,
pub total_monthly_earnings_usd: i32,
@@ -1224,7 +1224,7 @@ where
.map_err(serde::de::Error::custom)
}
/// Node group for farmer organization
/// Node group for resource_provider organization
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeGroup {
pub id: String,
@@ -1358,7 +1358,7 @@ pub struct NodeCapacity {
pub ram_gb: i32,
}
/// Farmer node information
/// ResourceProvider node information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FarmNode {
pub id: String,
@@ -1418,7 +1418,7 @@ pub struct FarmNode {
pub health_score: f32,
}
/// Earnings record for farmer data
/// Earnings record for resource_provider data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EarningsRecord {
pub date: String,
@@ -1428,7 +1428,7 @@ pub struct EarningsRecord {
pub source: String,
}
/// Group statistics for farmer dashboard
/// Group statistics for resource_provider dashboard
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GroupStatistics {
pub group_name: String,
@@ -1517,7 +1517,7 @@ pub struct GridNodeData {
/// Additional missing user model types
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FarmerRentalEarning {
pub struct ResourceProviderRentalEarning {
pub date: String,
pub amount: Decimal,
pub rental_id: String,

View File

@@ -46,13 +46,13 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
// Marketplace routes
.route("/marketplace", web::get().to(MarketplaceController::dashboard))
.route("/marketplace/compute", web::get().to(MarketplaceController::compute_resources))
.route("/marketplace/3nodes", web::get().to(MarketplaceController::three_nodes))
.route("/marketplace/mycelium_nodes", web::get().to(MarketplaceController::mycelium_nodes))
.route("/marketplace/gateways", web::get().to(MarketplaceController::gateways))
.route("/marketplace/applications", web::get().to(MarketplaceController::applications))
.route("/marketplace/services", web::get().to(MarketplaceController::services))
.route("/marketplace/statistics", web::get().to(MarketplaceController::statistics))
// Slice rental routes
.route("/marketplace/slice/rent/{farmer_email}/{node_id}/{combination_id}", web::get().to(MarketplaceController::show_slice_rental_form))
.route("/marketplace/slice/rent/{resource_provider_email}/{node_id}/{combination_id}", web::get().to(MarketplaceController::show_slice_rental_form))
.route("/marketplace/slice/rent", web::post().to(MarketplaceController::process_slice_rental))
// .route("/marketplace/rent-slice", web::post().to(MarketplaceController::rent_slice)) // Legacy route [DISABLED]
// Product routes
@@ -107,8 +107,8 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
.route("/dashboard/slice-rentals/{id}/manage", web::post().to(DashboardController::manage_slice_rental_deployment))
.route("/dashboard/slice-rentals/{id}", web::delete().to(DashboardController::cancel_slice_rental))
.route("/dashboard/user/slice-rentals/{id}", web::post().to(DashboardController::manage_slice_rental))
.route("/dashboard/farmer-data", web::get().to(DashboardController::farmer_data_api))
.route("/dashboard/app-provider-data", web::get().to(DashboardController::app_provider_data_api))
.route("/dashboard/resource_provider-data", web::get().to(DashboardController::resource_provider_data_api))
.route("/dashboard/application-provider-data", web::get().to(DashboardController::application_provider_data_api))
.route("/dashboard/slice-products", web::get().to(DashboardController::get_slice_products))
.route("/dashboard/slice-products", web::post().to(DashboardController::create_slice_product))
.route("/dashboard/slice-products/{id}", web::delete().to(DashboardController::delete_slice_product))
@@ -116,19 +116,19 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
.route("/dashboard/slice-details/{id}", web::get().to(DashboardController::get_slice_details))
.route("/dashboard/slice-configuration/{id}", web::put().to(DashboardController::update_slice_configuration))
.route("/dashboard/service-provider-data", web::get().to(DashboardController::service_provider_data_api))
// Farmer management API routes
.route("/dashboard/farm-nodes", web::post().to(DashboardController::add_farm_node))
.route("/dashboard/farm-nodes-enhanced", web::post().to(DashboardController::add_farm_node_enhanced))
.route("/dashboard/farm-nodes/{id}", web::get().to(DashboardController::get_node_details))
.route("/dashboard/farm-nodes/{id}", web::put().to(DashboardController::update_node_comprehensive))
.route("/dashboard/farm-nodes/{id}/status", web::put().to(DashboardController::update_node_status))
// Farmer slice management API routes
.route("/dashboard/farmer/slice-calculations/refresh", web::post().to(DashboardController::refresh_slice_calculations))
.route("/dashboard/farmer/grid-sync", web::post().to(DashboardController::sync_with_grid))
.route("/dashboard/farmer/nodes/{id}/slices", web::get().to(DashboardController::get_node_slices))
.route("/dashboard/farmer/slice-statistics", web::get().to(DashboardController::get_slice_statistics))
.route("/dashboard/farm-nodes/{id}", web::delete().to(DashboardController::delete_node))
.route("/dashboard/farm-nodes/{id}/configuration", web::put().to(DashboardController::update_node_configuration))
// Resource provider management API routes
.route("/dashboard/resource_provider-nodes", web::post().to(DashboardController::add_farm_node))
.route("/dashboard/resource_provider-nodes-enhanced", web::post().to(DashboardController::add_farm_node_enhanced))
.route("/dashboard/resource_provider-nodes/{id}", web::get().to(DashboardController::get_node_details))
.route("/dashboard/resource_provider-nodes/{id}", web::put().to(DashboardController::update_node_comprehensive))
.route("/dashboard/resource_provider-nodes/{id}/status", web::put().to(DashboardController::update_node_status))
// Resource provider slice management API routes
.route("/dashboard/resource_provider/slice-calculations/refresh", web::post().to(DashboardController::refresh_slice_calculations))
.route("/dashboard/resource_provider/grid-sync", web::post().to(DashboardController::sync_with_grid))
.route("/dashboard/resource_provider/nodes/{id}/slices", web::get().to(DashboardController::get_node_slices))
.route("/dashboard/resource_provider/slice-statistics", web::get().to(DashboardController::get_slice_statistics))
.route("/dashboard/resource_provider-nodes/{id}", web::delete().to(DashboardController::delete_node))
.route("/dashboard/resource_provider-nodes/{id}/configuration", web::put().to(DashboardController::update_node_configuration))
.route("/dashboard/default-slice-formats", web::get().to(DashboardController::get_default_slice_formats))
.route("/dashboard/default-slice-details/{id}", web::get().to(DashboardController::get_default_slice_details))
.route("/dashboard/default-slice-customization/{id}", web::put().to(DashboardController::save_default_slice_customization))
@@ -148,8 +148,8 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
.route("/dashboard/node-groups/{id}", web::delete().to(DashboardController::delete_custom_node_group))
.route("/dashboard/nodes/assign-group", web::post().to(DashboardController::assign_node_to_group))
// Node staking API routes
.route("/dashboard/farm-nodes/{id}/stake", web::post().to(DashboardController::stake_on_node))
.route("/dashboard/farm-nodes/{id}/staking", web::put().to(DashboardController::update_node_staking))
.route("/dashboard/resource_provider-nodes/{id}/stake", web::post().to(DashboardController::stake_on_node))
.route("/dashboard/resource_provider-nodes/{id}/staking", web::put().to(DashboardController::update_node_staking))
.route("/dashboard/staking/statistics", web::get().to(DashboardController::get_staking_statistics))
// Service management API routes
.route("/dashboard/services", web::get().to(DashboardController::get_user_services))
@@ -245,7 +245,7 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
// Documentation routes
.route("/docs", web::get().to(DocsController::index))
.route("/docs/getting-started", web::get().to(DocsController::getting_started))
.route("/docs/3nodes", web::get().to(DocsController::three_nodes))
.route("/docs/mycelium_nodes", web::get().to(DocsController::mycelium_nodes))
.route("/docs/compute", web::get().to(DocsController::compute))
.route("/docs/gateways", web::get().to(DocsController::gateways))
.route("/docs/applications", web::get().to(DocsController::applications))
@@ -260,8 +260,8 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
.wrap(JwtAuth) // Apply authentication middleware to all dashboard routes
.route("", web::get().to(DashboardController::index))
.route("/user", web::get().to(DashboardController::user_section))
.route("/farmer", web::get().to(DashboardController::farmer_section))
.route("/app-provider", web::get().to(DashboardController::app_provider_section))
.route("/resource_provider", web::get().to(DashboardController::resource_provider_section))
.route("/application-provider", web::get().to(DashboardController::application_provider_section))
.route("/service-provider", web::get().to(DashboardController::service_provider_section))
// Shopping routes - embedded in dashboard
@@ -282,9 +282,9 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
// Public information routes (legal, changelog, roadmap)
.route("/privacy", web::get().to(PublicController::privacy))
.route("/terms", web::get().to(PublicController::terms))
.route("/terms/farmers", web::get().to(PublicController::terms_farmers))
.route("/terms/resource_providers", web::get().to(PublicController::terms_resource_providers))
.route("/terms/service-providers", web::get().to(PublicController::terms_service_providers))
.route("/terms/solution-providers", web::get().to(PublicController::terms_solution_providers))
.route("/terms/application-providers", web::get().to(PublicController::terms_application_providers))
.route("/terms/users", web::get().to(PublicController::terms_users))
.route("/changelog", web::get().to(PublicController::changelog))
.route("/roadmap", web::get().to(PublicController::roadmap));

View File

@@ -99,11 +99,11 @@ impl CurrencyService {
last_updated: chrono::Utc::now(),
},
Currency {
code: "TFC".to_string(),
name: "ThreeFold Credits".to_string(),
symbol: "TFC".to_string(),
code: "MC".to_string(),
name: "Mycelium Credit".to_string(),
symbol: "MC".to_string(),
currency_type: crate::models::currency::CurrencyType::Custom("credits".to_string()),
exchange_rate_to_base: dec!(1.0), // 1 TFC = 1 USD
exchange_rate_to_base: dec!(1.0), // 1 MC = 1 USD
is_base_currency: false,
decimal_places: 2,
is_active: true,

View File

@@ -2,7 +2,7 @@
pub mod auto_topup;
pub mod currency;
pub mod factory;
pub mod farmer;
pub mod resource_provider;
pub mod grid;
pub mod instant_purchase;
pub mod navbar;

View File

@@ -1,4 +1,4 @@
//! Node marketplace service for aggregating farmer nodes into marketplace products
//! Node marketplace service for aggregating resource_provider nodes into marketplace products
//! Follows the established builder pattern for consistent API design
use crate::models::user::FarmNode;
@@ -11,7 +11,7 @@ use rust_decimal::prelude::ToPrimitive;
use std::collections::HashMap;
use std::str::FromStr;
/// Service for converting farmer nodes to marketplace products
/// Service for converting resource_provider nodes to marketplace products
#[derive(Clone)]
pub struct NodeMarketplaceService {
currency_service: CurrencyService,
@@ -89,7 +89,7 @@ impl NodeMarketplaceService {
}
}
/// Get all farmer nodes as marketplace products
/// Get all resource_provider nodes as marketplace products
pub fn get_all_marketplace_nodes(&self) -> Vec<Product> {
let mut all_products = Vec::new();
@@ -110,7 +110,7 @@ impl NodeMarketplaceService {
for node in nodes {
// Filter by node type and status
if node.node_type == "3Node" && (self.include_offline_nodes || self.is_node_online(&node)) {
if node.node_type == "MyceliumNode" && (self.include_offline_nodes || self.is_node_online(&node)) {
if let Ok(product) = self.convert_node_to_product(&node, &user_email) {
all_products.push(product);
}
@@ -125,16 +125,16 @@ impl NodeMarketplaceService {
}
/// Convert FarmNode to Product using builder pattern
pub fn convert_node_to_product(&self, node: &FarmNode, farmer_email: &str) -> Result<Product, String> {
pub fn convert_node_to_product(&self, node: &FarmNode, resource_provider_email: &str) -> Result<Product, String> {
// Calculate price based on node capacity
let hourly_price = self.calculate_node_price(node)?;
// Create product attributes with node specifications
let mut attributes = HashMap::new();
attributes.insert("farmer_email".to_string(), crate::models::product::ProductAttribute {
key: "farmer_email".to_string(),
value: serde_json::Value::String(farmer_email.to_string()),
attributes.insert("resource_provider_email".to_string(), crate::models::product::ProductAttribute {
key: "resource_provider_email".to_string(),
value: serde_json::Value::String(resource_provider_email.to_string()),
attribute_type: crate::models::product::AttributeType::Text,
is_searchable: true,
is_filterable: true,
@@ -200,12 +200,12 @@ impl NodeMarketplaceService {
display_order: Some(6),
});
// Get farmer display name
let farmer_display_name = self.get_farmer_display_name(farmer_email);
// Get resource_provider display name
let resource_provider_display_name = self.get_resource_provider_display_name(resource_provider_email);
// Create metadata with location
let metadata = crate::models::product::ProductMetadata {
tags: vec!["3node".to_string(), "hardware".to_string(), node.region.clone()],
tags: vec!["mycelium_node".to_string(), "hardware".to_string(), node.region.clone()],
location: Some(node.location.clone()),
rating: Some(node.health_score / 20.0), // Convert health score to 5-star rating
review_count: 0,
@@ -215,8 +215,8 @@ impl NodeMarketplaceService {
// Use Product builder pattern with add_attribute for each attribute
let mut builder = crate::models::product::Product::builder()
.id(format!("node_{}", node.id))
.name(format!("{} - {}", node.name, farmer_display_name))
.description(format!("3Node with {} CPU cores, {} GB RAM, {} GB storage in {}. Uptime: {:.1}%, Health Score: {:.1}",
.name(format!("{} - {}", node.name, resource_provider_display_name))
.description(format!("Mycelium Node with {} CPU cores, {} GB RAM, {} GB storage in {}. Uptime: {:.1}%, Health Score: {:.1}",
node.capacity.cpu_cores,
node.capacity.memory_gb,
node.capacity.storage_gb,
@@ -226,8 +226,8 @@ impl NodeMarketplaceService {
.base_price(hourly_price)
.base_currency("USD".to_string())
.category_id("hardware".to_string())
.provider_id(farmer_email.to_string())
.provider_name(farmer_display_name)
.provider_id(resource_provider_email.to_string())
.provider_name(resource_provider_display_name)
.metadata(metadata)
.availability(if self.is_node_online(node) {
crate::models::product::ProductAvailability::Available
@@ -276,17 +276,17 @@ impl NodeMarketplaceService {
format!("{}", node.status) == "Online"
}
/// Get farmer display name from email
fn get_farmer_display_name(&self, farmer_email: &str) -> String {
/// Get resource_provider display name from email
fn get_resource_provider_display_name(&self, resource_provider_email: &str) -> String {
// Try to get actual name from persistent data
if let Some(user_data) = UserPersistence::load_user_data(farmer_email) {
if let Some(user_data) = UserPersistence::load_user_data(resource_provider_email) {
if let Some(name) = user_data.name {
return name;
}
}
// Fallback to email username
farmer_email.split('@').next().unwrap_or("Farmer").to_string()
resource_provider_email.split('@').next().unwrap_or("ResourceProvider").to_string()
}
/// Apply marketplace filters to node products
@@ -438,7 +438,7 @@ impl NodeMarketplaceService {
let mut total_cpu_cores = 0u32;
let mut total_memory_gb = 0u32;
let mut total_storage_gb = 0u32;
let mut unique_farmers = std::collections::HashSet::new();
let mut unique_resource_providers = std::collections::HashSet::new();
let mut unique_locations = std::collections::HashSet::new();
for product in &all_slices {
@@ -456,8 +456,8 @@ impl NodeMarketplaceService {
total_storage_gb += (storage * quantity) as u32;
}
if let Some(farmer) = product.attributes.get("farmer_email").and_then(|f| f.value.as_str()) {
unique_farmers.insert(farmer.to_string());
if let Some(resource_provider) = product.attributes.get("resource_provider_email").and_then(|f| f.value.as_str()) {
unique_resource_providers.insert(resource_provider.to_string());
}
if let Some(location) = &product.metadata.location {
@@ -471,9 +471,9 @@ impl NodeMarketplaceService {
"total_cpu_cores": total_cpu_cores,
"total_memory_gb": total_memory_gb,
"total_storage_gb": total_storage_gb,
"unique_farmers": unique_farmers.len(),
"unique_resource_providers": unique_resource_providers.len(),
"unique_locations": unique_locations.len(),
"farmers": unique_farmers.into_iter().collect::<Vec<_>>(),
"resource_providers": unique_resource_providers.into_iter().collect::<Vec<_>>(),
"locations": unique_locations.into_iter().collect::<Vec<_>>()
})
}
@@ -531,7 +531,7 @@ impl NodeMarketplaceService {
pub fn get_all_slice_combinations(&self) -> Vec<Product> {
let mut all_slice_products = Vec::new();
// Read all user data files to find farmers with nodes
// Read all user data files to find resource_providers with nodes
if let Ok(entries) = std::fs::read_dir("./user_data") {
for entry in entries.flatten() {
if let Some(filename) = entry.file_name().to_str() {
@@ -585,14 +585,14 @@ impl NodeMarketplaceService {
&self,
combination: &SliceCombination,
_node: &FarmNode,
farmer_email: &str
resource_provider_email: &str
) -> Result<Product, String> {
let mut attributes = HashMap::new();
// Farmer information
attributes.insert("farmer_email".to_string(), crate::models::product::ProductAttribute {
key: "farmer_email".to_string(),
value: serde_json::Value::String(farmer_email.to_string()),
// ResourceProvider information
attributes.insert("resource_provider_email".to_string(), crate::models::product::ProductAttribute {
key: "resource_provider_email".to_string(),
value: serde_json::Value::String(resource_provider_email.to_string()),
attribute_type: crate::models::product::AttributeType::Text,
is_searchable: true,
is_filterable: true,
@@ -701,15 +701,15 @@ impl NodeMarketplaceService {
display_order: Some(10),
});
// Get farmer display name
let farmer_display_name = self.get_farmer_display_name(farmer_email);
// Get resource_provider display name
let resource_provider_display_name = self.get_resource_provider_display_name(resource_provider_email);
// Create metadata
let metadata = crate::models::product::ProductMetadata {
location: Some(combination.node_location.clone()),
custom_fields: {
let mut fields = std::collections::HashMap::new();
fields.insert("provider".to_string(), serde_json::Value::String(farmer_display_name.clone()));
fields.insert("provider".to_string(), serde_json::Value::String(resource_provider_display_name.clone()));
fields.insert("certification".to_string(), serde_json::Value::String(combination.node_certification_type.clone()));
fields.insert("created_at".to_string(), serde_json::Value::String(chrono::Utc::now().to_rfc3339()));
fields.insert("updated_at".to_string(), serde_json::Value::String(chrono::Utc::now().to_rfc3339()));
@@ -727,7 +727,7 @@ impl NodeMarketplaceService {
// Build product using the builder pattern
let mut product = crate::models::product::Product::builder()
.id(format!("slice_{}_{}", combination.node_id, combination.id))
.name(format!("{} Slice ({}x Base Unit)", farmer_display_name, combination.multiplier))
.name(format!("{} Slice ({}x Base Unit)", resource_provider_display_name, combination.multiplier))
.description(format!(
"Compute slice with {} vCPU, {}GB RAM, {}GB storage from {} ({}% uptime)",
combination.cpu_cores,
@@ -739,8 +739,8 @@ impl NodeMarketplaceService {
.category_id("compute_slices".to_string())
.base_price(combination.price_per_hour)
.base_currency("USD".to_string())
.provider_id(farmer_email.to_string())
.provider_name(farmer_display_name)
.provider_id(resource_provider_email.to_string())
.provider_name(resource_provider_display_name)
.metadata(metadata)
.build()
.map_err(|e| format!("Failed to build slice product: {}", e))?;

View File

@@ -1,7 +1,7 @@
//! Node rental service for managing node rentals and farmer earnings
//! Node rental service for managing node rentals and resource_provider earnings
//! Follows the established builder pattern for consistent API design
use crate::models::user::{NodeRental, NodeRentalType, NodeRentalStatus, FarmerRentalEarning, PaymentStatus, NodeAvailabilityStatus};
use crate::models::user::{NodeRental, NodeRentalType, NodeRentalStatus, ResourceProviderRentalEarning, PaymentStatus, NodeAvailabilityStatus};
use crate::services::user_persistence::{UserPersistence, ProductRental};
use rust_decimal::Decimal;
use chrono::{Utc, Duration};
@@ -65,7 +65,7 @@ impl NodeRentalService {
duration_months: u32,
rental_type: NodeRentalType,
monthly_cost: Decimal,
) -> Result<(NodeRental, FarmerRentalEarning), String> {
) -> Result<(NodeRental, ResourceProviderRentalEarning), String> {
// Extract node ID from product ID
let node_id = if product_id.starts_with("fullnode_") {
product_id.strip_prefix("fullnode_").unwrap_or(product_id)
@@ -99,8 +99,8 @@ impl NodeRentalService {
.payment_method("USD".to_string())
.build()?;
// Create farmer earning record
let farmer_earning = crate::models::builders::FarmerRentalEarningBuilder::new()
// Create resource_provider earning record
let resource_provider_earning = crate::models::builders::ResourceProviderRentalEarningBuilder::new()
.node_id(node_id.to_string())
.rental_id(rental.id.clone())
.renter_email(renter_email.to_string())
@@ -111,29 +111,29 @@ impl NodeRentalService {
.payment_status(PaymentStatus::Completed)
.build()?;
// Find the farmer who owns this node
let farmer_email = self.find_node_owner(node_id)?;
// Find the resource_provider who owns this node
let resource_provider_email = self.find_node_owner(node_id)?;
// Save rental to renter's data
self.save_rental_to_user(&rental, renter_email, product_id)?;
// Save earning to farmer's data
self.save_earning_to_farmer(&farmer_earning, &farmer_email)?;
// Save earning to resource_provider's data
self.save_earning_to_resource_provider(&resource_provider_earning, &resource_provider_email)?;
// Update node availability status
self.update_node_availability(node_id, &farmer_email)?;
self.update_node_availability(node_id, &resource_provider_email)?;
Ok((rental, farmer_earning))
Ok((rental, resource_provider_earning))
}
/// Check for rental conflicts
fn check_rental_conflicts(&self, node_id: &str, rental_type: &NodeRentalType) -> Result<(), String> {
// Find the farmer who owns this node
let farmer_email = self.find_node_owner(node_id)?;
// Find the resource_provider who owns this node
let resource_provider_email = self.find_node_owner(node_id)?;
if let Some(farmer_data) = UserPersistence::load_user_data(&farmer_email) {
if let Some(resource_provider_data) = UserPersistence::load_user_data(&resource_provider_email) {
// Check existing rentals for this node
let existing_rentals: Vec<_> = farmer_data.node_rentals.iter()
let existing_rentals: Vec<_> = resource_provider_data.node_rentals.iter()
.filter(|r| r.node_id == node_id && r.is_active())
.collect();
@@ -166,7 +166,7 @@ impl NodeRentalService {
Ok(())
}
/// Find the farmer who owns a specific node
/// Find the resource_provider who owns a specific node
fn find_node_owner(&self, node_id: &str) -> Result<String, String> {
// Scan all user files to find the node owner
if let Ok(entries) = std::fs::read_dir("./user_data/") {
@@ -226,31 +226,31 @@ impl NodeRentalService {
Ok(())
}
/// Save earning record to farmer's persistent data
fn save_earning_to_farmer(&self, earning: &FarmerRentalEarning, farmer_email: &str) -> Result<(), String> {
let mut farmer_data = UserPersistence::load_user_data(farmer_email)
.unwrap_or_else(|| self.create_default_user_data(farmer_email));
/// Save earning record to resource_provider's persistent data
fn save_earning_to_resource_provider(&self, earning: &ResourceProviderRentalEarning, resource_provider_email: &str) -> Result<(), String> {
let mut resource_provider_data = UserPersistence::load_user_data(resource_provider_email)
.unwrap_or_else(|| self.create_default_user_data(resource_provider_email));
// Add to farmer rental earnings
farmer_data.farmer_rental_earnings.push(earning.clone());
// Add to resource_provider rental earnings
resource_provider_data.resource_provider_rental_earnings.push(earning.clone());
// Update wallet balance
farmer_data.wallet_balance_usd += earning.amount;
resource_provider_data.wallet_balance_usd += earning.amount;
UserPersistence::save_user_data(&farmer_data)
.map_err(|e| format!("Failed to save farmer data: {}", e))?;
UserPersistence::save_user_data(&resource_provider_data)
.map_err(|e| format!("Failed to save resource_provider data: {}", e))?;
Ok(())
}
/// Update node availability status based on current rentals
fn update_node_availability(&self, node_id: &str, farmer_email: &str) -> Result<(), String> {
let mut farmer_data = UserPersistence::load_user_data(farmer_email)
.ok_or("Farmer data not found")?;
fn update_node_availability(&self, node_id: &str, resource_provider_email: &str) -> Result<(), String> {
let mut resource_provider_data = UserPersistence::load_user_data(resource_provider_email)
.ok_or("ResourceProvider data not found")?;
if let Some(node) = farmer_data.nodes.iter_mut().find(|n| n.id == node_id) {
if let Some(node) = resource_provider_data.nodes.iter_mut().find(|n| n.id == node_id) {
// Count active rentals for this node
let active_rentals: Vec<_> = farmer_data.node_rentals.iter()
let active_rentals: Vec<_> = resource_provider_data.node_rentals.iter()
.filter(|r| r.node_id == node_id && r.is_active())
.collect();
@@ -262,7 +262,7 @@ impl NodeRentalService {
NodeAvailabilityStatus::PartiallyRented
};
UserPersistence::save_user_data(&farmer_data)
UserPersistence::save_user_data(&resource_provider_data)
.map_err(|e| format!("Failed to update node availability: {}", e))?;
}
@@ -285,10 +285,10 @@ impl NodeRentalService {
}
}
/// Get farmer earnings from rentals
pub fn get_farmer_rental_earnings(&self, farmer_email: &str) -> Vec<FarmerRentalEarning> {
if let Some(farmer_data) = UserPersistence::load_user_data(farmer_email) {
farmer_data.farmer_rental_earnings
/// Get resource_provider earnings from rentals
pub fn get_resource_provider_rental_earnings(&self, resource_provider_email: &str) -> Vec<ResourceProviderRentalEarning> {
if let Some(resource_provider_data) = UserPersistence::load_user_data(resource_provider_email) {
resource_provider_data.resource_provider_rental_earnings
} else {
Vec::new()
}
@@ -309,8 +309,8 @@ impl NodeRentalService {
}
// Update node availability
let farmer_email = self.find_node_owner(&rental.node_id)?;
self.update_node_availability(&rental.node_id, &farmer_email)?;
let resource_provider_email = self.find_node_owner(&rental.node_id)?;
self.update_node_availability(&rental.node_id, &resource_provider_email)?;
UserPersistence::save_user_data(&user_data)
.map_err(|e| format!("Failed to save user data: {}", e))?;

View File

@@ -626,8 +626,8 @@ impl OrderService {
order.set_payment_details(payment_details.clone());
}
// PHASE 1: Create app deployments for successful app orders
if let Err(e) = self.create_app_deployments_from_order(&order) {
// PHASE 1: Create application deployments for successful app orders
if let Err(e) = self.create_application_deployments_from_order(&order) {
}
// PHASE 2: Create service bookings for successful service orders
@@ -780,8 +780,8 @@ impl OrderService {
stats
}
/// PHASE 1: Create app deployments when apps are successfully ordered
fn create_app_deployments_from_order(&self, order: &Order) -> Result<(), String> {
/// PHASE 1: Create application deployments when apps are successfully ordered
fn create_application_deployments_from_order(&self, order: &Order) -> Result<(), String> {
use crate::services::user_persistence::{UserPersistence, AppDeployment};
use crate::models::user::ResourceUtilization;
use chrono::Utc;
@@ -804,8 +804,8 @@ impl OrderService {
// Only create deployments for application products
if item.product_category == "application" {
// Find the app provider by looking up who published this app
if let Some(app_provider_email) = self.find_app_provider(&item.product_id) {
// Find the application provider by looking up who published this app
if let Some(application_provider_email) = self.find_application_provider(&item.product_id) {
// Create deployment for each quantity ordered
for _i in 0..item.quantity {
@@ -837,14 +837,14 @@ impl OrderService {
.build()
.unwrap();
// Add deployment to app provider's data
if let Err(e) = UserPersistence::add_user_app_deployment(&app_provider_email, deployment.clone()) {
// Add deployment to application provider's data
if let Err(e) = UserPersistence::add_user_application_deployment(&application_provider_email, deployment.clone()) {
} else {
}
// Also add deployment to customer's data (for future user dashboard)
if customer_email != "guest" {
if let Err(e) = UserPersistence::add_user_app_deployment(&customer_email, deployment) {
if let Err(e) = UserPersistence::add_user_application_deployment(&customer_email, deployment) {
} else {
}
}
@@ -857,8 +857,8 @@ impl OrderService {
Ok(())
}
/// Find the app provider (user who published the app) by product ID
fn find_app_provider(&self, product_id: &str) -> Option<String> {
/// Find the application provider (user who published the app) by product ID
fn find_application_provider(&self, product_id: &str) -> Option<String> {
// Get all user data files and search for the app
let user_data_dir = std::path::Path::new("user_data");
if !user_data_dir.exists() {

View File

@@ -241,8 +241,8 @@ impl ProductService {
if let Some(combination_id) = product.attributes.get("combination_id") {
details["combination_id"] = combination_id.value.clone();
}
if let Some(farmer_email) = product.attributes.get("farmer_email") {
details["farmer_email"] = farmer_email.value.clone();
if let Some(resource_provider_email) = product.attributes.get("resource_provider_email") {
details["resource_provider_email"] = resource_provider_email.value.clone();
}
if let Some(cpu_cores) = product.attributes.get("cpu_cores") {
details["cpu_cores"] = cpu_cores.value.clone();

View File

@@ -1,4 +1,4 @@
//! Farmer service for managing nodes, slice allocation, and earnings
//! ResourceProvider service for managing nodes, slice allocation, and earnings
//! Follows the established builder pattern for consistent API design
use crate::models::user::{FarmNode, NodeCapacity, NodeStatus, EarningsRecord, NodeGroup, GroupStatistics, MarketplaceSLA};
@@ -12,7 +12,7 @@ use std::str::FromStr;
use chrono::{Utc, DateTime};
use serde::{Serialize, Deserialize};
/// Staking statistics for a farmer
/// Staking statistics for a resource_provider
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StakingStatistics {
pub total_staked_amount: Decimal,
@@ -30,25 +30,25 @@ impl Default for StakingStatistics {
}
}
/// Service for farmer-specific operations
/// Service for resource_provider-specific operations
#[derive(Clone)]
pub struct FarmerService {
pub struct ResourceProviderService {
auto_sync_enabled: bool,
metrics_collection: bool,
grid_service: GridService,
slice_calculator: SliceCalculatorService,
}
/// Builder for FarmerService
/// Builder for ResourceProviderService
#[derive(Default)]
pub struct FarmerServiceBuilder {
pub struct ResourceProviderServiceBuilder {
auto_sync_enabled: Option<bool>,
metrics_collection: Option<bool>,
grid_service: Option<GridService>,
slice_calculator: Option<SliceCalculatorService>,
}
impl FarmerServiceBuilder {
impl ResourceProviderServiceBuilder {
pub fn new() -> Self {
Self::default()
}
@@ -73,7 +73,7 @@ impl FarmerServiceBuilder {
self
}
pub fn build(self) -> Result<FarmerService, String> {
pub fn build(self) -> Result<ResourceProviderService, String> {
let grid_service = self.grid_service.unwrap_or_else(|| {
GridService::builder().build().expect("Failed to create default GridService")
});
@@ -82,7 +82,7 @@ impl FarmerServiceBuilder {
SliceCalculatorService::builder().build().expect("Failed to create default SliceCalculatorService")
});
Ok(FarmerService {
Ok(ResourceProviderService {
auto_sync_enabled: self.auto_sync_enabled.unwrap_or(true),
metrics_collection: self.metrics_collection.unwrap_or(true),
grid_service,
@@ -91,13 +91,13 @@ impl FarmerServiceBuilder {
}
}
impl FarmerService {
pub fn builder() -> FarmerServiceBuilder {
FarmerServiceBuilder::new()
impl ResourceProviderService {
pub fn builder() -> ResourceProviderServiceBuilder {
ResourceProviderServiceBuilder::new()
}
/// Get all nodes for a farmer
pub fn get_farmer_nodes(&self, user_email: &str) -> Vec<FarmNode> {
/// Get all nodes for a resource_provider
pub fn get_resource_provider_nodes(&self, user_email: &str) -> Vec<FarmNode> {
if let Some(data) = UserPersistence::load_user_data(user_email) {
// Debug: Log marketplace SLA data for all nodes
for node in &data.nodes {
@@ -111,10 +111,10 @@ impl FarmerService {
}
}
/// Add a new node for a farmer (manual creation)
/// Add a new node for a resource_provider (manual creation)
pub fn add_node(&self, user_email: &str, node_data: NodeCreationData) -> Result<FarmNode, String> {
// Check for duplicate node names first
let existing_nodes = self.get_farmer_nodes(user_email);
let existing_nodes = self.get_resource_provider_nodes(user_email);
if existing_nodes.iter().any(|n| n.name == node_data.name) {
return Err(format!("Node '{}' is already registered", node_data.name));
}
@@ -169,7 +169,7 @@ impl FarmerService {
last_seen: Some(Utc::now()),
health_score: 100.0,
region: node_data.region.unwrap_or_else(|| "Unknown".to_string()),
node_type: node_data.node_type.unwrap_or_else(|| "3Node".to_string()),
node_type: node_data.node_type.unwrap_or_else(|| "MyceliumNode".to_string()),
slice_formats: node_data.slice_formats.clone(),
rental_options: node_data.rental_options.as_ref().map(|opts| serde_json::to_value(opts).unwrap_or_default()),
staking_options: None,
@@ -234,7 +234,7 @@ impl FarmerService {
.map_err(|e| e.to_string())?;
// Auto-generate marketplace products if rental options are configured
self.auto_generate_marketplace_products(&node, user_email, &persistent_data.name.unwrap_or_else(|| "Unknown Farmer".to_string()), node_data.slice_prices.as_ref())?;
self.auto_generate_marketplace_products(&node, user_email, &persistent_data.name.unwrap_or_else(|| "Unknown ResourceProvider".to_string()), node_data.slice_prices.as_ref())?;
Ok(node)
}
@@ -375,7 +375,7 @@ impl FarmerService {
earnings_today_usd: Decimal::ZERO,
name: if grid_data.farm_name.is_empty() { format!("Grid Node {}", grid_node_id) } else { grid_data.farm_name.clone() },
region: if grid_data.country.is_empty() { "Unknown".to_string() } else { grid_data.country.clone() },
node_type: "3Node".to_string(),
node_type: "MyceliumNode".to_string(),
slice_formats: None, // Not used in new slice system
staking_options: None,
availability_status: crate::models::user::NodeAvailabilityStatus::Available,
@@ -434,10 +434,10 @@ impl FarmerService {
/// Sync node capacity and slices with grid data
pub async fn sync_node_with_grid(&self, user_email: &str, node_id: &str) -> Result<(), String> {
let mut farmer_data = UserPersistence::load_user_data(user_email)
.ok_or("Farmer data not found")?;
let mut resource_provider_data = UserPersistence::load_user_data(user_email)
.ok_or("ResourceProvider data not found")?;
if let Some(node) = farmer_data.nodes.iter_mut().find(|n| n.id == node_id) {
if let Some(node) = resource_provider_data.nodes.iter_mut().find(|n| n.id == node_id) {
if let Some(grid_node_id) = &node.grid_node_id {
// Fetch latest capacity from grid
let grid_id: u32 = grid_node_id.parse().unwrap_or(0);
@@ -472,7 +472,7 @@ impl FarmerService {
}
// Save updated data
UserPersistence::save_user_data(&farmer_data)
UserPersistence::save_user_data(&resource_provider_data)
.map_err(|e| format!("Failed to save user data: {}", e))?;
} else {
return Err("Node is not linked to grid".to_string());
@@ -486,7 +486,7 @@ impl FarmerService {
/// Auto-generate marketplace products for a node based on rental options
/// Products are now managed through persistent user data and aggregated by ProductService
fn auto_generate_marketplace_products(&self, _node: &FarmNode, _farmer_email: &str, _farmer_name: &str, _slice_prices: Option<&std::collections::HashMap<String, rust_decimal::Decimal>>) -> Result<(), String> {
fn auto_generate_marketplace_products(&self, _node: &FarmNode, _resource_provider_email: &str, _resource_provider_name: &str, _slice_prices: Option<&std::collections::HashMap<String, rust_decimal::Decimal>>) -> Result<(), String> {
// Product generation is now handled through persistent user data
// ProductService automatically aggregates products from user-owned data
Ok(())
@@ -754,7 +754,7 @@ impl FarmerService {
}
/// Create a slice product from a node and slice format
fn create_slice_product_from_node(&self, node: &FarmNode, slice_format_id: &str, farmer_email: &str, farmer_name: &str, custom_prices: Option<&std::collections::HashMap<String, rust_decimal::Decimal>>) -> Result<crate::models::product::Product, String> {
fn create_slice_product_from_node(&self, node: &FarmNode, slice_format_id: &str, resource_provider_email: &str, resource_provider_name: &str, custom_prices: Option<&std::collections::HashMap<String, rust_decimal::Decimal>>) -> Result<crate::models::product::Product, String> {
// Get slice format details and default pricing
let (cpu_cores, memory_gb, storage_gb, bandwidth_mbps, default_price) = match slice_format_id {
"basic" => (2, 4, 100, 100, 25),
@@ -789,8 +789,8 @@ impl FarmerService {
base_price: price,
base_currency: "USD".to_string(),
attributes: std::collections::HashMap::new(),
provider_id: farmer_email.to_string(),
provider_name: farmer_name.to_string(),
provider_id: resource_provider_email.to_string(),
provider_name: resource_provider_name.to_string(),
availability: match node.availability_status {
crate::models::user::NodeAvailabilityStatus::Available => crate::models::product::ProductAvailability::Available,
crate::models::user::NodeAvailabilityStatus::PartiallyRented => crate::models::product::ProductAvailability::Limited,
@@ -880,19 +880,19 @@ impl FarmerService {
Ok(())
}
/// Get farmer earnings
pub fn get_farmer_earnings(&self, user_email: &str) -> Vec<EarningsRecord> {
/// Get resource_provider earnings
pub fn get_resource_provider_earnings(&self, user_email: &str) -> Vec<EarningsRecord> {
if let Some(data) = UserPersistence::load_user_data(user_email) {
data.farmer_earnings
data.resource_provider_earnings
} else {
Vec::new()
}
}
/// Get farmer statistics
pub fn get_farmer_statistics(&self, user_email: &str) -> FarmerStatistics {
let nodes = self.get_farmer_nodes(user_email);
let earnings = self.get_farmer_earnings(user_email);
/// Get resource_provider statistics
pub fn get_resource_provider_statistics(&self, user_email: &str) -> ResourceProviderStatistics {
let nodes = self.get_resource_provider_nodes(user_email);
let earnings = self.get_resource_provider_earnings(user_email);
let total_nodes = nodes.len() as i32;
let online_nodes = nodes.iter()
@@ -962,7 +962,7 @@ impl FarmerService {
allocated_base_slices += node_allocated_slices;
}
FarmerStatistics {
ResourceProviderStatistics {
total_nodes,
online_nodes,
total_capacity,
@@ -990,7 +990,7 @@ impl FarmerService {
slice_config.bandwidth_mbps <= available_bandwidth)
}
/// Get default slice formats available to all farmers
/// Get default slice formats available to all resource providers
pub fn get_default_slice_formats(&self) -> Vec<DefaultSliceFormat> {
vec![
DefaultSliceFormat {
@@ -1039,8 +1039,8 @@ impl FarmerService {
// Load user customizations from persistent data
if let Some(persistent_data) = UserPersistence::load_user_data(user_email) {
if let Some(farmer_settings) = persistent_data.farmer_settings {
if let Some(customizations) = farmer_settings.default_slice_customizations {
if let Some(resource_provider_settings) = persistent_data.resource_provider_settings {
if let Some(customizations) = resource_provider_settings.default_slice_customizations {
if let Some(custom_format) = customizations.get(format_id) {
// Apply user customizations
if let Some(cpu_cores) = custom_format.get("cpu_cores").and_then(|v| v.as_u64()).map(|v| v as u32) {
@@ -1075,9 +1075,9 @@ impl FarmerService {
pub fn save_default_slice_customization(&self, user_email: &str, format_id: &str, customization: DefaultSliceFormat) -> Result<(), String> {
let mut persistent_data = crate::models::builders::SessionDataBuilder::load_or_create(user_email);
// Initialize farmer settings if needed
if persistent_data.farmer_settings.is_none() {
persistent_data.farmer_settings = Some(crate::models::user::FarmerSettings {
// Initialize resource_provider settings if needed
if persistent_data.resource_provider_settings.is_none() {
persistent_data.resource_provider_settings = Some(crate::models::user::ResourceProviderSettings {
auto_accept_reserved_slices: true,
maintenance_window: "02:00-04:00 UTC".to_string(),
notification_email: None,
@@ -1096,12 +1096,12 @@ impl FarmerService {
}
// Initialize default slice customizations if needed
if let Some(ref mut farmer_settings) = persistent_data.farmer_settings {
if farmer_settings.default_slice_customizations.is_none() {
farmer_settings.default_slice_customizations = Some(serde_json::to_value(std::collections::HashMap::<String, serde_json::Value>::new()).unwrap_or_default());
if let Some(ref mut resource_provider_settings) = persistent_data.resource_provider_settings {
if resource_provider_settings.default_slice_customizations.is_none() {
resource_provider_settings.default_slice_customizations = Some(serde_json::to_value(std::collections::HashMap::<String, serde_json::Value>::new()).unwrap_or_default());
}
if let Some(ref mut customizations) = farmer_settings.default_slice_customizations {
if let Some(ref mut customizations) = resource_provider_settings.default_slice_customizations {
if let Some(obj) = customizations.as_object_mut() {
obj.insert(format_id.to_string(), serde_json::to_value(customization).unwrap_or_default());
}
@@ -1288,7 +1288,7 @@ impl FarmerService {
/// Add a node from ThreeFold Grid by node ID
pub async fn add_grid_node(&self, user_email: &str, grid_node_id: u32, _slice_format: Option<String>, _slice_price: Option<Decimal>) -> Result<FarmNode, String> {
// Check for duplicate grid node IDs first
let existing_nodes = self.get_farmer_nodes(user_email);
let existing_nodes = self.get_resource_provider_nodes(user_email);
if existing_nodes.iter().any(|n| n.grid_node_id == Some(grid_node_id.to_string())) {
return Err(format!("Node {} is already registered", grid_node_id));
}
@@ -1316,7 +1316,7 @@ impl FarmerService {
.earnings_today_usd(Decimal::ZERO)
.health_score(100.0)
.region(if grid_data.country.is_empty() { "Unknown".to_string() } else { grid_data.country.clone() })
.node_type("3Node".to_string())
.node_type("MyceliumNode".to_string())
.grid_node_id(grid_node_id)
.grid_data(grid_data)
.build()?;
@@ -1371,7 +1371,7 @@ impl FarmerService {
.earnings_today_usd(Decimal::ZERO)
.health_score(100.0)
.region(if grid_data.country.is_empty() { "Unknown".to_string() } else { grid_data.country.clone() })
.node_type("3Node".to_string())
.node_type("MyceliumNode".to_string())
.grid_node_id(grid_node_id)
.grid_data(grid_data)
.build()
@@ -1460,7 +1460,7 @@ impl FarmerService {
.last_seen(Utc::now())
.health_score(95.0)
.region(if grid_data.country.is_empty() { "Unknown" } else { &grid_data.country }.to_string())
.node_type("3Node".to_string())
.node_type("MyceliumNode".to_string())
.grid_node_id(grid_node_id)
.grid_data(grid_data)
.availability_status(crate::models::user::NodeAvailabilityStatus::Available);
@@ -1536,7 +1536,7 @@ impl FarmerService {
.last_seen(Utc::now())
.health_score(95.0)
.region(if grid_data.country.is_empty() { "Unknown" } else { &grid_data.country }.to_string())
.node_type("3Node".to_string())
.node_type("MyceliumNode".to_string())
.grid_node_id(grid_node_id)
.grid_data(grid_data)
.availability_status(crate::models::user::NodeAvailabilityStatus::Available);
@@ -1638,7 +1638,7 @@ impl FarmerService {
.last_seen(Utc::now())
.health_score(95.0)
.region(if grid_data.country.is_empty() { "Unknown" } else { &grid_data.country }.to_string())
.node_type("3Node".to_string())
.node_type("MyceliumNode".to_string())
.grid_node_id(grid_node_id)
.grid_data(grid_data)
.availability_status(crate::models::user::NodeAvailabilityStatus::Available);
@@ -1703,7 +1703,7 @@ impl FarmerService {
.last_seen(Utc::now())
.health_score(95.0)
.region(if grid_data.country.is_empty() { "Unknown" } else { &grid_data.country }.to_string())
.node_type("3Node".to_string())
.node_type("MyceliumNode".to_string())
.grid_node_id(grid_node_id)
.grid_data(grid_data)
.availability_status(crate::models::user::NodeAvailabilityStatus::Available);
@@ -1803,7 +1803,7 @@ impl FarmerService {
last_seen: Some(Utc::now()),
health_score: 100.0,
region: if grid_data.country.is_empty() { "Unknown" } else { &grid_data.country }.to_string(),
node_type: "3Node".to_string(),
node_type: "MyceliumNode".to_string(),
slice_formats: if slice_formats.is_empty() { None } else { Some(slice_formats.clone()) },
rental_options: rental_options.map(|ro| serde_json::to_value(&ro).unwrap_or_default()),
staking_options: None,
@@ -1845,8 +1845,8 @@ impl FarmerService {
.unwrap_or(false);
if slice_rental_enabled || full_node_rental_enabled {
let farmer_name = persistent_data.name.unwrap_or_else(|| "Unknown Farmer".to_string());
self.auto_generate_marketplace_products(&node, user_email, &farmer_name, None)?;
let resource_provider_name = persistent_data.name.unwrap_or_else(|| "Unknown ResourceProvider".to_string());
self.auto_generate_marketplace_products(&node, user_email, &resource_provider_name, None)?;
}
}
Ok(node)
@@ -1856,7 +1856,7 @@ impl FarmerService {
// NODE GROUP MANAGEMENT
// =============================================================================
/// Ensure default node groups exist for a farmer
/// Ensure default node groups exist for a resource_provider
pub fn ensure_default_node_groups(&self, user_email: &str) -> Result<(), String> {
let mut persistent_data = crate::models::builders::SessionDataBuilder::load_or_create(user_email);
@@ -1898,7 +1898,7 @@ impl FarmerService {
Ok(group)
}
/// Get all node groups for a farmer (ensures defaults exist)
/// Get all node groups for a resource_provider (ensures defaults exist)
pub fn get_node_groups(&self, user_email: &str) -> Vec<NodeGroup> {
// Ensure default groups exist first
if let Err(e) = self.ensure_default_node_groups(user_email) {
@@ -2165,7 +2165,7 @@ impl FarmerService {
/// Get node by name (for duplicate checking)
pub fn get_node_by_name(&self, user_email: &str, node_name: &str) -> Option<FarmNode> {
let nodes = self.get_farmer_nodes(user_email);
let nodes = self.get_resource_provider_nodes(user_email);
nodes.into_iter().find(|n| n.name == node_name)
}
@@ -2278,12 +2278,12 @@ impl FarmerService {
Ok(())
}
/// Create default user data for new farmers
/// Create default user data for new resource providers
fn create_default_user_data(user_email: &str) -> UserPersistentData {
crate::models::builders::SessionDataBuilder::new_user(user_email)
}
/// Refresh all slice calculations for a farmer
/// Refresh all slice calculations for a resource_provider
pub fn refresh_all_slice_calculations(&self, user_email: &str) -> Result<(), String> {
let mut persistent_data = UserPersistence::load_user_data(user_email)
.ok_or("User data not found")?;
@@ -2395,7 +2395,7 @@ impl FarmerService {
Ok(slices)
}
/// Get comprehensive slice statistics for a farmer
/// Get comprehensive slice statistics for a resource_provider
pub fn get_slice_statistics(&self, user_email: &str) -> Result<serde_json::Value, String> {
let persistent_data = UserPersistence::load_user_data(user_email)
.ok_or("User data not found")?;
@@ -2560,7 +2560,7 @@ impl FarmerService {
last_seen: Some(Utc::now()),
health_score: 100.0,
region: if grid_data.country.is_empty() { "Unknown".to_string() } else { grid_data.country.clone() },
node_type: "3Node".to_string(),
node_type: "MyceliumNode".to_string(),
slice_formats: None,
rental_options: {
let rental_opts = crate::models::user::NodeRentalOptions {
@@ -2660,7 +2660,7 @@ impl FarmerService {
last_seen: Some(Utc::now()),
health_score: 100.0,
region: if grid_data.country.is_empty() { "Unknown".to_string() } else { grid_data.country.clone() },
node_type: "3Node".to_string(),
node_type: "MyceliumNode".to_string(),
slice_formats: None,
rental_options: Some(serde_json::to_value(&crate::models::user::NodeRentalOptions {
full_node_available: enable_full_node_rental,
@@ -2821,7 +2821,7 @@ impl FarmerService {
Ok(repaired_count)
}
/// Refresh all slice calculations for a farmer (async version)
/// Refresh all slice calculations for a resource_provider (async version)
pub async fn refresh_all_slice_calculations_async(&self, user_email: &str) -> Result<u32, String> {
let mut persistent_data = UserPersistence::load_user_data(user_email)
.ok_or("User data not found")?;
@@ -2971,7 +2971,7 @@ pub struct NodeUpdateData {
pub marketplace_sla: Option<MarketplaceSLA>,
}
/// Default slice formats available to all farmers
/// Default slice formats available to all resource providers
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DefaultSliceFormat {
pub id: String,
@@ -2984,9 +2984,9 @@ pub struct DefaultSliceFormat {
pub price_per_hour: rust_decimal::Decimal,
}
/// Farmer statistics summary
/// ResourceProvider statistics summary
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FarmerStatistics {
pub struct ResourceProviderStatistics {
pub total_nodes: i32,
pub online_nodes: i32,
pub total_capacity: NodeCapacity,

View File

@@ -18,7 +18,7 @@ pub struct SliceAssignmentService {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SliceAssignmentRequest {
pub user_email: String,
pub farmer_email: String,
pub resource_provider_email: String,
pub node_id: String,
pub combination_id: String,
pub quantity: u32,
@@ -127,7 +127,7 @@ pub struct SecurityConfiguration {
pub struct SliceAssignment {
pub assignment_id: String,
pub user_email: String,
pub farmer_email: String,
pub resource_provider_email: String,
pub node_id: String,
pub combination_id: String,
pub slice_allocations: Vec<SliceAllocation>,
@@ -241,7 +241,7 @@ impl SliceAssignmentService {
let assignment = SliceAssignment {
assignment_id,
user_email: request.user_email,
farmer_email: request.farmer_email,
resource_provider_email: request.resource_provider_email,
node_id: request.node_id,
combination_id: request.combination_id,
slice_allocations,
@@ -377,7 +377,7 @@ impl SliceAssignmentService {
"status": "deploying",
"deployment_type": "vm",
"node_id": assignment.node_id,
"farmer_email": assignment.farmer_email,
"resource_provider_email": assignment.resource_provider_email,
"started_at": Utc::now(),
"estimated_completion": Utc::now() + chrono::Duration::minutes(5)
});

View File

@@ -42,7 +42,7 @@ pub struct SliceCombination {
pub node_location: String,
pub node_certification_type: String,
pub node_id: String,
pub farmer_email: String,
pub resource_provider_email: String,
}
/// Track individual slice rentals
@@ -70,7 +70,7 @@ pub enum AllocationStatus {
pub struct SlicePricing {
pub base_price_per_hour: Decimal, // Price for 1 base slice per hour
pub currency: String,
pub pricing_multiplier: Decimal, // Farmer can adjust pricing (0.5x - 2.0x)
pub pricing_multiplier: Decimal, // ResourceProvider can adjust pricing (0.5x - 2.0x)
}
impl Default for SlicePricing {
@@ -157,7 +157,7 @@ impl SliceCalculatorService {
max_base_slices: u32,
allocated_slices: u32,
node: &FarmNode,
farmer_email: &str
resource_provider_email: &str
) -> Vec<SliceCombination> {
let available_base_slices = max_base_slices.saturating_sub(allocated_slices);
let mut combinations = Vec::new();
@@ -208,7 +208,7 @@ impl SliceCalculatorService {
.to_string())
.unwrap_or_else(|| "DIY".to_string()),
node_id: node.id.clone(),
farmer_email: farmer_email.to_string(),
resource_provider_email: resource_provider_email.to_string(),
};
combinations.push(combination);
@@ -225,7 +225,7 @@ impl SliceCalculatorService {
max_base_slices: u32,
allocated_slices: u32,
node: &FarmNode,
farmer_email: &str,
resource_provider_email: &str,
uptime_percentage: f64,
bandwidth_mbps: u32,
base_price_per_hour: Decimal
@@ -283,7 +283,7 @@ impl SliceCalculatorService {
.to_string())
.unwrap_or_else(|| "DIY".to_string()),
node_id: node.id.clone(),
farmer_email: farmer_email.to_string(),
resource_provider_email: resource_provider_email.to_string(),
};
combinations.push(combination);
@@ -304,7 +304,7 @@ impl SliceCalculatorService {
&self,
node: &mut FarmNode,
rented_base_slices: u32,
farmer_email: &str
resource_provider_email: &str
) -> Result<(), String> {
// Update allocated count
node.allocated_base_slices += rented_base_slices as i32;
@@ -314,7 +314,7 @@ impl SliceCalculatorService {
node.total_base_slices as u32,
node.allocated_base_slices as u32,
node,
farmer_email
resource_provider_email
);
node.available_combinations = combinations.iter()
.map(|c| serde_json::to_value(c).unwrap_or_default())
@@ -328,7 +328,7 @@ impl SliceCalculatorService {
&self,
node: &mut FarmNode,
released_base_slices: u32,
farmer_email: &str
resource_provider_email: &str
) -> Result<(), String> {
// Update allocated count
node.allocated_base_slices = node.allocated_base_slices.saturating_sub(released_base_slices as i32);
@@ -338,7 +338,7 @@ impl SliceCalculatorService {
node.total_base_slices as u32,
node.allocated_base_slices as u32,
node,
farmer_email
resource_provider_email
).iter()
.map(|c| serde_json::to_value(c).unwrap_or_default())
.collect();
@@ -364,7 +364,7 @@ pub struct SliceRental {
pub rental_id: String,
pub slice_combination_id: String,
pub node_id: String,
pub farmer_email: String,
pub resource_provider_email: String,
pub slice_allocation: SliceAllocation,
pub total_cost: Decimal,
pub payment_status: PaymentStatus,

View File

@@ -56,11 +56,11 @@ impl SliceRentalService {
SliceRentalServiceBuilder::new()
}
/// Rent a slice combination from a farmer's node
/// Rent a slice combination from a resource_provider's node
pub fn rent_slice_combination(
&self,
renter_email: &str,
farmer_email: &str,
resource_provider_email: &str,
node_id: &str,
combination_id: &str,
quantity: u32,
@@ -68,9 +68,9 @@ impl SliceRentalService {
) -> Result<SliceRental, String> {
// Atomic operation with file locking to prevent conflicts
if self.enable_file_locking {
self.rent_with_file_lock(renter_email, farmer_email, node_id, combination_id, quantity, rental_duration_hours)
self.rent_with_file_lock(renter_email, resource_provider_email, node_id, combination_id, quantity, rental_duration_hours)
} else {
self.rent_without_lock(renter_email, farmer_email, node_id, combination_id, quantity, rental_duration_hours)
self.rent_without_lock(renter_email, resource_provider_email, node_id, combination_id, quantity, rental_duration_hours)
}
}
@@ -78,7 +78,7 @@ impl SliceRentalService {
pub fn rent_slice_combination_with_deployment(
&self,
renter_email: &str,
farmer_email: &str,
resource_provider_email: &str,
node_id: &str,
combination_id: &str,
quantity: u32,
@@ -89,7 +89,7 @@ impl SliceRentalService {
) -> Result<SliceRental, String> {
// First rent the slice combination
let mut rental = self.rent_slice_combination(
renter_email, farmer_email, node_id, combination_id, quantity, rental_duration_hours
renter_email, resource_provider_email, node_id, combination_id, quantity, rental_duration_hours
)?;
// Add deployment metadata to the rental
@@ -133,21 +133,21 @@ impl SliceRentalService {
fn rent_with_file_lock(
&self,
renter_email: &str,
farmer_email: &str,
resource_provider_email: &str,
node_id: &str,
combination_id: &str,
quantity: u32,
rental_duration_hours: u32,
) -> Result<SliceRental, String> {
// Create lock file
let lock_file_path = format!("./user_data/.lock_{}_{}", farmer_email.replace("@", "_"), node_id);
let lock_file_path = format!("./user_data/.lock_{}_{}", resource_provider_email.replace("@", "_"), node_id);
let _lock_file = OpenOptions::new()
.create(true)
.write(true)
.open(&lock_file_path)
.map_err(|e| format!("Failed to create lock file: {}", e))?;
let result = self.rent_without_lock(renter_email, farmer_email, node_id, combination_id, quantity, rental_duration_hours);
let result = self.rent_without_lock(renter_email, resource_provider_email, node_id, combination_id, quantity, rental_duration_hours);
// Clean up lock file
let _ = std::fs::remove_file(&lock_file_path);
@@ -159,21 +159,21 @@ impl SliceRentalService {
fn rent_without_lock(
&self,
renter_email: &str,
farmer_email: &str,
resource_provider_email: &str,
node_id: &str,
combination_id: &str,
quantity: u32,
rental_duration_hours: u32,
) -> Result<SliceRental, String> {
// Load farmer data
let mut farmer_data = UserPersistence::load_user_data(farmer_email)
.ok_or_else(|| "Farmer not found".to_string())?;
// Load resource_provider data
let mut resource_provider_data = UserPersistence::load_user_data(resource_provider_email)
.ok_or_else(|| "ResourceProvider not found".to_string())?;
// Find the node
let node_index = farmer_data.nodes.iter().position(|n| n.id == node_id)
let node_index = resource_provider_data.nodes.iter().position(|n| n.id == node_id)
.ok_or_else(|| "Node not found".to_string())?;
let node = &mut farmer_data.nodes[node_index];
let node = &mut resource_provider_data.nodes[node_index];
// Find the slice combination
let combination = node.available_combinations.iter()
@@ -226,7 +226,7 @@ impl SliceRentalService {
self.slice_calculator.update_availability_after_rental(
node,
total_base_slices_needed,
farmer_email
resource_provider_email
)?;
// Add allocation to node
@@ -237,7 +237,7 @@ impl SliceRentalService {
rental_id: rental_id.clone(),
slice_combination_id: combination_id.to_string(),
node_id: node_id.to_string(),
farmer_email: farmer_email.to_string(),
resource_provider_email: resource_provider_email.to_string(),
slice_allocation: allocation,
total_cost,
payment_status: PaymentStatus::Paid,
@@ -260,12 +260,12 @@ impl SliceRentalService {
renter_data.wallet_balance_usd -= total_cost;
renter_data.slice_rentals.push(slice_rental.clone());
// Add earnings to farmer
farmer_data.wallet_balance_usd += total_cost;
// Add earnings to resource_provider
resource_provider_data.wallet_balance_usd += total_cost;
// Save both user data
UserPersistence::save_user_data(&farmer_data)
.map_err(|e| format!("Failed to save farmer data: {}", e))?;
UserPersistence::save_user_data(&resource_provider_data)
.map_err(|e| format!("Failed to save resource_provider data: {}", e))?;
UserPersistence::save_user_data(&renter_data)
.map_err(|e| format!("Failed to save renter data: {}", e))?;
@@ -273,14 +273,14 @@ impl SliceRentalService {
}
/// Release expired slice rentals
pub fn release_expired_rentals(&self, farmer_email: &str) -> Result<u32, String> {
let mut farmer_data = UserPersistence::load_user_data(farmer_email)
.ok_or_else(|| "Farmer not found".to_string())?;
pub fn release_expired_rentals(&self, resource_provider_email: &str) -> Result<u32, String> {
let mut resource_provider_data = UserPersistence::load_user_data(resource_provider_email)
.ok_or_else(|| "ResourceProvider not found".to_string())?;
let mut released_count = 0;
let now = Utc::now();
for node in &mut farmer_data.nodes {
for node in &mut resource_provider_data.nodes {
let mut expired_allocations = Vec::new();
// Find expired allocations
@@ -314,7 +314,7 @@ impl SliceRentalService {
self.slice_calculator.update_availability_after_release(
node,
base_slices_used,
farmer_email
resource_provider_email
)?;
released_count += 1;
@@ -322,20 +322,20 @@ impl SliceRentalService {
}
if released_count > 0 {
UserPersistence::save_user_data(&farmer_data)
.map_err(|e| format!("Failed to save farmer data: {}", e))?;
UserPersistence::save_user_data(&resource_provider_data)
.map_err(|e| format!("Failed to save resource_provider data: {}", e))?;
}
Ok(released_count)
}
/// Get slice rental statistics for a farmer
pub fn get_farmer_slice_statistics(&self, farmer_email: &str) -> SliceRentalStatistics {
if let Some(farmer_data) = UserPersistence::load_user_data(farmer_email) {
/// Get slice rental statistics for a resource_provider
pub fn get_resource_provider_slice_statistics(&self, resource_provider_email: &str) -> SliceRentalStatistics {
if let Some(resource_provider_data) = UserPersistence::load_user_data(resource_provider_email) {
let mut stats = SliceRentalStatistics::default();
for node in &farmer_data.nodes {
for node in &resource_provider_data.nodes {
stats.total_nodes += 1;
stats.total_base_slices += node.total_base_slices as u32;
stats.allocated_base_slices += node.allocated_base_slices as u32;
@@ -379,7 +379,7 @@ impl SliceRentalService {
}
}
/// Statistics for farmer slice rentals
/// Statistics for resource_provider slice rentals
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SliceRentalStatistics {
pub total_nodes: u32,

View File

@@ -41,15 +41,15 @@ pub struct UserPersistentData {
pub slas: Vec<ServiceLevelAgreement>,
// App provider data
pub apps: Vec<crate::models::user::PublishedApp>,
pub app_deployments: Vec<AppDeployment>,
pub application_deployments: Vec<AppDeployment>,
// Account deletion tracking
pub deleted: Option<bool>,
pub deleted_at: Option<String>,
pub deletion_reason: Option<String>,
// Farmer-specific data
// ResourceProvider-specific data
pub nodes: Vec<crate::models::user::FarmNode>,
pub farmer_earnings: Vec<crate::models::user::EarningsRecord>,
pub farmer_settings: Option<crate::models::user::FarmerSettings>,
pub resource_provider_earnings: Vec<crate::models::user::EarningsRecord>,
pub resource_provider_settings: Option<crate::models::user::ResourceProviderSettings>,
#[serde(default)]
pub slice_products: Vec<crate::models::product::Product>,
// User activity tracking
@@ -63,10 +63,10 @@ pub struct UserPersistentData {
#[serde(default)]
pub active_product_rentals: Vec<ProductRental>,
#[serde(default)]
pub farmer_rental_earnings: Vec<crate::models::user::FarmerRentalEarning>,
pub resource_provider_rental_earnings: Vec<crate::models::user::ResourceProviderRentalEarning>,
#[serde(default)]
pub node_rentals: Vec<crate::models::user::NodeRental>,
// Node groups for farmer organization
// Node groups for resource_provider organization
#[serde(default)]
pub node_groups: Vec<crate::models::user::NodeGroup>,
// NEW: Slice rental tracking for users
@@ -137,20 +137,20 @@ impl Default for UserPersistentData {
availability: None,
slas: Vec::new(),
apps: Vec::new(),
app_deployments: Vec::new(),
application_deployments: Vec::new(),
deleted: None,
deleted_at: None,
deletion_reason: None,
nodes: Vec::new(),
farmer_earnings: Vec::new(),
farmer_settings: None,
resource_provider_earnings: Vec::new(),
resource_provider_settings: None,
slice_products: Vec::new(),
user_activities: Vec::new(),
user_preferences: None,
usage_statistics: None,
orders: Vec::new(),
active_product_rentals: Vec::new(),
farmer_rental_earnings: Vec::new(),
resource_provider_rental_earnings: Vec::new(),
node_rentals: Vec::new(),
node_groups: Vec::new(),
slice_rentals: Vec::new(),
@@ -1247,22 +1247,22 @@ impl UserPersistence {
}
/// Get app deployments for a user
pub fn get_user_app_deployments(user_email: &str) -> Vec<AppDeployment> {
pub fn get_user_application_deployments(user_email: &str) -> Vec<AppDeployment> {
if let Some(data) = Self::load_user_data(user_email) {
data.app_deployments
data.application_deployments
} else {
Vec::default()
}
}
/// Add a new app deployment
pub fn add_user_app_deployment(
pub fn add_user_application_deployment(
user_email: &str,
deployment: AppDeployment
) -> Result<(), Box<dyn std::error::Error>> {
let mut data = crate::models::builders::SessionDataBuilder::load_or_create(user_email);
data.app_deployments.push(deployment.clone());
data.application_deployments.push(deployment.clone());
Self::save_user_data(&data)?;
Ok(())
}
@@ -1306,19 +1306,19 @@ impl UserPersistence {
}
}
/// Get farmer earnings for a user
pub fn get_farmer_earnings(user_email: &str) -> Vec<crate::models::user::EarningsRecord> {
/// Get resource_provider earnings for a user
pub fn get_resource_provider_earnings(user_email: &str) -> Vec<crate::models::user::EarningsRecord> {
if let Some(data) = Self::load_user_data(user_email) {
data.farmer_earnings
data.resource_provider_earnings
} else {
Vec::default()
}
}
/// Get farmer settings for a user
pub fn get_farmer_settings(user_email: &str) -> Option<crate::models::user::FarmerSettings> {
/// Get resource_provider settings for a user
pub fn get_resource_provider_settings(user_email: &str) -> Option<crate::models::user::ResourceProviderSettings> {
if let Some(data) = Self::load_user_data(user_email) {
data.farmer_settings
data.resource_provider_settings
} else {
None
}

View File

@@ -181,9 +181,9 @@ impl UserService {
/// Get user's purchased applications (for user dashboard) - derived from deployments
pub fn get_user_applications(&self, user_email: &str) -> Vec<crate::models::user::PublishedApp> {
if let Some(persistent_data) = UserPersistence::load_user_data(user_email) {
// Convert app deployments to application view for user dashboard
// Convert application deployments to application view for user dashboard
// IMPORTANT: Only show deployments where THIS user is the customer
let purchased_apps: Vec<crate::models::user::PublishedApp> = persistent_data.app_deployments
let purchased_apps: Vec<crate::models::user::PublishedApp> = persistent_data.application_deployments
.into_iter()
.filter(|d| d.status == "Active" && d.customer_email == user_email)
.map(|deployment| crate::models::user::PublishedApp {

View File

@@ -107,7 +107,7 @@ window.AppProviderDashboard = window.AppProviderDashboard || (class AppProviderD
tooltip: {
callbacks: {
label: function(context) {
return `Revenue: ${context.raw} TFC`;
return `Revenue: ${context.raw} MC`;
}
}
}
@@ -117,7 +117,7 @@ window.AppProviderDashboard = window.AppProviderDashboard || (class AppProviderD
beginAtZero: true,
title: {
display: true,
text: 'Revenue (TFC)'
text: 'Revenue (MC)'
}
},
x: {
@@ -168,7 +168,7 @@ window.AppProviderDashboard = window.AppProviderDashboard || (class AppProviderD
label: function(context) {
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((context.raw / total) * 100).toFixed(1);
return `${context.label}: ${context.raw} TFC (${percentage}%)`;
return `${context.label}: ${context.raw} MC (${percentage}%)`;
}
}
}
@@ -330,7 +330,7 @@ window.AppProviderDashboard = window.AppProviderDashboard || (class AppProviderD
<div>${stars}</div>
</div>
</td>
<td>${(app.monthly_revenue_usd || 0)} TFC</td>
<td>${(app.monthly_revenue_usd || 0)} MC</td>
<td>
<div class="btn-group">
<button class="btn btn-sm btn-outline-primary" onclick="manageApp('${app.id}', '${app.name}')">Manage</button>
@@ -417,7 +417,7 @@ window.AppProviderDashboard = window.AppProviderDashboard || (class AppProviderD
<small>${healthScore.toFixed(1)}%</small>
</td>
<td>${resourceUsage}</td>
<td><strong>${this.estimateDeploymentRevenue(deployment.app_name)} TFC</strong></td>
<td><strong>${this.estimateDeploymentRevenue(deployment.app_name)} MC</strong></td>
<td>
${autoHealingDisplay}
</td>
@@ -566,7 +566,7 @@ document.addEventListener('DOMContentLoaded', function() {
}
// Load app provider data via apiJson
window.apiJson('/api/dashboard/app-provider-data', { cache: 'no-store' })
window.apiJson('/api/dashboard/application-provider-data', { cache: 'no-store' })
.then(data => {
window.__appProviderDashboard = new window.AppProviderDashboard(data || {});
})

View File

@@ -508,7 +508,7 @@ class DashboardMessaging {
const colors = {
'service_booking': 'primary',
'slice_rental': 'success',
'app_deployment': 'info',
'application_deployment': 'info',
'general': 'secondary',
'support': 'warning'
};

View File

@@ -1,15 +1,15 @@
// Dashboard Farmer JavaScript
// Handles farmer dashboard functionality including automatic slice management, grid integration, and node management
// Dashboard ResourceProvider JavaScript
// Handles resource_provider dashboard functionality including automatic slice management, grid integration, and node management
if (window.farmerDashboardInitialized) {
console.debug('Farmer dashboard already initialized; skipping');
if (window.resourceProviderDashboardInitialized) {
console.debug('ResourceProvider dashboard already initialized; skipping');
} else {
window.farmerDashboardInitialized = true;
window.resourceProviderDashboardInitialized = true;
document.addEventListener('DOMContentLoaded', function() {
console.log('🚜 Farmer Dashboard JavaScript loaded - Automatic Slice System');
console.log('🚜 ResourceProvider Dashboard JavaScript loaded - Automatic Slice System');
// Initialize dashboard
initializeFarmerDashboard();
initializeResourceProviderDashboard();
initializeAutomaticSliceManagement(); // NEW: Initialize automatic slice management
initializeNodeManagement();
initializeStakingManagement();
@@ -762,7 +762,7 @@ function showNodeDetailsModal(node) {
function loadSliceStatistics() {
console.log('📊 Loading slice statistics');
window.apiJson('/api/dashboard/farmer/slice-statistics')
window.apiJson('/api/dashboard/resource_provider/slice-statistics')
.then(data => {
// apiJson returns unwrapped data; support either {statistics: {...}} or {...}
const stats = (data && data.statistics) ? data.statistics : (data || {});
@@ -1173,13 +1173,13 @@ function createSimpleIndividualPricingForms() {
}
/**
* Initialize farmer dashboard functionality
* Initialize resource_provider dashboard functionality
*/
function initializeFarmerDashboard() {
console.log('🚜 Initializing farmer dashboard');
function initializeResourceProviderDashboard() {
console.log('🚜 Initializing resource_provider dashboard');
// Load farmer data
loadFarmerData();
// Load resource_provider data
loadResourceProviderData();
// Load slice templates
loadSliceTemplates();
@@ -1188,7 +1188,7 @@ function initializeFarmerDashboard() {
loadNodeGroups();
// Set up periodic data refresh
setInterval(loadFarmerData, 30000); // Refresh every 30 seconds
setInterval(loadResourceProviderData, 30000); // Refresh every 30 seconds
setInterval(loadSliceTemplates, 60000); // Refresh slice templates every minute
setInterval(loadNodeGroups, 60000); // Refresh node groups every minute
}
@@ -2911,12 +2911,12 @@ function resetSliceConfigurationForm() {
}
/**
* Load farmer data from API
* Load resource_provider data from API
*/
async function loadFarmerData() {
async function loadResourceProviderData() {
try {
const data = await window.apiJson('/api/dashboard/farmer-data');
console.log('🚜 Loaded farmer data:', data);
const data = await window.apiJson('/api/dashboard/resource_provider-data');
console.log('🚜 Loaded resource_provider data:', data);
// Load node groups as well
const groupsResult = await window.apiJson('/api/dashboard/node-groups');
@@ -2924,8 +2924,8 @@ async function loadFarmerData() {
data.nodeGroups = nodeGroups;
console.log('🚜 Node groups loaded:', nodeGroups);
// Store farmer data globally for slice status checking
window.farmerData = data;
// Store resource_provider data globally for slice status checking
window.resourceProviderData = data;
// Update dashboard stats
updateDashboardStats(data);
@@ -2940,8 +2940,8 @@ async function loadFarmerData() {
loadSliceTemplates();
} catch (error) {
console.error('🚜 Error loading farmer data:', error);
showNotification('Failed to load farmer data', 'error');
console.error('🚜 Error loading resource_provider data:', error);
showNotification('Failed to load resource_provider data', 'error');
}
}
@@ -3130,13 +3130,13 @@ function showSliceTemplatesError() {
* Returns 'Active' if at least one node is using this slice format, 'Available' otherwise
*/
function getSliceFormatStatus(sliceFormatId) {
// Check if we have farmer data loaded
if (!window.farmerData || !window.farmerData.nodes) {
// Check if we have resource_provider data loaded
if (!window.resourceProviderData || !window.resourceProviderData.nodes) {
return 'Available'; // Default to Available if no data
}
// Check if any node has this slice format in their slice_formats array
const isUsedByAnyNode = window.farmerData.nodes.some(node => {
const isUsedByAnyNode = window.resourceProviderData.nodes.some(node => {
return node.slice_formats &&
Array.isArray(node.slice_formats) &&
node.slice_formats.includes(sliceFormatId);
@@ -3507,8 +3507,8 @@ function updateNodesTable(nodes) {
// Group info with enhanced display - get actual group name from loaded groups
let groupInfo = '<span class="badge bg-secondary">Single</span>';
if (node.node_group_id && window.farmerData && window.farmerData.nodeGroups) {
const group = window.farmerData.nodeGroups.find(g => g.id === node.node_group_id);
if (node.node_group_id && window.resourceProviderData && window.resourceProviderData.nodeGroups) {
const group = window.resourceProviderData.nodeGroups.find(g => g.id === node.node_group_id);
if (group) {
groupInfo = `<span class="badge bg-info">${group.name}</span>`;
}
@@ -3966,9 +3966,9 @@ async function addGridNodes() {
// Reset form
resetAddNodeForm();
// Reload farmer data and node groups with small delay for backend processing
// Reload resource_provider data and node groups with small delay for backend processing
setTimeout(async () => {
await loadFarmerData();
await loadResourceProviderData();
await loadNodeGroups(); // FARMER FIX: Refresh node groups table after adding new nodes
await loadWalletBalance(); // Refresh wallet balance after staking
await updateStakingDisplay(); // Refresh staking statistics after staking
@@ -4155,9 +4155,9 @@ async function confirmNodeDeletion() {
deleteModal.hide();
}
// Reload farmer data and node groups with small delay for backend processing
// Reload resource_provider data and node groups with small delay for backend processing
setTimeout(() => {
loadFarmerData();
loadResourceProviderData();
loadNodeGroups(); // FARMER FIX: Refresh node groups table after node deletion
}, 100);
@@ -4925,9 +4925,9 @@ async function saveNodeConfiguration() {
modal.hide();
}
// Reload farmer data and node groups with small delay for backend processing
// Reload resource_provider data and node groups with small delay for backend processing
setTimeout(async () => {
await loadFarmerData();
await loadResourceProviderData();
await loadNodeGroups(); // FARMER FIX: Refresh node groups table after node configuration changes
await loadWalletBalance(); // Refresh wallet balance after staking changes
await updateStakingDisplay(); // Refresh staking statistics after staking changes
@@ -5627,7 +5627,7 @@ function createSliceFormatCard(format, type) {
</div>
`;
} else {
// Use persistent data from farmer settings (price_per_hour) instead of hardcoded fallback
// Use persistent data from resource_provider settings (price_per_hour) instead of hardcoded fallback
const hourlyPrice = format.price_per_hour || format.price || 10; // Default to 10 if no price found
pricingDisplay = `<small class="text-muted">${hourlyPrice} ${currency}/hour</small>`;
}
@@ -5706,15 +5706,15 @@ function getSliceFormatDisplayName(formatId, formatName) {
return formatName;
}
// Look up custom slice products from loaded farmer data
if (window.farmerData && window.farmerData.slice_products) {
const sliceProduct = window.farmerData.slice_products.find(product => product.id === formatId);
// Look up custom slice products from loaded resource_provider data
if (window.resourceProviderData && window.resourceProviderData.slice_products) {
const sliceProduct = window.resourceProviderData.slice_products.find(product => product.id === formatId);
if (sliceProduct && sliceProduct.name) {
return sliceProduct.name;
}
}
// Look up from globally loaded slice products if farmer data not available
// Look up from globally loaded slice products if resource_provider data not available
if (window.loadedSliceProducts) {
const sliceProduct = window.loadedSliceProducts.find(product => product.id === formatId);
if (sliceProduct && sliceProduct.name) {
@@ -6587,7 +6587,7 @@ async function deleteCustomNodeGroup(groupId) {
console.log('📦 Custom group deleted:', result);
showNotification('Custom node group deleted successfully', 'success');
modal.hide();
setTimeout(() => { loadNodeGroups(); try { loadExistingGroups(); } catch (_) {} loadFarmerData(); }, 100);
setTimeout(() => { loadNodeGroups(); try { loadExistingGroups(); } catch (_) {} loadResourceProviderData(); }, 100);
} catch (error) {
console.error('📦 Error deleting custom group:', error);
showNotification('Failed to delete custom group', 'error');
@@ -6727,7 +6727,7 @@ async function assignNodeToGroup(nodeId, groupId) {
// FARMER FIX: Add small delay to ensure backend processing is complete
setTimeout(() => {
loadFarmerData(); // Reload to show updated assignments
loadResourceProviderData(); // Reload to show updated assignments
loadNodeGroups(); // Reload to update group statistics
}, 100);
@@ -7189,7 +7189,7 @@ function clearValidationError(input) {
*/
async function updateStakingDisplay() {
try {
const data = await window.apiJson('/api/dashboard/farmer-data');
const data = await window.apiJson('/api/dashboard/resource_provider-data');
const nodes = data.nodes || [];
// Calculate staking statistics
@@ -7523,7 +7523,7 @@ function showStakeNodeModal(nodeId) {
console.log('🛡️ Showing stake modal for node:', nodeId);
// Find the node data
const node = window.farmerData?.nodes?.find(n => n.id === nodeId);
const node = window.resourceProviderData?.nodes?.find(n => n.id === nodeId);
if (!node) {
showNotification('Node not found', 'error');
return;
@@ -7657,8 +7657,8 @@ async function stakeOnNode(nodeId, modal) {
showNotification(`Successfully staked ${stakeAmount} TFP on node`, 'success');
modal.hide();
// Refresh farmer data to show updated staking
await loadFarmerData();
// Refresh resource_provider data to show updated staking
await loadResourceProviderData();
// Update wallet balance display
await loadWalletBalance();
@@ -7683,7 +7683,7 @@ async function unstakeFromNode(nodeId) {
console.log('🛡️ Unstaking TFP from node:', nodeId);
// Find the node data
const node = window.farmerData?.nodes?.find(n => n.id === nodeId);
const node = window.resourceProviderData?.nodes?.find(n => n.id === nodeId);
if (!node || !node.staking_options || !node.staking_options.staking_enabled) {
showNotification('Node is not currently staked', 'error');
return;
@@ -7712,8 +7712,8 @@ async function unstakeFromNode(nodeId) {
const returnedAmount = result?.returned_amount || stakedAmount;
showNotification(`Successfully unstaked ${returnedAmount} TFP from node`, 'success');
// Refresh farmer data to show updated staking
await loadFarmerData();
// Refresh resource_provider data to show updated staking
await loadResourceProviderData();
// Update wallet balance display
await loadWalletBalance();
@@ -7731,7 +7731,7 @@ function showUpdateStakeModal(nodeId) {
console.log('🛡️ Showing update stake modal for node:', nodeId);
// Find the node data
const node = window.farmerData?.nodes?.find(n => n.id === nodeId);
const node = window.resourceProviderData?.nodes?.find(n => n.id === nodeId);
if (!node || !node.staking_options || !node.staking_options.staking_enabled) {
showNotification('Node is not currently staked', 'error');
return;
@@ -7847,8 +7847,8 @@ async function updateNodeStaking(nodeId, modal) {
showNotification(`Successfully updated staking to ${newStakeAmount} TFP`, 'success');
modal.hide();
// Refresh farmer data to show updated staking
await loadFarmerData();
// Refresh resource_provider data to show updated staking
await loadResourceProviderData();
// Update wallet balance display
await loadWalletBalance();

View File

@@ -100,7 +100,7 @@ class ServiceProviderDashboard {
tooltip: {
callbacks: {
label: function(context) {
return `Revenue: ${context.raw} TFP`;
return `Revenue: ${context.raw} MC`;
}
}
}
@@ -110,7 +110,7 @@ class ServiceProviderDashboard {
beginAtZero: true,
title: {
display: true,
text: 'Revenue (TFP)'
text: 'Revenue (MC)'
}
},
x: {
@@ -293,7 +293,7 @@ class ServiceProviderDashboard {
callbacks: {
label: function(context) {
const point = context.raw;
return `${point.label}: Rating ${point.x}, ${point.y} TFP/hour`;
return `${point.label}: Rating ${point.x}, ${point.y} MC/hour`;
}
}
}
@@ -310,7 +310,7 @@ class ServiceProviderDashboard {
y: {
title: {
display: true,
text: 'Price per Hour (TFP)'
text: 'Price per Hour (MC)'
},
beginAtZero: true
}
@@ -397,7 +397,7 @@ class ServiceProviderDashboard {
</td>
<td>${service.category || 'General'}</td>
<td><span class="badge bg-${statusClass}">${service.status || 'Active'}</span></td>
<td>${service.hourly_rate || service.price_per_hour || service.price_per_hour_usd || service.price_amount || 0} TFC/hour</td>
<td>${service.hourly_rate || service.price_per_hour || service.price_per_hour_usd || service.price_amount || 0} MC/hour</td>
<td>${service.clients || 0}</td>
<td>
<div class="d-flex align-items-center">
@@ -523,7 +523,7 @@ class ServiceProviderDashboard {
<td><span class="badge bg-${statusClass}">${request.status}</span></td>
<td>${request.requested_date}</td>
<td>${request.estimated_hours}</td>
<td>${request.budget} TFP</td>
<td>${request.budget} MC</td>
<td><span class="badge bg-${priorityClass}">${request.priority}</span></td>
<td>
<div class="btn-group">
@@ -584,7 +584,7 @@ class ServiceProviderDashboard {
</div>
</div>
</td>
<td>${request.budget} TFP</td>
<td>${request.budget} MC</td>
<td><span class="badge bg-${priorityClass}">${request.priority}</span></td>
<td>
<div class="btn-group">
@@ -654,7 +654,7 @@ class ServiceProviderDashboard {
<td><span class="badge bg-success">${request.status}</span></td>
<td>${completedDate}</td>
<td>${totalHours}${typeof totalHours === 'number' ? ' hours' : ''}</td>
<td>${totalAmount}${typeof totalAmount === 'number' ? ' TFP' : ''}</td>
<td>${totalAmount}${typeof totalAmount === 'number' ? ' MC' : ''}</td>
<td>
<div class="d-flex align-items-center">
<span class="me-2">${rating}</span>
@@ -1176,8 +1176,8 @@ async function loadServiceAnalytics(serviceId) {
document.getElementById('serviceOnTimeDelivery').textContent = `${analytics.performance.on_time_delivery.toFixed(0)}%`;
document.getElementById('serviceCompletionRate').textContent = `${analytics.performance.completion_rate.toFixed(0)}%`;
document.getElementById('serviceTotalRevenue').textContent = `${analytics.revenue.total} TFP`;
document.getElementById('serviceMonthlyRevenue').textContent = `${analytics.revenue.monthly} TFP`;
document.getElementById('serviceTotalRevenue').textContent = `${analytics.revenue.total} MC`;
document.getElementById('serviceMonthlyRevenue').textContent = `${analytics.revenue.monthly} MC`;
document.getElementById('serviceTotalProjects').textContent = analytics.project_metrics.total_projects;
document.getElementById('serviceActiveClients').textContent = analytics.client_metrics.total_clients;
@@ -1237,7 +1237,7 @@ function createServiceRevenueAnalyticsChart(revenueData) {
tooltip: {
callbacks: {
label: function(context) {
return `Revenue: ${context.raw} TFP`;
return `Revenue: ${context.raw} MC`;
}
}
}
@@ -1247,7 +1247,7 @@ function createServiceRevenueAnalyticsChart(revenueData) {
beginAtZero: true,
title: {
display: true,
text: 'Revenue (TFP)'
text: 'Revenue (MC)'
}
},
x: {
@@ -1300,7 +1300,7 @@ async function loadServiceClients(serviceId) {
</div>
</td>
<td>${client.projects.length}</td>
<td>${client.total_revenue} TFP</td>
<td>${client.total_revenue} MC</td>
<td>
<div class="d-flex align-items-center">
<span class="me-2">${client.avg_rating.toFixed(1)}</span>
@@ -1607,7 +1607,7 @@ function populateRequestDetailsModal(request) {
document.getElementById('detailRequestedDate').textContent = request.requested_date;
document.getElementById('detailEstimatedHours').textContent = `${request.estimated_hours} hours`;
document.getElementById('detailBudget').textContent = `${request.budget} TFP`;
document.getElementById('detailBudget').textContent = `${request.budget} MC`;
// Progress bar
const progress = request.progress || 0;
@@ -1888,7 +1888,7 @@ function populateCompletedRequestModal(request) {
document.getElementById('completedServiceType').textContent = request.service_name;
document.getElementById('completedDate').textContent = request.completed_date || 'N/A';
document.getElementById('completedHoursLogged').textContent = `${request.hours_logged || 0} hours`;
document.getElementById('completedRevenue').textContent = `${request.revenue || 0} TFP`;
document.getElementById('completedRevenue').textContent = `${request.revenue || 0} MC`;
document.getElementById('completedRating').textContent = request.rating ? `${request.rating}/5 ⭐` : 'Not rated';
document.getElementById('completedOnTime').textContent = request.on_time ? 'Yes ✅' : 'No ❌';
document.getElementById('completedSummary').textContent = request.summary || 'No summary available.';

View File

@@ -23,13 +23,13 @@ class UserDashboard {
Chart.defaults.responsive = true;
Chart.defaults.maintainAspectRatio = false;
this.createTFCUsageChart();
this.createMCUsageChart();
this.createResourceUtilizationChart();
this.createUserActivityChart();
this.createDeploymentDistributionChart();
}
createTFCUsageChart() {
createMCUsageChart() {
const ctx = document.getElementById('tfpUsageChart');
if (!ctx) return;
@@ -38,7 +38,7 @@ class UserDashboard {
data: {
labels: ['Month 1', 'Month 2', 'Month 3', 'Month 4', 'Month 5', 'Month 6'],
datasets: [{
label: 'TFC Usage',
label: 'MC Usage',
data: this.userData.tfp_usage_trend || [0, 0, 0, 0, 0, 0],
borderColor: '#007bff',
backgroundColor: 'rgba(0, 123, 255, 0.1)',
@@ -58,7 +58,7 @@ class UserDashboard {
tooltip: {
callbacks: {
label: function(context) {
return `TFC Usage: ${context.raw} TFC`;
return `MC Usage: ${context.raw} MC`;
}
}
}
@@ -368,7 +368,7 @@ class UserDashboard {
<td><span class="badge ${statusBadge}">${app.status}</span></td>
<td>${app.rating}/5 ⭐</td>
<td>${app.deployments} active</td>
<td>${app.monthly_revenue} TFP/month</td>
<td>${app.monthly_revenue} MC/month</td>
<td>
<div class="btn-group">
<button class="btn btn-sm btn-outline-primary" onclick="manageApplication('${app.id}', '${app.name}')">Manage</button>
@@ -416,7 +416,7 @@ class UserDashboard {
</td>
<td><span class="badge bg-light text-dark">${service.category}</span></td>
<td><span class="badge ${statusBadge}">${service.status}</span></td>
<td>${service.price_per_hour} TFP</td>
<td>${service.price_per_hour} MC</td>
<td>${service.clients}</td>
<td>
<span class="text-warning">${stars}</span>
@@ -472,7 +472,7 @@ class UserDashboard {
<td>${resource.location}</td>
<td><span class="badge ${statusBadge}">${resource.status}</span></td>
<td>${resource.sla}</td>
<td>${resource.monthly_cost} TFP/month</td>
<td>${resource.monthly_cost} MC/month</td>
<td>
<div class="btn-group">
<button class="btn btn-sm btn-outline-primary" onclick="manageResource('${resource.id}')">Details</button>
@@ -525,7 +525,7 @@ class UserDashboard {
<td>${rental.specs}</td>
<td>${rental.location}</td>
<td><span class="badge ${statusBadge}">${rental.status}</span></td>
<td>${rental.monthly_cost} TFP/month</td>
<td>${rental.monthly_cost} MC/month</td>
<td>
<div class="btn-group">
<button class="btn btn-sm btn-outline-primary" onclick="manageSliceRental('${rental.id}')">Manage</button>
@@ -558,7 +558,7 @@ class UserDashboard {
if (activeRentalsElement) activeRentalsElement.textContent = activeRentals;
if (vmDeploymentsElement) vmDeploymentsElement.textContent = vmDeployments;
if (k8sDeploymentsElement) k8sDeploymentsElement.textContent = k8sDeployments;
if (monthlyCostElement) monthlyCostElement.textContent = `${totalMonthlyCost} TFP`;
if (monthlyCostElement) monthlyCostElement.textContent = `${totalMonthlyCost} MC`;
console.log(`📊 Updated slice rental stats: ${activeRentals} active, ${vmDeployments} VM, ${k8sDeployments} K8s, ${totalMonthlyCost} TFP/month`);
}

View File

@@ -206,12 +206,12 @@
if (tfpAmountTFT) {
tfpAmountTFT.addEventListener('input', () => {
const amount = parseFloat(tfpAmountTFT.value) || 0;
const tftCost = amount * 0.5; // 1 TFC = 0.5 TFT
const modal = document.getElementById('buyTFCWithTFTModal');
const tftCost = amount * 0.5; // 1 MC = 0.5 TFT
const modal = document.getElementById('buyMCWithTFTModal');
if (modal) {
const rows = modal.querySelectorAll('.alert .d-flex.justify-content-between .text-end');
// rows[0] -> Amount, rows[1] -> Cost
if (rows[0]) rows[0].textContent = `${amount} TFC`;
if (rows[0]) rows[0].textContent = `${amount} MC`;
if (rows[1]) rows[1].textContent = `${tftCost.toFixed(1)} TFT`;
}
});
@@ -222,11 +222,11 @@
if (sellTfpAmountTFT) {
sellTfpAmountTFT.addEventListener('input', () => {
const amount = parseFloat(sellTfpAmountTFT.value) || 0;
const tftReceive = amount * 0.5; // 1 TFC = 0.5 TFT
const modal = document.getElementById('sellTFCForTFTModal');
const tftReceive = amount * 0.5; // 1 MC = 0.5 TFT
const modal = document.getElementById('sellMCForTFTModal');
if (modal) {
const rows = modal.querySelectorAll('.alert .d-flex.justify-content-between .text-end');
if (rows[0]) rows[0].textContent = `${amount} TFC`;
if (rows[0]) rows[0].textContent = `${amount} MC`;
if (rows[1]) rows[1].textContent = `${tftReceive.toFixed(1)} TFT`;
}
});
@@ -237,11 +237,11 @@
if (tfpAmountPEAQ) {
tfpAmountPEAQ.addEventListener('input', () => {
const amount = parseFloat(tfpAmountPEAQ.value) || 0;
const peaqCost = amount * 2.0; // 1 TFC = 2 PEAQ
const modal = document.getElementById('buyTFCWithPEAQModal');
const peaqCost = amount * 2.0; // 1 MC = 2 PEAQ
const modal = document.getElementById('buyMCWithPEAQModal');
if (modal) {
const rows = modal.querySelectorAll('.alert .d-flex.justify-content-between .text-end');
if (rows[0]) rows[0].textContent = `${amount} TFC`;
if (rows[0]) rows[0].textContent = `${amount} MC`;
if (rows[1]) rows[1].textContent = `${peaqCost.toFixed(0)} PEAQ`;
}
});
@@ -252,11 +252,11 @@
if (sellTfpAmountPEAQ) {
sellTfpAmountPEAQ.addEventListener('input', () => {
const amount = parseFloat(sellTfpAmountPEAQ.value) || 0;
const peaqReceive = amount * 2.0; // 1 TFC = 2 PEAQ
const modal = document.getElementById('sellTFCForPEAQModal');
const peaqReceive = amount * 2.0; // 1 MC = 2 PEAQ
const modal = document.getElementById('sellMCForPEAQModal');
if (modal) {
const rows = modal.querySelectorAll('.alert .d-flex.justify-content-between .text-end');
if (rows[0]) rows[0].textContent = `${amount} TFC`;
if (rows[0]) rows[0].textContent = `${amount} MC`;
if (rows[1]) rows[1].textContent = `${peaqReceive.toFixed(0)} PEAQ`;
}
});
@@ -375,8 +375,8 @@
},
body: JSON.stringify({ amount, payment_method: 'TFT' }),
});
showSuccessToast(`Purchased ${amount} TFC with TFT`);
const modal = document.getElementById('buyTFCWithTFTModal');
showSuccessToast(`Purchased ${amount} MC with TFT`);
const modal = document.getElementById('buyMCWithTFTModal');
if (modal && window.bootstrap) bootstrap.Modal.getOrCreateInstance(modal).hide();
} catch (e) {
if (e && e.status === 402) {
@@ -398,8 +398,8 @@
},
body: JSON.stringify({ amount, currency: 'TFT', payout_method: 'blockchain' }),
});
showSuccessToast(`Sold ${amount} TFC for TFT`);
const modal = document.getElementById('sellTFCForTFTModal');
showSuccessToast(`Sold ${amount} MC for TFT`);
const modal = document.getElementById('sellMCForTFTModal');
if (modal && window.bootstrap) bootstrap.Modal.getOrCreateInstance(modal).hide();
} catch (e) {
if (e && e.status === 402) {
@@ -421,8 +421,8 @@
},
body: JSON.stringify({ amount, payment_method: 'PEAQ' }),
});
showSuccessToast(`Purchased ${amount} TFC with PEAQ`);
const modal = document.getElementById('buyTFCWithPEAQModal');
showSuccessToast(`Purchased ${amount} MC with PEAQ`);
const modal = document.getElementById('buyMCWithPEAQModal');
if (modal && window.bootstrap) bootstrap.Modal.getOrCreateInstance(modal).hide();
} catch (e) {
if (e && e.status === 402) {
@@ -444,8 +444,8 @@
},
body: JSON.stringify({ amount, currency: 'PEAQ', payout_method: 'blockchain' }),
});
showSuccessToast(`Sold ${amount} TFC for PEAQ`);
const modal = document.getElementById('sellTFCForPEAQModal');
showSuccessToast(`Sold ${amount} MC for PEAQ`);
const modal = document.getElementById('sellMCForPEAQModal');
if (modal && window.bootstrap) bootstrap.Modal.getOrCreateInstance(modal).hide();
} catch (e) {
if (e && e.status === 402) {

View File

@@ -11,8 +11,8 @@ class DemoWorkflow {
action: () => this.showWelcome()
},
{
title: "App Provider: Register New Application",
description: "Let's start by registering a new application as an App Provider.",
title: "Application Provider: Register New Application",
description: "Let's start by registering a new application as an Application Provider.",
action: () => this.demoAppRegistration()
},
{
@@ -31,7 +31,7 @@ class DemoWorkflow {
action: () => this.demoAppDeployment()
},
{
title: "Farmer: Node Management",
title: "ResourceProvider: Node Management",
description: "Manage your farming nodes and monitor capacity.",
action: () => this.demoNodeManagement()
},
@@ -146,7 +146,7 @@ class DemoWorkflow {
showNotification('Demo: Navigating to App Provider dashboard...', 'info');
setTimeout(() => {
if (window.location.pathname !== '/dashboard/app-provider') {
if (window.location.pathname !== '/dashboard/application-provider') {
showNotification('Please navigate to the App Provider dashboard to continue the demo', 'warning');
return;
}
@@ -265,7 +265,7 @@ class DemoWorkflow {
}
demoNodeManagement() {
showNotification('Demo: Simulating farmer node management...', 'info');
showNotification('Demo: Simulating resource_provider node management...', 'info');
setTimeout(() => {
// Simulate node status change

View File

@@ -431,7 +431,7 @@ class MarketplaceIntegration {
if (!sessionStorage.getItem('marketplaceSlices')) {
// Get real users from user database
const alexUser = userDB.getUser('user-001'); // Alex Thompson - Farmer
const alexUser = userDB.getUser('user-001'); // Alex Thompson - ResourceProvider
const mockSlices = [
{

View File

@@ -77,7 +77,7 @@
fill: true,
},
{
label: '3Nodes',
label: 'Mycelium Nodes',
data: pick(data, 'monthlyGrowth.nodes', [45, 60, 75, 90, 120, 150]),
borderColor: '#28a745',
backgroundColor: 'rgba(40, 167, 69, 0.1)',
@@ -190,14 +190,14 @@
data: {
labels: pick(data, 'nodeGeographic.labels', ['Europe', 'North America', 'Asia', 'Africa', 'South America', 'Oceania']),
datasets: [{
label: 'Number of 3Nodes',
label: 'Number of Mycelium Nodes',
data: pick(data, 'nodeGeographic.values', [45, 32, 20, 15, 8, 5]),
backgroundColor: 'rgba(40, 167, 69, 0.7)',
borderWidth: 1,
}],
},
options: {
plugins: { legend: { display: false }, title: { display: true, text: 'Geographic Distribution of 3Nodes' } },
plugins: { legend: { display: false }, title: { display: true, text: 'Geographic Distribution of Mycelium Nodes' } },
scales: { y: { beginAtZero: true, title: { display: true, text: 'Number of Nodes' } } },
},
});
@@ -233,7 +233,7 @@
}],
},
options: {
plugins: { legend: { display: false }, title: { display: true, text: '3Node Uptime Performance' } },
plugins: { legend: { display: false }, title: { display: true, text: 'Mycelium Node Uptime Performance' } },
scales: { y: { min: 95, max: 100, title: { display: true, text: 'Uptime %' } } },
},
});
@@ -253,7 +253,7 @@
}],
},
options: {
plugins: { legend: { display: false }, title: { display: true, text: '3Node Certification Rate' } },
plugins: { legend: { display: false }, title: { display: true, text: 'Mycelium Node Certification Rate' } },
scales: { y: { min: 50, max: 100, title: { display: true, text: 'Certification %' } } },
},
});

View File

@@ -12,11 +12,11 @@ class UserDatabase {
const mockUsers = [
{
id: 'user-001',
username: 'sara_farmer',
username: 'sara_resource_provider',
display_name: 'Sara Nicks',
email: 'user1@example.com',
password: 'password',
role: 'farmer',
role: 'resource_provider',
location: 'Amsterdam, Netherlands',
joined_date: '2024-01-15',
reputation: 4.8,
@@ -33,7 +33,7 @@ class UserDatabase {
display_name: 'Alex Thompson',
email: 'user2@example.com',
password: 'password',
role: 'app_provider',
role: 'application_provider',
location: 'Berlin, Germany',
joined_date: '2024-02-20',
reputation: 4.9,
@@ -84,7 +84,7 @@ class UserDatabase {
display_name: 'Jordan Mitchell',
email: 'user5@example.com',
password: 'password',
role: 'multi', // Can be farmer, app_provider, service_provider, user
role: 'multi', // Can be resource_provider, application_provider, service_provider, user
location: 'Toronto, Canada',
joined_date: new Date().toISOString().split('T')[0],
reputation: 5.0,

View File

@@ -14,7 +14,7 @@ impl DataValidator {
if let Value::Object(ref mut obj) = value {
Self::repair_user_activities(obj)?;
Self::repair_farmer_settings(obj)?;
Self::repair_resource_provider_settings(obj)?;
Self::ensure_required_fields(obj)?;
}
@@ -47,20 +47,20 @@ impl DataValidator {
Ok(())
}
/// Repairs farmer settings to include all required fields
fn repair_farmer_settings(obj: &mut Map<String, Value>) -> Result<(), String> {
if let Some(Value::Object(ref mut farmer_settings)) = obj.get_mut("farmer_settings") {
/// Repairs resource_provider settings to include all required fields
fn repair_resource_provider_settings(obj: &mut Map<String, Value>) -> Result<(), String> {
if let Some(Value::Object(ref mut resource_provider_settings)) = obj.get_mut("resource_provider_settings") {
// Ensure minimum_deployment_duration exists
if !farmer_settings.contains_key("minimum_deployment_duration") {
farmer_settings.insert(
if !resource_provider_settings.contains_key("minimum_deployment_duration") {
resource_provider_settings.insert(
"minimum_deployment_duration".to_string(),
Value::Number(serde_json::Number::from(24))
);
}
// Ensure preferred_regions exists
if !farmer_settings.contains_key("preferred_regions") {
farmer_settings.insert(
if !resource_provider_settings.contains_key("preferred_regions") {
resource_provider_settings.insert(
"preferred_regions".to_string(),
Value::Array(vec![
Value::String("NA".to_string()),
@@ -81,9 +81,9 @@ impl DataValidator {
("services", Value::Array(vec![])),
("service_requests", Value::Array(vec![])),
("apps", Value::Array(vec![])),
("app_deployments", Value::Array(vec![])),
("application_deployments", Value::Array(vec![])),
("nodes", Value::Array(vec![])),
("farmer_earnings", Value::Array(vec![])),
("resource_provider_earnings", Value::Array(vec![])),
("user_activities", Value::Array(vec![])),
("pool_positions", Value::Object(Map::new())),
];

View File

@@ -1,10 +1,10 @@
{% extends "dashboard/layout.html" %}
{% block title %}ThreeFold Dashboard - App Provider{% endblock %}
{% block title %}ThreeFold Dashboard - Application Provider{% endblock %}
{% block dashboard_content %}
<div class="my-4">
<h1>App Provider Dashboard</h1>
<h1>Application Provider Dashboard</h1>
<p class="lead">Develop, deploy, and manage applications for the ThreeFold ecosystem</p>
<!-- Status Summary -->
@@ -13,7 +13,7 @@
<div class="stats-card info">
<h5 class="card-title">Published Apps</h5>
<div class="d-flex justify-content-between align-items-end">
<h2 class="mb-0">{% if app_provider_data is defined and app_provider_data.published_apps is defined %}{{ app_provider_data.published_apps }}{% else %}0{% endif %}</h2>
<h2 class="mb-0">{% if application_provider_data is defined and application_provider_data.published_apps is defined %}{{ application_provider_data.published_apps }}{% else %}0{% endif %}</h2>
<small class="text-muted">Active</small>
</div>
</div>
@@ -22,7 +22,7 @@
<div class="stats-card primary">
<h5 class="card-title">Active Deployments</h5>
<div class="d-flex justify-content-between align-items-end">
<h2 class="mb-0">{% if app_provider_data is defined and app_provider_data.active_deployments is defined %}{{ app_provider_data.active_deployments }}{% else %}0{% endif %}</h2>
<h2 class="mb-0">{% if application_provider_data is defined and application_provider_data.active_deployments is defined %}{{ application_provider_data.active_deployments }}{% else %}0{% endif %}</h2>
<small class="text-muted">Instances</small>
</div>
</div>
@@ -31,7 +31,7 @@
<div class="stats-card warning">
<h5 class="card-title">Customer Base</h5>
<div class="d-flex justify-content-between align-items-end">
<h2 class="mb-0">{% if app_provider_data is defined and app_provider_data.total_deployments is defined %}{{ app_provider_data.total_deployments }}{% else %}0{% endif %}</h2>
<h2 class="mb-0">{% if application_provider_data is defined and application_provider_data.total_deployments is defined %}{{ application_provider_data.total_deployments }}{% else %}0{% endif %}</h2>
<small class="text-muted">Total Deployments</small>
</div>
</div>
@@ -40,7 +40,7 @@
<div class="stats-card success">
<h5 class="card-title">Monthly Earnings</h5>
<div class="d-flex justify-content-between align-items-end">
<h2 class="mb-0">{% if app_provider_data is defined and app_provider_data.monthly_revenue_usd is defined %}{{ app_provider_data.monthly_revenue_usd }}{% else %}0{% endif %}</h2>
<h2 class="mb-0">{% if application_provider_data is defined and application_provider_data.monthly_revenue_usd is defined %}{{ application_provider_data.monthly_revenue_usd }}{% else %}0{% endif %}</h2>
<small class="text-muted">$/month</small>
</div>
</div>
@@ -232,14 +232,14 @@
{{ super() }}
<!-- Hydration JSON for initial app provider dashboard data (safe, non-executable) -->
<script id="ap-dashboard-hydration" type="application/json">
{% if app_provider_data is defined %}
{{ app_provider_data | json_encode() }}
{% if application_provider_data is defined %}
{{ application_provider_data | json_encode() }}
{% else %}
null
{% endif %}
</script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.1/dist/chart.min.js"></script>
<script src="/static/js/dashboard-app-provider.js"></script>
<script src="/static/js/dashboard-application-provider.js"></script>
<style>
/* Ensure charts have consistent sizes */
.card-body {

View File

@@ -78,8 +78,8 @@
</a>
</div>
<div class="col-md-3">
<a href="/dashboard/farmer" class="btn btn-outline-info w-100 mb-2">
<i class="bi bi-hdd-rack me-2"></i> Add a 3Node
<a href="/dashboard/resource_provider" class="btn btn-outline-info w-100 mb-2">
<i class="bi bi-hdd-rack me-2"></i> Add a Mycelium Node
</a>
</div>
</div>
@@ -117,21 +117,21 @@
</div>
<div class="col-md-6 col-lg-3 mb-4">
<div class="dashboard-card">
<span class="badge bg-success badge-role">FARMER</span>
<h4>Farmer Dashboard</h4>
<span class="badge bg-success badge-role">RESOURCE PROVIDER</span>
<h4>Resource Provider Dashboard</h4>
<p>Manage your nodes, create slices, set pricing, and track earnings.</p>
<div class="d-grid">
<a href="/dashboard/farmer" class="btn btn-sm btn-outline-success">Access Farmer Dashboard</a>
<a href="/dashboard/resource_provider" class="btn btn-sm btn-outline-success">Access Resource Provider Dashboard</a>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3 mb-4">
<div class="dashboard-card">
<span class="badge bg-info badge-role">APP PROVIDER</span>
<h4>App Provider Dashboard</h4>
<span class="badge bg-info badge-role">APPLICATION PROVIDER</span>
<h4>Application Provider Dashboard</h4>
<p>Develop, deploy, and manage applications for the TF ecosystem.</p>
<div class="d-grid">
<a href="/dashboard/app-provider" class="btn btn-sm btn-outline-info">Access App Provider Dashboard</a>
<a href="/dashboard/application-provider" class="btn btn-sm btn-outline-info">Access Application Provider Dashboard</a>
</div>
</div>
</div>

View File

@@ -28,15 +28,15 @@
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if active_section == 'farmer' %}active{% endif %}" href="/dashboard/farmer">
<a class="nav-link {% if active_section == 'resource_provider' %}active{% endif %}" href="/dashboard/resource_provider">
<i class="bi bi-hdd-rack me-1"></i>
Farmer
Resource Provider
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if active_section == 'app_provider' %}active{% endif %}" href="/dashboard/app-provider">
<a class="nav-link {% if active_section == 'application_provider' %}active{% endif %}" href="/dashboard/application-provider">
<i class="bi bi-app me-1"></i>
App Provider
Application Provider
</a>
</li>
<li class="nav-item">

View File

@@ -244,7 +244,7 @@
</div>
</div>
<!-- Buy TFC Credits Modal -->
<!-- Buy MC Credits Modal -->
<div class="modal fade" id="buyCreditsModal" tabindex="-1" aria-labelledby="buyCreditsModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
@@ -301,7 +301,7 @@
</div>
</div>
<!-- Sell TFC Credits Modal -->
<!-- Sell MC Credits Modal -->
<div class="modal fade" id="sellCreditsModal" tabindex="-1" aria-labelledby="sellCreditsModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
@@ -359,7 +359,7 @@
</div>
</div>
<!-- Stake TFC Credits Modal -->
<!-- Stake MC Credits Modal -->
<div class="modal fade" id="stakeCreditsModal" tabindex="-1" aria-labelledby="stakeCreditsModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
@@ -406,29 +406,29 @@
</div>
</div>
<!-- Buy TFC Credits with TFT Modal -->
<div class="modal fade" id="buyTFCWithTFTModal" tabindex="-1" aria-labelledby="buyTFCWithTFTModalLabel" aria-hidden="true">
<!-- Buy MC Credits with TFT Modal -->
<div class="modal fade" id="buyMCWithTFTModal" tabindex="-1" aria-labelledby="buyMCWithTFTModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title" id="buyTFCWithTFTModalLabel">Buy ThreeFold Credits (TFC) with TFT</h5>
<h5 class="modal-title" id="buyMCWithTFTModalLabel">Buy Mycelium Credits (MC) with TFT</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="mb-3">
<label for="tfcAmountTFT" class="form-label">Amount of TFC Credits to purchase</label>
<label for="tfcAmountTFT" class="form-label">Amount of MC Credits to purchase</label>
<input type="number" class="form-control" id="tfpAmountTFT" min="10" value="100">
</div>
<div class="alert alert-info">
<div class="d-flex align-items-center">
<i class="bi bi-info-circle-fill me-2 fs-4"></i>
<div>
<strong>Exchange Rate:</strong> 1 TFC = 0.5 TFT
<strong>Exchange Rate:</strong> 1 MC = 0.5 TFT
<hr class="my-2">
<div class="d-flex justify-content-between">
<span>Amount:</span>
<span class="text-end">100 TFC</span>
<span class="text-end">100 MC</span>
</div>
<div class="d-flex justify-content-between">
<span>Cost:</span>
@@ -447,30 +447,30 @@
</div>
</div>
<!-- Sell TFC Credits for TFT Modal -->
<div class="modal fade" id="sellTFCForTFTModal" tabindex="-1" aria-labelledby="sellTFCForTFTModalLabel" aria-hidden="true">
<!-- Sell MC Credits for TFT Modal -->
<div class="modal fade" id="sellMCForTFTModal" tabindex="-1" aria-labelledby="sellMCForTFTModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title" id="sellTFCForTFTModalLabel">Sell ThreeFold Credits (TFC) for TFT</h5>
<h5 class="modal-title" id="sellMCForTFTModalLabel">Sell Mycelium Credits (MC) for TFT</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="mb-3">
<label for="sellTfcAmountTFT" class="form-label">Amount of TFC Credits to sell</label>
<label for="sellTfcAmountTFT" class="form-label">Amount of MC Credits to sell</label>
<input type="number" class="form-control" id="sellTfpAmountTFT" min="10" max="1250" value="100">
<div class="form-text">Maximum available: 1,250 TFC</div>
<div class="form-text">Maximum available: 1,250 MC</div>
</div>
<div class="alert alert-info">
<div class="d-flex align-items-center">
<i class="bi bi-info-circle-fill me-2 fs-4"></i>
<div>
<strong>Exchange Rate:</strong> 1 TFC = 0.5 TFT
<strong>Exchange Rate:</strong> 1 MC = 0.5 TFT
<hr class="my-2">
<div class="d-flex justify-content-between">
<span>Amount:</span>
<span class="text-end">100 TFC</span>
<span class="text-end">100 MC</span>
</div>
<div class="d-flex justify-content-between">
<span>You receive:</span>
@@ -489,29 +489,29 @@
</div>
</div>
<!-- Buy TFC Credits with PEAQ Modal -->
<div class="modal fade" id="buyTFCWithPEAQModal" tabindex="-1" aria-labelledby="buyTFCWithPEAQModalLabel" aria-hidden="true">
<!-- Buy MC Credits with PEAQ Modal -->
<div class="modal fade" id="buyMCWithPEAQModal" tabindex="-1" aria-labelledby="buyMCWithPEAQModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-info text-white">
<h5 class="modal-title" id="buyTFCWithPEAQModalLabel">Buy ThreeFold Credits (TFC) with PEAQ</h5>
<h5 class="modal-title" id="buyMCWithPEAQModalLabel">Buy Mycelium Credits (MC) with PEAQ</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="mb-3">
<label for="tfcAmountPEAQ" class="form-label">Amount of TFC Credits to purchase</label>
<label for="tfcAmountPEAQ" class="form-label">Amount of MC Credits to purchase</label>
<input type="number" class="form-control" id="tfpAmountPEAQ" min="10" value="100">
</div>
<div class="alert alert-info">
<div class="d-flex align-items-center">
<i class="bi bi-info-circle-fill me-2 fs-4"></i>
<div>
<strong>Exchange Rate:</strong> 1 TFC = 2 PEAQ
<strong>Exchange Rate:</strong> 1 MC = 2 PEAQ
<hr class="my-2">
<div class="d-flex justify-content-between">
<span>Amount:</span>
<span class="text-end">100 TFC</span>
<span class="text-end">100 MC</span>
</div>
<div class="d-flex justify-content-between">
<span>Cost:</span>
@@ -530,30 +530,30 @@
</div>
</div>
<!-- Sell TFC Credits for PEAQ Modal -->
<div class="modal fade" id="sellTFCForPEAQModal" tabindex="-1" aria-labelledby="sellTFCForPEAQModalLabel" aria-hidden="true">
<!-- Sell MC Credits for PEAQ Modal -->
<div class="modal fade" id="sellMCForPEAQModal" tabindex="-1" aria-labelledby="sellMCForPEAQModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-info text-white">
<h5 class="modal-title" id="sellTFCForPEAQModalLabel">Sell ThreeFold Credits (TFC) for PEAQ</h5>
<h5 class="modal-title" id="sellMCForPEAQModalLabel">Sell Mycelium Credits (MC) for PEAQ</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="mb-3">
<label for="sellTfcAmountPEAQ" class="form-label">Amount of TFC Credits to sell</label>
<label for="sellTfcAmountPEAQ" class="form-label">Amount of MC Credits to sell</label>
<input type="number" class="form-control" id="sellTfpAmountPEAQ" min="10" max="1250" value="100">
<div class="form-text">Maximum available: 1,250 TFC</div>
<div class="form-text">Maximum available: 1,250 MC</div>
</div>
<div class="alert alert-info">
<div class="d-flex align-items-center">
<i class="bi bi-info-circle-fill me-2 fs-4"></i>
<div>
<strong>Exchange Rate:</strong> 1 TFC = 2 PEAQ
<strong>Exchange Rate:</strong> 1 MC = 2 PEAQ
<hr class="my-2">
<div class="d-flex justify-content-between">
<span>Amount:</span>
<span class="text-end">100 TFC</span>
<span class="text-end">100 MC</span>
</div>
<div class="d-flex justify-content-between">
<span>You receive:</span>

View File

@@ -1,10 +1,10 @@
{% extends "dashboard/layout.html" %}
{% block title %}ThreeFold Dashboard - Farmer{% endblock %}
{% block title %}ThreeFold Dashboard - Resource Provider{% endblock %}
{% block dashboard_content %}
<div class="my-4">
<h1>Farmer Dashboard</h1>
<h1>Resource Provider Dashboard</h1>
<p class="lead">Manage your nodes, configure slices, and monitor earnings</p>
<!-- Status Summary -->
@@ -13,8 +13,8 @@
<div class="stats-card success">
<h5 class="card-title">Active Nodes</h5>
<div class="d-flex justify-content-between align-items-end">
<h2 class="mb-0" id="active-nodes-count">{{ farmer_stats.online_nodes }}</h2>
<small class="text-muted">of {{ farmer_stats.total_nodes }} total</small>
<h2 class="mb-0" id="active-nodes-count">{{ resource_provider_stats.online_nodes }}</h2>
<small class="text-muted">of {{ resource_provider_stats.total_nodes }} total</small>
</div>
</div>
</div>
@@ -22,8 +22,8 @@
<div class="stats-card primary">
<h5 class="card-title">Allocated Slices</h5>
<div class="d-flex justify-content-between align-items-end">
<h2 class="mb-0" id="active-slices-count">{{ farmer_stats.allocated_base_slices }}</h2>
<small class="text-muted">of {{ farmer_stats.total_base_slices }} total</small>
<h2 class="mb-0" id="active-slices-count">{{ resource_provider_stats.allocated_base_slices }}</h2>
<small class="text-muted">of {{ resource_provider_stats.total_base_slices }} total</small>
</div>
</div>
</div>
@@ -31,7 +31,7 @@
<div class="stats-card warning">
<h5 class="card-title">Monthly Earnings</h5>
<div class="d-flex justify-content-between align-items-end">
<h2 class="mb-0" id="monthly-earnings">{{ farmer_stats.monthly_earnings }}</h2>
<h2 class="mb-0" id="monthly-earnings">{{ resource_provider_stats.monthly_earnings }}</h2>
<small class="text-muted">$/month</small>
</div>
</div>
@@ -187,7 +187,7 @@
<div class="stats-card primary">
<h6 class="card-title">Total Base Slices</h6>
<div class="d-flex justify-content-between align-items-end">
<h3 class="mb-0" id="total-base-slices">{{ farmer_stats.total_base_slices }}</h3>
<h3 class="mb-0" id="total-base-slices">{{ resource_provider_stats.total_base_slices }}</h3>
<small class="text-muted">Available</small>
</div>
</div>
@@ -196,7 +196,7 @@
<div class="stats-card success">
<h6 class="card-title">Allocated Slices</h6>
<div class="d-flex justify-content-between align-items-end">
<h3 class="mb-0" id="allocated-base-slices">{{ farmer_stats.allocated_base_slices }}</h3>
<h3 class="mb-0" id="allocated-base-slices">{{ resource_provider_stats.allocated_base_slices }}</h3>
<small class="text-muted">Rented</small>
</div>
</div>
@@ -205,7 +205,7 @@
<div class="stats-card info">
<h6 class="card-title">Available Slices</h6>
<div class="d-flex justify-content-between align-items-end">
<h3 class="mb-0" id="available-base-slices">{{ farmer_stats.available_base_slices }}</h3>
<h3 class="mb-0" id="available-base-slices">{{ resource_provider_stats.available_base_slices }}</h3>
<small class="text-muted">For Rent</small>
</div>
</div>
@@ -214,7 +214,7 @@
<div class="stats-card warning">
<h6 class="card-title">Utilization</h6>
<div class="d-flex justify-content-between align-items-end">
<h3 class="mb-0" id="slice-utilization">{{ farmer_stats.slice_utilization_percentage }}%</h3>
<h3 class="mb-0" id="slice-utilization">{{ resource_provider_stats.slice_utilization_percentage }}%</h3>
<small class="text-muted">Capacity</small>
</div>
</div>
@@ -238,8 +238,8 @@
</tr>
</thead>
<tbody id="node-slices-table">
{% if farmer_nodes %}
{% for node in farmer_nodes %}
{% if resource_provider_nodes %}
{% for node in resource_provider_nodes %}
<tr>
<td>
<div class="d-flex align-items-center">
@@ -396,8 +396,8 @@
{{ super() }}
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.1/dist/chart.min.js"></script>
<!-- Load farmer dashboard JavaScript -->
<script src="/static/js/dashboard-farmer.js"></script>
<!-- Load resource provider dashboard JavaScript -->
<script src="/static/js/dashboard-resource_provider.js"></script>
<style>
/* Ensure charts have consistent sizes */
@@ -526,7 +526,7 @@
<div class="modal-body">
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
<strong>ThreeFold Grid Integration:</strong> Enter the Grid Node IDs of your physical 3Nodes. Node specifications and location will be automatically fetched from the ThreeFold Grid.
<strong>Mycelium Grid Integration:</strong> Enter the Grid Node IDs of your physical Mycelium Nodes. Node specifications and location will be automatically fetched from the ThreeFold Grid.
</div>
<!-- Node Input Mode Selection -->

View File

@@ -1,11 +1,11 @@
{% extends "dashboard/layout.html" %}
{% block title %}ThreeFold Dashboard - Settings{% endblock %}
{% block title %}Mycelium Dashboard - Settings{% endblock %}
{% block dashboard_content %}
<div class="my-4">
<h1>Account Settings</h1>
<p class="lead">Manage your ThreeFold account preferences and security settings</p>
<p class="lead">Manage your Mycelium account preferences and security settings</p>
<div class="row mt-5">
<div class="col-md-3">
@@ -415,7 +415,7 @@
<div class="col-sm-9">
<select class="form-select" id="displayCurrency" name="display_currency">
<option value="USD" {% if user_display_currency == "USD" %}selected{% endif %}>USD - US Dollar</option>
<option value="TFC" {% if user_display_currency == "TFC" %}selected{% endif %}>TFC - ThreeFold Credits</option>
<option value="MC" {% if user_display_currency == "MC" %}selected{% endif %}>MC - Mycelium Credit</option>
<option value="EUR" {% if user_display_currency == "EUR" %}selected{% endif %}>EUR - Euro</option>
<option value="CAD" {% if user_display_currency == "CAD" %}selected{% endif %}>CAD - Canadian Dollar</option>
</select>
@@ -425,7 +425,7 @@
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
<strong>Note:</strong> All transactions are processed in USD Credits. Display currency is used for convenience only and prices are converted in real-time.
<strong>Note:</strong> All transactions are processed in MC Credits. Display currency is used for convenience only and prices are converted in real-time.
</div>
<div class="d-flex justify-content-end">

View File

@@ -55,7 +55,7 @@
<div class="display-4 mb-3 text-success">
<i class="bi bi-hdd-rack"></i>
</div>
<h5>As a Farmer</h5>
<h5>As a Resource Provider</h5>
<p>Manage nodes, configure slices, set pricing</p>
</div>
</div>

View File

@@ -1,15 +1,15 @@
{% extends "docs/layout.html" %}
{% block title %}Application Solutions - Project Mycelium{% endblock %}
{% block title %}Agentic Apps - Project Mycelium{% endblock %}
{% block docs_content %}
<div class="my-4">
<h1>Application Solutions</h1>
<h1>Agentic Apps</h1>
<p class="lead">Discover self-healing applications that maintain sovereignty while offering convenience.</p>
<div class="doc-section">
<h2>Overview</h2>
<p>ThreeFold Application Solutions represent a new approach to application deployment that balances ease of use with digital sovereignty. These pre-configured, self-healing applications are designed to run on the ThreeFold Grid while allowing users to maintain complete control over their infrastructure and data.</p>
<p>ThreeFold Agentic Apps represent a new approach to application deployment that balances ease of use with digital sovereignty. These pre-configured, self-healing applications are designed to run on the ThreeFold Grid while allowing users to maintain complete control over their infrastructure and data.</p>
<div class="alert alert-info">
<div class="d-flex">
@@ -18,7 +18,7 @@
</div>
<div>
<h5 class="alert-heading">The ThreeFold Difference</h5>
<p class="mb-0">Unlike traditional SaaS offerings that require surrendering control of your data to the provider, ThreeFold Application Solutions run on compute resources that remain under your sovereign control, while the solution provider manages only the application layer.</p>
<p class="mb-0">Unlike traditional SaaS offerings that require surrendering control of your data to the provider, ThreeFold Agentic Apps run on compute resources that remain under your sovereign control, while the application provider manages only the application layer.</p>
</div>
</div>
</div>
@@ -26,7 +26,7 @@
<div class="doc-section">
<h2>Application Categories</h2>
<p>The Project Mycelium offers a diverse range of application solutions across multiple categories:</p>
<p>The Project Mycelium offers a diverse range of applications across multiple categories:</p>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 mb-4">
<div class="col">
@@ -88,11 +88,11 @@
<div class="doc-section">
<h2>How It Works</h2>
<p>Application Solutions on the Project Mycelium function through a unique partnership model between users and solution providers:</p>
<p>Agentic Apps on the Project Mycelium function through a unique partnership model between users and application providers:</p>
<div class="row align-items-center mb-4">
<div class="col-md-6">
<img src="/static/images/docs/application-flow.svg" alt="Application Solution Flow Diagram" class="img-fluid rounded shadow">
<img src="/static/images/docs/application-flow.svg" alt="Agentic Apps Flow Diagram" class="img-fluid rounded shadow">
</div>
<div class="col-md-6">
<ol class="list-group list-group-numbered mb-0">
@@ -105,7 +105,7 @@
<li class="list-group-item d-flex">
<div>
<strong>Solution Deployment</strong>
<p class="mb-0 text-muted">The solution provider deploys their application on your sovereign compute resources.</p>
<p class="mb-0 text-muted">The application provider deploys their application on your sovereign compute resources.</p>
</div>
</li>
<li class="list-group-item d-flex">
@@ -117,7 +117,7 @@
<li class="list-group-item d-flex">
<div>
<strong>Payment Model</strong>
<p class="mb-0 text-muted">You pay the solution provider in USD Credits based on the agreed-upon pricing model.</p>
<p class="mb-0 text-muted">You pay the application provider in USD Credits based on the agreed-upon pricing model.</p>
</div>
</li>
</ol>
@@ -131,7 +131,7 @@
</div>
<div>
<h5 class="alert-heading">Sovereignty Maintained</h5>
<p class="mb-0">Your data and applications remain on infrastructure under your control. The solution provider cannot access your data without your explicit permission, and you can revoke their management access at any time.</p>
<p class="mb-0">Your data and applications remain on infrastructure under your control. The application provider cannot access your data without your explicit permission, and you can revoke their management access at any time.</p>
</div>
</div>
</div>
@@ -192,7 +192,7 @@
</div>
<div class="doc-section">
<h2>Solution Provider Certification</h2>
<h2>Application Provider Certification</h2>
<p>Solution providers in the Project Mycelium undergo a certification process to ensure quality and reliability:</p>
<div class="table-responsive">
@@ -258,7 +258,7 @@
<i class="bi bi-shop fs-1 text-primary"></i>
</div>
<h5 class="card-title">1. Select Application</h5>
<p class="card-text">Browse the marketplace and select your desired application solution.</p>
<p class="card-text">Browse the marketplace and select your desired application.</p>
</div>
</div>
</div>

View File

@@ -33,7 +33,7 @@
<div class="card h-100">
<div class="card-body">
<h5 class="card-title"><i class="bi bi-hdd-rack me-2 text-primary"></i>Hardware Certification</h5>
<p class="card-text">Verification of 3Nodes and physical infrastructure components to ensure they meet performance, reliability, and security standards.</p>
<p class="card-text">Verification of Mycelium Nodes and physical infrastructure components to ensure they meet performance, reliability, and security standards.</p>
</div>
</div>
</div>

View File

@@ -9,7 +9,7 @@
<div class="doc-section">
<h2>Overview</h2>
<p>Compute Resources, often referred to as "Slices," are the fundamental units of computing capacity in the ThreeFold ecosystem. They represent virtualized portions of underlying physical hardware (3Nodes) that can be used to deploy workloads, applications, and services.</p>
<p>Compute Resources, often referred to as "Slices," are the fundamental units of computing capacity in the Mycelium ecosystem. They represent virtualized portions of underlying physical hardware (Mycelium Nodes) that can be used to deploy workloads, applications, and services.</p>
<div class="alert alert-info">
<div class="d-flex">

View File

@@ -56,7 +56,7 @@
<h5 class="mb-3"><i class="bi bi-shop me-2 text-primary"></i>Discovering Resources</h5>
<p>Once your account and wallet are set up, you can start exploring the marketplace:</p>
<ul>
<li><strong>Browse Categories:</strong> Explore different sections such as compute resources, 3Nodes, applications, and services</li>
<li><strong>Browse Categories:</strong> Explore different sections such as compute resources, Mycelium Nodes, applications, and services</li>
<li><strong>Apply Filters:</strong> Narrow down options based on specifications, location, pricing, and other criteria</li>
<li><strong>Compare Options:</strong> Evaluate different offerings to find the best fit for your needs</li>
<li><strong>Read Reviews:</strong> Check ratings and feedback from other users</li>

View File

@@ -46,10 +46,10 @@
<td><a href="/docs/compute" class="btn btn-sm btn-outline-primary">Learn More</a></td>
</tr>
<tr>
<td><strong>3Nodes</strong></td>
<td><strong>Mycelium Nodes</strong></td>
<td>Physical computing hardware marketplace</td>
<td>Credits transferred based on hardware value</td>
<td><a href="/docs/3nodes" class="btn btn-sm btn-outline-primary">Learn More</a></td>
<td><a href="/docs/mycelium_nodes" class="btn btn-sm btn-outline-primary">Learn More</a></td>
</tr>
<tr>
<td><strong>Mycelium Gateways</strong></td>
@@ -58,9 +58,9 @@
<td><a href="/docs/gateways" class="btn btn-sm btn-outline-primary">Learn More</a></td>
</tr>
<tr>
<td><strong>Application Solutions</strong></td>
<td><strong>Agentic Apps</strong></td>
<td>Pre-configured, self-healing applications</td>
<td>Credits paid to solution providers for application management while users maintain sovereignty</td>
<td>Credits paid to application providers for application management while users maintain sovereignty</td>
<td><a href="/docs/applications" class="btn btn-sm btn-outline-primary">Learn More</a></td>
</tr>
<tr>

View File

@@ -40,9 +40,9 @@
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if active_section == '3nodes' %}active{% endif %}" href="/docs/3nodes">
<a class="nav-link {% if active_section == 'mycelium_nodes' %}active{% endif %}" href="/docs/mycelium_nodes">
<i class="bi bi-hdd-rack me-1"></i>
3Nodes
Mycelium Nodes
</a>
</li>
<li class="nav-item">
@@ -54,7 +54,7 @@
<li class="nav-item">
<a class="nav-link {% if active_section == 'applications' %}active{% endif %}" href="/docs/applications">
<i class="bi bi-app me-1"></i>
Application Solutions
Agentic Apps
</a>
</li>
<li class="nav-item">

View File

@@ -1,22 +1,22 @@
{% extends "docs/layout.html" %}
{% block title %}3Nodes Documentation - Project Mycelium{% endblock %}
{% block title %}Mycelium Nodes Documentation - Project Mycelium{% endblock %}
{% block docs_content %}
<div class="my-4">
<h1>3Nodes</h1>
<h1>Mycelium Nodes</h1>
<p class="lead">Physical computing hardware that can be bought and sold within the Project Mycelium using USD Credits.</p>
<div class="doc-section">
<h2>Overview</h2>
<p>3Nodes are physical computing hardware (servers, computers) that can be bought and sold within the TF Marketplace using USD Credits. Users can purchase 3Nodes in two distinct ways: either hosted by the supplier or physically transferred to the buyer.</p>
<p>Mycelium Nodes are physical computing hardware (servers, computers) that can be bought and sold within the TF Marketplace using USD Credits. Users can purchase Mycelium Nodes in two distinct ways: either hosted by the supplier or physically transferred to the buyer.</p>
<div class="row mt-4">
<div class="col-md-6">
<div class="card mb-4 h-100">
<div class="card-body">
<h5 class="card-title"><i class="bi bi-building me-2 text-primary"></i>Supplier-Hosted</h5>
<p class="card-text">Buy the 3Node with upfront Credits payment but have it hosted in the supplier's facility. You maintain ownership while the supplier handles physical maintenance.</p>
<p class="card-text">Buy the Mycelium Node with upfront Credits payment but have it hosted in the supplier's facility. You maintain ownership while the supplier handles physical maintenance.</p>
<p><strong>Ideal for:</strong> Buyers without suitable hosting facilities</p>
</div>
</div>
@@ -25,7 +25,7 @@
<div class="card mb-4 h-100">
<div class="card-body">
<h5 class="card-title"><i class="bi bi-truck me-2 text-primary"></i>Physical Transfer</h5>
<p class="card-text">Buy the 3Node with upfront Credits payment and have it physically shipped to your location. Complete physical control and responsibility for the hardware.</p>
<p class="card-text">Buy the Mycelium Node with upfront Credits payment and have it physically shipped to your location. Complete physical control and responsibility for the hardware.</p>
<p><strong>Ideal for:</strong> Buyers with their own hosting capabilities</p>
</div>
</div>
@@ -120,7 +120,7 @@
<div class="card-body">
<h5>Hardware Discovery</h5>
<ul>
<li>Browse available 3Nodes with filtering options</li>
<li>Browse available Mycelium Nodes with filtering options</li>
<li>Compare specifications and pricing</li>
<li>Review seller ratings and history</li>
<li>Ask questions about specific hardware</li>
@@ -128,7 +128,7 @@
<h5 class="mt-4">Purchase Process</h5>
<ul>
<li>Select desired 3Node and purchase option (hosted or transferred)</li>
<li>Select desired Mycelium Node and purchase option (hosted or transferred)</li>
<li>Pay upfront using Credits for either option</li>
<li>For physical transfer: arrange shipping and delivery</li>
<li>For hosted option: receive access credentials</li>
@@ -140,7 +140,7 @@
<div class="doc-section">
<h2>Hardware Specifications</h2>
<p>3Nodes can include various types of computing hardware:</p>
<p>Mycelium Nodes can include various types of computing hardware:</p>
<div class="row row-cols-1 row-cols-md-2 g-4 mb-4">
<div class="col">
@@ -341,7 +341,7 @@
<div class="card h-100">
<div class="card-body">
<h5 class="card-title"><i class="bi bi-people me-2 text-success"></i>Community Building</h5>
<p class="card-text">Connects hardware providers with potential farmers</p>
<p class="card-text">Connects hardware providers with potential resource providers</p>
</div>
</div>
</div>
@@ -350,10 +350,10 @@
<div class="card bg-light mt-5">
<div class="card-body">
<h2><i class="bi bi-question-circle me-2"></i>Ready to Buy or Sell 3Nodes?</h2>
<p class="lead">Visit the Project Mycelium 3Nodes section to start exploring options.</p>
<h2><i class="bi bi-question-circle me-2"></i>Ready to Buy or Sell Mycelium Nodes?</h2>
<p class="lead">Visit the Project Mycelium Mycelium Nodes section to start exploring options.</p>
<div class="mt-3">
<a href="/marketplace/3nodes" class="btn btn-primary me-2">Explore 3Nodes Marketplace</a>
<a href="/marketplace/mycelium_nodes" class="btn btn-primary me-2">Explore Mycelium Nodes Marketplace</a>
<a href="/docs" class="btn btn-outline-secondary">Back to Documentation</a>
</div>
</div>

View File

@@ -63,7 +63,7 @@
<h5 class="card-title"><i class="bi bi-hdd-rack me-2 text-primary"></i>Deployment</h5>
<p class="card-text">Setup, configuration, and deployment of applications and infrastructure.</p>
<ul class="small text-muted">
<li>3Node configuration</li>
<li>Mycelium Node configuration</li>
<li>Application deployment</li>
<li>Network setup</li>
<li>Migration services</li>

View File

@@ -9,7 +9,7 @@
<div class="doc-section">
<h2>Overview</h2>
<p>Slices represent the core unit of compute capacity within the ThreeFold Grid. They are standardized divisions of physical computing hardware (3Nodes) that can be allocated and managed as independent virtual resources. Slices form the foundation of all deployments on the ThreeFold Grid and serve as the primary unit of exchange for computational resources.</p>
<p>Slices represent the core unit of compute capacity within the Mycelium Grid. They are standardized divisions of physical computing hardware (Mycelium Nodes) that can be allocated and managed as independent virtual resources. Slices form the foundation of all deployments on the Mycelium Grid and serve as the primary unit of exchange for computational resources.</p>
<div class="alert alert-info">
<div class="d-flex">
@@ -18,7 +18,7 @@
</div>
<div>
<h5 class="alert-heading">Key Concept</h5>
<p class="mb-0">Slices abstract away the underlying physical infrastructure, providing a consistent and standardized way to measure, allocate, and exchange computing capacity across the entire ThreeFold Grid.</p>
<p class="mb-0">Slices abstract away the underlying physical infrastructure, providing a consistent and standardized way to measure, allocate, and exchange computing capacity across the entire Mycelium Grid.</p>
</div>
</div>
</div>
@@ -139,7 +139,7 @@
<div class="doc-section">
<h2>Slice Types</h2>
<p>The ThreeFold Grid offers different types of slices optimized for specific workloads:</p>
<p>The Mycelium Grid offers different types of slices optimized for specific workloads:</p>
<div class="row">
<div class="col-lg-4">

View File

@@ -34,7 +34,7 @@
<td>Credits charged based on resource utilization</td>
</tr>
<tr>
<td><strong>3Nodes</strong></td>
<td><strong>Mycelium Nodes</strong></td>
<td>Physical computing hardware marketplace</td>
<td>Credits transferred based on hardware value</td>
</tr>
@@ -44,9 +44,9 @@
<td>Credits paid based on bandwidth consumption</td>
</tr>
<tr>
<td><strong>Application Solutions</strong></td>
<td><strong>Agentic Apps</strong></td>
<td>Pre-configured, self-healing applications</td>
<td>Users provide slices to solution providers while maintaining sovereignty</td>
<td>Users provide slices to application providers while maintaining sovereignty</td>
</tr>
<tr>
<td><strong>Human Energy Services</strong></td>

View File

@@ -78,7 +78,7 @@
<div class="col-md-6">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title"><i class="bi bi-hdd-rack me-2"></i>Farmers</h5>
<h5 class="card-title"><i class="bi bi-hdd-rack me-2"></i>Resource Providers</h5>
<p class="card-text">Contribute compute capacity to the ThreeFold Grid and earn through the platform's ecosystem.</p>
<a href="/docs" class="btn btn-outline-primary">Learn More</a>
</div>

View File

@@ -47,9 +47,9 @@
<div class="card h-100">
<div class="card-body text-center">
<i class="bi bi-hdd-rack text-primary display-4 mb-3"></i>
<h3>3Nodes</h3>
<h3>Mycelium Nodes</h3>
<p>Buy and sell physical computing hardware to support the Grid.</p>
<a href="/marketplace/3nodes" class="btn btn-sm btn-outline-primary">Explore</a>
<a href="/marketplace/mycelium_nodes" class="btn btn-sm btn-outline-primary">Explore</a>
</div>
</div>
</div>
@@ -101,7 +101,7 @@
</h2>
<div id="faqCollapse2" class="accordion-collapse collapse" aria-labelledby="faqHeading2" data-bs-parent="#faqAccordion">
<div class="accordion-body">
The Marketplace offers various categories including: Compute Resources (slices) for cloud computing, physical 3Nodes for hardware, Mycelium Gateways for connectivity, self-healing Application Solutions that maintain your data sovereignty, and Human Energy Services where you can access professional expertise.
The Marketplace offers various categories including: Compute Resources (slices) for cloud computing, physical Mycelium Nodes for hardware, Mycelium Gateways for connectivity, self-healing Agentic Apps that maintain your data sovereignty, and Human Energy Services where you can access professional expertise.
</div>
</div>
</div>
@@ -118,9 +118,9 @@
<p>Unlike traditional SaaS offerings where you surrender control to the provider, ThreeFold's model maintains your sovereignty in two ways:</p>
<ol class="mb-3">
<li><strong>Direct Management:</strong> Manage your own compute slices directly for complete control</li>
<li><strong>Sovereign Allocation:</strong> Allocate your sovereign compute slices to solution providers who deploy and manage applications on your infrastructure</li>
<li><strong>Sovereign Allocation:</strong> Allocate your sovereign compute slices to application providers who deploy and manage applications on your infrastructure</li>
</ol>
<p>With application solutions, you maintain control because:</p>
<p>With applications, you maintain control because:</p>
<ul>
<li>You still own and control the underlying resources</li>
<li>Your data remains on infrastructure under your control</li>

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Solution Providers Terms and Conditions - Project Mycelium{% endblock %}
{% block title %}Application Providers Terms and Conditions - Project Mycelium{% endblock %}
{% block content %}
<div class="container my-5">
@@ -9,50 +9,50 @@
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/terms">Terms and Conditions</a></li>
<li class="breadcrumb-item active" aria-current="page">Solution Providers</li>
<li class="breadcrumb-item active" aria-current="page">Application Providers</li>
</ol>
</nav>
<div class="d-flex align-items-center mb-4">
<i class="bi bi-box-seam fs-1 me-3 text-info"></i>
<h1>Solution Providers Terms and Conditions</h1>
<h1>Application Providers Terms and Conditions</h1>
</div>
<p class="lead mb-4">Last updated: May 22, 2025</p>
<div class="alert alert-info">
<p class="mb-0">These terms specifically apply to Solution Providers on the Project Mycelium who develop and deploy applications and solutions on the ThreeFold Grid.</p>
<p class="mb-0">These terms specifically apply to Application Providers on the Project Mycelium who develop and deploy applications on the ThreeFold Grid.</p>
</div>
<h2>1. Definition of a Solution Provider</h2>
<p>A "Solution Provider" refers to any individual or entity that develops, deploys, and maintains software applications, platforms, or digital solutions on the Project Mycelium. Solution Providers create the applications and services that run on the ThreeFold Grid's infrastructure.</p>
<h2>1. Definition of an Application Provider</h2>
<p>An "Application Provider" refers to any individual or entity that develops, deploys, and maintains software applications, platforms, or digital applications on the Project Mycelium. Application Providers create the applications and services that run on the ThreeFold Grid's infrastructure.</p>
<h2>2. Solution Provider Responsibilities</h2>
<p>As a Solution Provider on the Project Mycelium, you agree to:</p>
<h2>2. Application Provider Responsibilities</h2>
<p>As an Application Provider on the Project Mycelium, you agree to:</p>
<ul>
<li>Provide accurate descriptions of your applications and solutions</li>
<li>Maintain your deployed solutions with regular updates and security patches</li>
<li>Ensure your solutions comply with best practices for security and data protection</li>
<li>Provide clear documentation for users of your solutions</li>
<li>Provide accurate descriptions of your applications and applications</li>
<li>Maintain your deployed applications with regular updates and security patches</li>
<li>Ensure your applications comply with best practices for security and data protection</li>
<li>Provide clear documentation for users of your applications</li>
<li>Respond promptly to technical issues and bug reports</li>
<li>Set fair and transparent pricing for your solutions</li>
<li>Set fair and transparent pricing for your applications</li>
</ul>
<h2>3. Reputation System and Staking</h2>
<p>The Project Mycelium employs a reputation system that affects solution provider visibility and benefits:</p>
<p>The Project Mycelium employs a reputation system that affects application provider visibility and benefits:</p>
<ul>
<li>Your reputation score is calculated based on user ratings, solution performance, security metrics, and staked Credits</li>
<li>Your reputation score is calculated based on user ratings, application performance, security metrics, and staked Credits</li>
<li>Higher reputation scores may result in improved visibility in marketplace listings</li>
<li>Staking Credits increases your reputation score and may qualify you for reduced platform fees</li>
<li>Security vulnerabilities or performance issues may result in reputation penalties</li>
</ul>
<h2>4. Intellectual Property Rights</h2>
<p>Regarding the intellectual property of your solutions:</p>
<p>Regarding the intellectual property of your applications:</p>
<ul>
<li>You retain all intellectual property rights to your applications and solutions</li>
<li>You grant ThreeFold a limited license to display, promote, and facilitate access to your solutions on the marketplace</li>
<li>You are responsible for ensuring your solutions do not infringe on third-party intellectual property rights</li>
<li>You may choose the appropriate licensing model for your solutions (open source, proprietary, etc.)</li>
<li>You retain all intellectual property rights to your applications and applications</li>
<li>You grant ThreeFold a limited license to display, promote, and facilitate access to your applications on the marketplace</li>
<li>You are responsible for ensuring your applications do not infringe on third-party intellectual property rights</li>
<li>You may choose the appropriate licensing model for your applications (open source, proprietary, etc.)</li>
</ul>
<h2>5. Application Deployment and Credits</h2>
@@ -60,20 +60,20 @@
<ul>
<li>All transactions on the marketplace use USD Credits as the medium of exchange</li>
<li>Credits have a fixed value of 1.0 USD per Credit</li>
<li>Solution Providers receive Credits from users who utilize their applications</li>
<li>Application Providers receive Credits from users who utilize their applications</li>
<li>Earned Credits can be exchanged for fiat currencies or other supported tokens through the available liquidity pools</li>
<li>Platform fees may apply to transactions as detailed in your Solution Provider dashboard</li>
<li>You are responsible for the resource utilization costs of your deployed solutions</li>
<li>Platform fees may apply to transactions as detailed in your Application Provider dashboard</li>
<li>You are responsible for the resource utilization costs of your deployed applications</li>
</ul>
<h2>6. Solution Listings and Distribution</h2>
<p>Regarding your solution listings on the marketplace:</p>
<h2>6. Application Listings and Distribution</h2>
<p>Regarding your application listings on the marketplace:</p>
<ul>
<li>All solution descriptions must comply with the Marketplace Content Guidelines</li>
<li>False or misleading claims about solutions are prohibited</li>
<li>All application descriptions must comply with the Marketplace Content Guidelines</li>
<li>False or misleading claims about applications are prohibited</li>
<li>ThreeFold reserves the right to remove or request modifications to listings that violate guidelines</li>
<li>You may utilize marketplace promotional tools to increase the visibility of your solutions</li>
<li>You may offer free trials, freemium models, or paid solutions according to your business model</li>
<li>You may utilize marketplace promotional tools to increase the visibility of your applications</li>
<li>You may offer free trials, freemium models, or paid applications according to your business model</li>
</ul>
<h2>7. API Usage and Integration</h2>
@@ -91,12 +91,12 @@
<li>Security vulnerabilities that put user data at risk</li>
<li>Consistent performance issues that significantly impact user experience</li>
<li>Violations of content guidelines or terms of service</li>
<li>Fraudulent activities or misrepresentation of solution capabilities</li>
<li>Fraudulent activities or misrepresentation of application capabilities</li>
<li>Slashing penalties may include reputation reduction, temporary suspension of listings, or in severe cases, permanent removal from the marketplace</li>
</ul>
<h2>9. Updates and Maintenance</h2>
<p>Regarding the ongoing maintenance of your solutions:</p>
<p>Regarding the ongoing maintenance of your applications:</p>
<ul>
<li>You are responsible for providing regular updates and maintenance</li>
<li>Critical security updates should be prioritized and deployed promptly</li>
@@ -104,18 +104,18 @@
<li>Scheduled maintenance should be announced in advance when possible</li>
</ul>
<h2>10. Termination of Solution Provider Status</h2>
<p>You may cease being a Solution Provider by:</p>
<h2>10. Termination of Application Provider Status</h2>
<p>You may cease being an Application Provider by:</p>
<ul>
<li>Removing all solution listings from the marketplace</li>
<li>Removing all application listings from the marketplace</li>
<li>Providing a reasonable migration path or notice to existing users</li>
<li>Completing all outstanding obligations to users</li>
<li>Providing notice through your dashboard at least 30 days prior to solution removal</li>
<li>Providing notice through your dashboard at least 30 days prior to application removal</li>
</ul>
<div class="alert alert-warning mt-5">
<h5 class="alert-heading">Important Note</h5>
<p class="mb-0">These Solution Provider-specific terms are in addition to the <a href="/terms">General Terms and Conditions</a> that apply to all users of the Project Mycelium. Please ensure you have reviewed both documents.</p>
<p class="mb-0">These Application Provider-specific terms are in addition to the <a href="/terms">General Terms and Conditions</a> that apply to all users of the Project Mycelium. Please ensure you have reviewed both documents.</p>
</div>
<div class="text-center mt-5 mb-3">

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Farmers Terms and Conditions - Project Mycelium{% endblock %}
{% block title %}Resource Providers Terms and Conditions - Project Mycelium{% endblock %}
{% block content %}
<div class="container my-5">
@@ -9,25 +9,25 @@
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/terms">Terms and Conditions</a></li>
<li class="breadcrumb-item active" aria-current="page">Farmers</li>
<li class="breadcrumb-item active" aria-current="page">Resource Providers</li>
</ol>
</nav>
<div class="d-flex align-items-center mb-4">
<i class="bi bi-hdd-rack fs-1 me-3 text-primary"></i>
<h1>Farmers Terms and Conditions</h1>
<h1>Resource Providers Terms and Conditions</h1>
</div>
<p class="lead mb-4">Last updated: May 22, 2025</p>
<div class="alert alert-info">
<p class="mb-0">These terms specifically apply to Farmers (Resource Providers) on the Project Mycelium who contribute capacity to the ThreeFold Grid.</p>
<p class="mb-0">These terms specifically apply to Resource Providers (Resource Providers) on the Project Mycelium who contribute capacity to the Mycelium Grid.</p>
</div>
<h2>1. Definition of a Farmer</h2>
<p>A "Farmer" refers to any individual or entity that connects hardware resources to the ThreeFold Grid, including but not limited to compute nodes (3Nodes), storage capacity, network infrastructure, or other compatible devices that contribute to the ThreeFold Grid's capacity.</p>
<h2>1. Definition of a Resource Provider</h2>
<p>A "Resource Provider" refers to any individual or entity that connects hardware resources to the Mycelium Grid, including but not limited to compute nodes (Mycelium Nodes), storage capacity, network infrastructure, or other compatible devices that contribute to the Mycelium Grid's capacity.</p>
<h2>2. Farmer Responsibilities</h2>
<p>As a Farmer on the Project Mycelium, you agree to:</p>
<h2>2. Resource Provider Responsibilities</h2>
<p>As a Resource Provider on the Project Mycelium, you agree to:</p>
<ul>
<li>Maintain your connected hardware in good working condition with adequate internet connectivity</li>
<li>Ensure your hardware meets the minimum technical requirements specified in the Farming documentation</li>
@@ -37,7 +37,7 @@
</ul>
<h2>3. Reputation System and Staking</h2>
<p>The Project Mycelium employs a reputation system that affects farmer visibility and rewards:</p>
<p>The Project Mycelium employs a reputation system that affects resource provider visibility and rewards:</p>
<ul>
<li>Your reputation score is calculated based on multiple factors including uptime, staked Credits, and performance metrics</li>
<li>Staking Credits increases your reputation score and may qualify you for additional benefits</li>
@@ -48,16 +48,16 @@
<h2>4. Rewards and Credits</h2>
<p>Compensation for resource contribution is governed by the following principles:</p>
<ul>
<li>Farmers receive USD Credits based on the resources utilized from their contributed capacity</li>
<li>Resource Providers receive USD Credits based on the resources utilized from their contributed capacity</li>
<li>Credits have a fixed value of 1.0 USD per Credit</li>
<li>Earned Credits can be exchanged for fiat currencies or other supported tokens through the available liquidity pools</li>
<li>Payment schedules and minimum thresholds are detailed in the Farmer dashboard</li>
<li>Payment schedules and minimum thresholds are detailed in the Resource Provider dashboard</li>
</ul>
<h2>5. Hardware and Capacity</h2>
<p>Regarding the hardware and capacity you provide:</p>
<ul>
<li>You retain ownership of all hardware connected to the ThreeFold Grid</li>
<li>You retain ownership of all hardware connected to the Mycelium Grid</li>
<li>You are responsible for the electricity, internet connectivity, and physical security of your hardware</li>
<li>You have the right to disconnect your hardware at any time, subject to any active resource reservations</li>
<li>ThreeFold does not guarantee that your capacity will be utilized by users</li>
@@ -72,16 +72,16 @@
<li>Slashing penalties may include reputation reduction, temporary suspension, or in severe cases, permanent removal from the marketplace</li>
</ul>
<h2>7. Termination of Farmer Status</h2>
<p>You may cease being a Farmer by:</p>
<h2>7. Termination of Resource Provider Status</h2>
<p>You may cease being a Resource Provider by:</p>
<ul>
<li>Disconnecting your hardware from the ThreeFold Grid</li>
<li>Disconnecting your hardware from the Mycelium Grid</li>
<li>Providing notice through your dashboard at least 30 days prior to complete disconnection</li>
<li>Ensuring all active resource reservations have been properly concluded or migrated</li>
</ul>
<h2>8. Liability Limitation</h2>
<p>As a Farmer, you acknowledge that:</p>
<p>As a Resource Provider, you acknowledge that:</p>
<ul>
<li>ThreeFold is not responsible for any damage to your hardware resulting from normal operation</li>
<li>ThreeFold does not guarantee minimum income or utilization rates for your contributed capacity</li>
@@ -90,7 +90,7 @@
<div class="alert alert-warning mt-5">
<h5 class="alert-heading">Important Note</h5>
<p class="mb-0">These Farmer-specific terms are in addition to the <a href="/terms">General Terms and Conditions</a> that apply to all users of the Project Mycelium. Please ensure you have reviewed both documents.</p>
<p class="mb-0">These Resource Provider-specific terms are in addition to the <a href="/terms">General Terms and Conditions</a> that apply to all users of the Project Mycelium. Please ensure you have reviewed both documents.</p>
</div>
<div class="text-center mt-5 mb-3">

View File

@@ -18,9 +18,9 @@
<div class="card h-100 shadow-sm">
<div class="card-body text-center">
<i class="bi bi-hdd-rack fs-1 mb-3 text-primary"></i>
<h5 class="card-title">Farmers</h5>
<h5 class="card-title">Resource Providers</h5>
<p class="card-text">Resource Providers contributing capacity to the ThreeFold Grid</p>
<a href="/terms/farmers" class="btn btn-outline-primary mt-3">View Terms</a>
<a href="/terms/resource providers" class="btn btn-outline-primary mt-3">View Terms</a>
</div>
</div>
</div>
@@ -67,10 +67,10 @@
<p>The Project Mycelium is a platform that facilitates the exchange of value through the USD Credits system. It connects providers and users, enabling the discovery, acquisition, and management of various resources and services including but not limited to:</p>
<ul>
<li>Compute Resources (Slices)</li>
<li>3Nodes</li>
<li>Mycelium Nodes</li>
<li>Mycelium Gateways</li>
<li>Bandwidth Providers</li>
<li>Application Solutions</li>
<li>Agentic Apps</li>
<li>Human Energy Services</li>
</ul>

View File

@@ -1,21 +1,21 @@
{% extends "marketplace/layout.html" %}
{% block title %}Project Mycelium - Application Solutions{% endblock %}
{% block title %}Project Mycelium - Agentic Apps{% endblock %}
{% block marketplace_content %}
<div class="my-4">
<h1>Application Solutions</h1>
<h1>Agentic Apps</h1>
<p class="lead">Discover self-healing applications that maintain sovereignty while offering convenience.</p>
<!-- Application Solutions Introduction -->
<!-- Agentic Apps Introduction -->
<div class="alert alert-info mb-4">
<div class="d-flex">
<div class="me-3">
<i class="bi bi-info-circle-fill fs-3"></i>
</div>
<div>
<h5 class="alert-heading">How Application Solutions Work</h5>
<p>ThreeFold Application Solutions use a unique model: You provide the compute resources (Slices), while solution providers manage the applications. This ensures you maintain sovereignty over your infrastructure while benefiting from professional management.</p>
<h5 class="alert-heading">How Agentic Apps Work</h5>
<p>ThreeFold Agentic Apps use a unique model: You provide the compute resources (Slices), while application providers manage the applications. This ensures you maintain sovereignty over your infrastructure while benefiting from professional management.</p>
<hr>
<p class="mb-0">When you deploy an application, you'll be guided through the process of allocating the necessary resources if you don't already have them.</p>
</div>
@@ -169,7 +169,7 @@
<div class="text-center py-5">
<i class="bi bi-app display-1 text-muted"></i>
<h4 class="mt-3">No Applications Available</h4>
<p class="text-muted">Check back later for new application solutions.</p>
<p class="text-muted">Check back later for new applications.</p>
</div>
</div>
{% endif %}
@@ -273,7 +273,7 @@
</div>
<div class="col-md-6">
<h5>3. Deployment & Management</h5>
<p>The solution provider handles deployment, updates, and maintenance while you retain full sovereignty.</p>
<p>The application provider handles deployment, updates, and maintenance while you retain full sovereignty.</p>
<h5>4. Access & Control</h5>
<p>Access your application through secure channels while maintaining complete control over your data and infrastructure.</p>

View File

@@ -129,8 +129,8 @@
</td>
<td>
<div class="provider-name">
{% if product_data.product.attributes.farmer_email %}
{{ product_data.product.attributes.farmer_email.value | truncate(length=15) }}
{% if product_data.product.attributes.resource_provider_email %}
{{ product_data.product.attributes.resource_provider_email.value | truncate(length=15) }}
{% else %}
{% if product_data.product.provider %}{{ product_data.product.provider }}{% else %}Unknown{% endif %}
{% endif %}

View File

@@ -21,9 +21,9 @@
<div class="col-md-3">
<div class="card text-white bg-success mb-3">
<div class="card-body">
<h5 class="card-title">3Nodes</h5>
<h5 class="card-title">Mycelium Nodes</h5>
<p class="card-text">120+ certified nodes</p>
<a href="/marketplace/3nodes" class="text-white">Browse 3Nodes →</a>
<a href="/marketplace/mycelium_nodes" class="text-white">Browse Mycelium Nodes →</a>
</div>
</div>
</div>

View File

@@ -34,9 +34,9 @@
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if active_section == 'three_nodes' %}active{% endif %}" href="/marketplace/3nodes">
<a class="nav-link {% if active_section == 'three_nodes' %}active{% endif %}" href="/marketplace/mycelium_nodes">
<i class="bi bi-hdd-rack me-1"></i>
3Nodes
Mycelium Nodes
</a>
</li>
<li class="nav-item">
@@ -48,7 +48,7 @@
<li class="nav-item">
<a class="nav-link {% if active_section == 'applications' %}active{% endif %}" href="/marketplace/applications">
<i class="bi bi-app me-1"></i>
Application Solutions
Agentic Apps
</a>
</li>
<li class="nav-item">

View File

@@ -1,23 +1,23 @@
{% extends "marketplace/layout.html" %}
{% block title %}Project Mycelium - 3Nodes Hardware{% endblock %}
{% block title %}Project Mycelium - Mycelium Nodes Hardware{% endblock %}
{% block marketplace_content %}
<div class="my-4">
<h1>3Nodes Hardware</h1>
<p class="lead">Discover certified hardware nodes that power the ThreeFold Grid infrastructure.</p>
<h1>Mycelium Nodes Hardware</h1>
<p class="lead">Discover certified hardware nodes that power the Mycelium Grid infrastructure.</p>
<!-- 3Nodes Introduction -->
<!-- Mycelium Nodes Introduction -->
<div class="alert alert-info mb-4">
<div class="d-flex">
<div class="me-3">
<i class="bi bi-server fs-3"></i>
</div>
<div>
<h5 class="alert-heading">What are 3Nodes?</h5>
<p>3Nodes are the physical hardware units that make up the ThreeFold Grid. These certified servers provide compute, storage, and network capacity to the decentralized internet infrastructure.</p>
<h5 class="alert-heading">What are Mycelium Nodes?</h5>
<p>Mycelium Nodes are the physical hardware units that make up the Mycelium Grid. These certified servers provide compute, storage, and network capacity to the decentralized internet infrastructure.</p>
<hr>
<p class="mb-0">By purchasing a 3Node, you become a farmer in the ThreeFold ecosystem, earning TFT rewards while contributing to the decentralized internet.</p>
<p class="mb-0">By purchasing a Mycelium Node, you become a resource provider in the Mycelium ecosystem, earning MC rewards while contributing to the decentralized internet.</p>
</div>
</div>
</div>
@@ -25,7 +25,7 @@
<!-- Filter and Search Section -->
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title">Filter 3Nodes</h5>
<h5 class="card-title">Filter Mycelium Nodes</h5>
<form class="row g-3" method="GET">
<div class="col-md-3">
<label for="locationFilter" class="form-label">Location</label>
@@ -64,13 +64,13 @@
</div>
<div class="col-md-3 d-flex align-items-end">
<button type="submit" class="btn btn-primary me-2">Apply Filters</button>
<a href="/marketplace/3nodes" class="btn btn-outline-secondary">Clear</a>
<a href="/marketplace/mycelium_nodes" class="btn btn-outline-secondary">Clear</a>
</div>
</form>
</div>
</div>
<!-- 3Nodes Grid -->
<!-- Mycelium Nodes Grid -->
<div class="row">
{% if hardware_products and hardware_products | length > 0 %}
{% for product_data in hardware_products %}
@@ -166,7 +166,7 @@
<div class="mb-3">
<div class="alert alert-success small">
<i class="bi bi-currency-dollar me-1"></i>
<strong>Farming Potential:</strong> Earn TFT rewards by contributing to the ThreeFold Grid
<strong>Farming Potential:</strong> Earn MC rewards by contributing to the Mycelium Grid
</div>
</div>
</div>
@@ -184,7 +184,7 @@
data-product-name="{{ product_data.product.name }}"
data-unit-price="{{ product_data.price.display_amount }}"
data-currency="{{ product_data.price.display_currency }}"
data-category="3nodes">
data-category="mycelium_nodes">
<i class="bi bi-lightning-charge me-1"></i>Buy Now
</button>
<button class="btn btn-primary btn-sm add-to-cart-btn"
@@ -206,7 +206,7 @@
<div class="col-12">
<div class="text-center py-5">
<i class="bi bi-server display-1 text-muted"></i>
<h4 class="mt-3">No 3Nodes Available</h4>
<h4 class="mt-3">No Mycelium Nodes Available</h4>
<p class="text-muted">Check back later for new hardware offerings.</p>
</div>
</div>
@@ -215,7 +215,7 @@
<!-- Pagination -->
{% if pagination and pagination.total_pages > 1 %}
<nav aria-label="3Node pages" class="mt-4">
<nav aria-label="Mycelium Node pages" class="mt-4">
<ul class="pagination justify-content-center">
<!-- Previous Page -->
<li class="page-item {% if not pagination.has_previous %}disabled{% endif %}">
@@ -293,7 +293,7 @@
<!-- Results Info -->
<div class="text-center text-muted mt-2">
Showing page {{ pagination.current_page + 1 }} of {{ pagination.total_pages }}
({{ pagination.total_count }} total 3Nodes)
({{ pagination.total_count }} total Mycelium Nodes)
</div>
{% endif %}
@@ -302,27 +302,27 @@
<div class="col-12">
<div class="card">
<div class="card-body">
<h3 class="card-title">Become a ThreeFold Farmer</h3>
<h3 class="card-title">Become a Mycelium Resource Provider</h3>
<div class="row">
<div class="col-md-6">
<h5>1. Purchase Your 3Node</h5>
<p>Choose from certified hardware options that meet ThreeFold Grid requirements.</p>
<h5>1. Purchase Your Mycelium Node</h5>
<p>Choose from certified hardware options that meet Mycelium Grid requirements.</p>
<h5>2. Connect to the Grid</h5>
<p>Boot your 3Node with Zero-OS and connect it to the ThreeFold Grid infrastructure.</p>
<p>Boot your Mycelium Node with Zero-OS and connect it to the Mycelium Grid infrastructure.</p>
</div>
<div class="col-md-6">
<h5>3. Start Earning</h5>
<p>Your 3Node automatically provides capacity to the grid and earns TFT rewards based on utilization.</p>
<p>Your Mycelium Node automatically provides capacity to the grid and earns MC rewards based on utilization.</p>
<h5>4. Monitor & Maintain</h5>
<p>Use the ThreeFold Dashboard to monitor your node's performance and earnings.</p>
<p>Use the Mycelium Dashboard to monitor your node's performance and earnings.</p>
</div>
</div>
<div class="alert alert-info mt-3">
<i class="bi bi-info-circle me-2"></i>
<strong>ROI Potential:</strong> 3Node farmers typically see return on investment within 2-4 years, depending on grid utilization and TFT price.
<strong>ROI Potential:</strong> Mycelium Node resource providers typically see return on investment within 2-4 years, depending on grid utilization and MC price.
</div>
</div>
</div>
@@ -332,14 +332,14 @@
<!-- Node Types -->
<div class="row mt-4">
<div class="col-12">
<h3 class="mb-4">3Node Types</h3>
<h3 class="mb-4">Mycelium Node Types</h3>
</div>
<div class="col-md-4 mb-3">
<div class="card">
<div class="card-body text-center">
<i class="bi bi-tools fs-1 text-primary mb-3"></i>
<h5>DIY Nodes</h5>
<p class="small text-muted">Build your own 3Node using compatible hardware. Most cost-effective option for technical users.</p>
<p class="small text-muted">Build your own Mycelium Node using compatible hardware. Most cost-effective option for technical users.</p>
</div>
</div>
</div>
@@ -348,7 +348,7 @@
<div class="card-body text-center">
<i class="bi bi-award fs-1 text-primary mb-3"></i>
<h5>Certified Nodes</h5>
<p class="small text-muted">Pre-built and certified hardware that's guaranteed to work with the ThreeFold Grid.</p>
<p class="small text-muted">Pre-built and certified hardware that's guaranteed to work with the Mycelium Grid.</p>
</div>
</div>
</div>

View File

@@ -24,7 +24,7 @@
<strong>Node:</strong> {{ slice.node_id }}
</div>
<div class="mb-3">
<strong>Farmer:</strong> {{ slice.farmer_email }}
<strong>Resource Provider:</strong> {{ slice.resource_provider_email }}
</div>
<div class="mb-3">
<strong>Specifications:</strong>
@@ -54,7 +54,7 @@
<div class="card-body">
<form id="sliceRentalForm" method="POST" action="/marketplace/slice/rent">
<!-- Hidden fields -->
<input type="hidden" name="farmer_email" value="{{ farmer_email }}">
<input type="hidden" name="resource_provider_email" value="{{ resource_provider_email }}">
<input type="hidden" name="node_id" value="{{ node_id }}">
<input type="hidden" name="combination_id" value="{{ combination_id }}">

View File

@@ -82,11 +82,11 @@
</div>
</div>
<!-- 3Nodes Section -->
<!-- Mycelium Nodes Section -->
<div class="row mt-4">
<div class="col-12">
<div class="dashboard-section">
<h3>3Nodes Statistics</h3>
<h3>Mycelium Nodes Statistics</h3>
<div class="row">
<div class="col-lg-6">
<div class="card mb-4 h-100">
@@ -121,7 +121,7 @@
<div class="col-lg-6">
<div class="card mb-4 h-100">
<div class="card-header">
<h5>3Node Certification Rate</h5>
<h5>Mycelium Node Certification Rate</h5>
</div>
<div class="card-body d-flex justify-content-center align-items-center">
<canvas id="nodeCertificationChart" width="400" height="300"></canvas>

View File

@@ -104,7 +104,7 @@ async fn test_complete_marketplace_categories_ux_workflow() {
// Simulate node browsing and information
let node_categories = vec![
("3node", "Dedicated ThreeFold node for farming"),
("mycelium_node", "Dedicated Mycelium node for farming"),
("titan", "High-performance enterprise node"),
("quantum", "Quantum-safe storage node"),
];

View File

@@ -42,12 +42,12 @@ async fn test_complete_provider_dashboards_ux_workflow() {
// Test users for different provider types
let farmer_email = "farmer_dashboard_test@example.com";
let app_provider_email = "app_provider_test@example.com";
let application_provider_email = "application_provider_test@example.com";
let service_provider_email = "service_provider_test@example.com";
// Clean up any existing test data
cleanup_test_user_data(farmer_email);
cleanup_test_user_data(app_provider_email);
cleanup_test_user_data(application_provider_email);
cleanup_test_user_data(service_provider_email);
// Initialize services
@@ -133,8 +133,8 @@ async fn test_complete_provider_dashboards_ux_workflow() {
println!("✅ Farmer Dashboard: WORKING - Node management, capacity planning, earnings tracking");
// Step 3: Test Application Provider Dashboard (/dashboard/app-provider)
println!("\n🔧 Step 3: Test Application Provider Dashboard (/dashboard/app-provider)");
// Step 3: Test Application Provider Dashboard (/dashboard/application-provider)
println!("\n🔧 Step 3: Test Application Provider Dashboard (/dashboard/application-provider)");
// Create test app provider profile
let published_apps = vec![
@@ -144,7 +144,7 @@ async fn test_complete_provider_dashboards_ux_workflow() {
("backup-solution", "Automated Backup Tool", 34, 4.1, "pending"),
];
println!(" 📱 Application Provider: {}", app_provider_email);
println!(" 📱 Application Provider: {}", application_provider_email);
println!(" 📊 Published Applications:");
let mut total_installs = 0;
@@ -282,7 +282,7 @@ async fn test_complete_provider_dashboards_ux_workflow() {
// Final cleanup
cleanup_test_user_data(farmer_email);
cleanup_test_user_data(app_provider_email);
cleanup_test_user_data(application_provider_email);
cleanup_test_user_data(service_provider_email);
// Final verification
@@ -321,7 +321,7 @@ async fn test_provider_dashboards_performance() {
let start_time = std::time::Instant::now();
// Simulate dashboard data loading
let provider_types = vec!["farmer", "app_provider", "service_provider"];
let provider_types = vec!["farmer", "application_provider", "service_provider"];
for provider_type in provider_types {
// Simulate dashboard page load

View File

@@ -165,7 +165,7 @@ impl TestDataManager {
/// Create test marketplace data
fn create_test_marketplace_data(personas: &HashMap<UserRole, TestPersona>) -> TestMarketplaceData {
let farmer_email = personas.get(&UserRole::Farmer).unwrap().email.clone();
let app_provider_email = personas.get(&UserRole::AppProvider).unwrap().email.clone();
let application_provider_email = personas.get(&UserRole::AppProvider).unwrap().email.clone();
let service_provider_email = personas.get(&UserRole::ServiceProvider).unwrap().email.clone();
TestMarketplaceData {
@@ -186,7 +186,7 @@ impl TestDataManager {
price: 25.0,
currency: "TFC".to_string(),
description: "Test application for UX testing".to_string(),
provider_email: app_provider_email.clone(),
provider_email: application_provider_email.clone(),
},
],
services: vec![

View File

@@ -86,7 +86,7 @@ async fn test_anonymous_marketplace_browsing() -> Result<(), Box<dyn std::error:
// Test each marketplace category
let categories = vec![
("/marketplace/compute", "compute resources"),
("/marketplace/3nodes", "3nodes"),
("/marketplace/mycelium_nodes", "mycelium_nodes"),
("/marketplace/gateways", "gateways"),
("/marketplace/applications", "applications"),
("/marketplace/services", "services"),
@@ -106,9 +106,9 @@ async fn test_anonymous_marketplace_browsing() -> Result<(), Box<dyn std::error:
// Should show VM/slice options
helper.browser.wait_for_element(".compute-resources, .vm-options, .slice-list").await.ok();
}
"/marketplace/3nodes" => {
"/marketplace/mycelium_nodes" => {
// Should show node listings
helper.browser.wait_for_element(".node-listings, .server-list, .3node-grid").await.ok();
helper.browser.wait_for_element(".node-listings, .server-list, .mycelium-node-grid").await.ok();
}
"/marketplace/gateways" => {
// Should show gateway services

View File

@@ -1,7 +1,26 @@
{
"user_email": "user123@example.com",
"wallet_balance_usd": 0.0,
"transactions": [],
"wallet_balance_usd": 12.0,
"transactions": [
{
"id": "5590075e-2ec3-4ece-b64e-2ab2bd8a4a92",
"user_id": "user123@example.com",
"transaction_type": {
"Purchase": {
"product_id": "credits"
}
},
"amount": 12.0,
"currency": "USD",
"exchange_rate_usd": 1.0,
"amount_usd": 12.0,
"description": "Credits purchase via credit_card",
"reference_id": "5590075e-2ec3-4ece-b64e-2ab2bd8a4a92",
"metadata": null,
"timestamp": "2025-09-08T17:16:29.777650952Z",
"status": "Completed"
}
],
"staked_amount_usd": 0.0,
"pool_positions": {},
"name": "user123",
@@ -14,20 +33,69 @@
"availability": null,
"slas": [],
"apps": [],
"app_deployments": [],
"application_deployments": [],
"deleted": null,
"deleted_at": null,
"deletion_reason": null,
"nodes": [],
"farmer_earnings": [],
"farmer_settings": null,
"resource_provider_earnings": [],
"resource_provider_settings": null,
"slice_products": [],
"user_activities": [],
"user_preferences": null,
"user_activities": [
{
"id": "1db56139-8a5f-4e32-a235-c0f1bb4ab357",
"user_email": "user123@example.com",
"activity_type": "WalletTransaction",
"description": "Purchased $12 credits via credit_card",
"metadata": null,
"timestamp": "2025-09-08T17:16:29.777958164Z",
"ip_address": null,
"user_agent": null,
"session_id": null,
"importance": "Medium",
"category": "Wallet"
}
],
"user_preferences": {
"theme": "light",
"language": "en",
"currency_display": "symbol",
"email_notifications": true,
"push_notifications": true,
"marketing_emails": false,
"data_sharing": false,
"preferred_currency": "USD",
"preferred_language": "en",
"timezone": "UTC",
"notification_settings": {
"email_enabled": true,
"push_enabled": true,
"sms_enabled": false,
"slack_webhook": null,
"discord_webhook": null,
"enabled": true,
"push": true,
"node_offline_alerts": true,
"maintenance_reminders": true,
"earnings_reports": true
},
"privacy_settings": {
"profile_public": false,
"email_public": false,
"activity_public": false,
"stats_public": false,
"profile_visibility": "private",
"marketing_emails": false,
"data_sharing": false,
"activity_tracking": true
},
"dashboard_layout": "default",
"last_payment_method": "credit_card"
},
"usage_statistics": null,
"orders": [],
"active_product_rentals": [],
"farmer_rental_earnings": [],
"resource_provider_rental_earnings": [],
"node_rentals": [],
"node_groups": [],
"slice_rentals": [],