diff --git a/src/gui/src/UI/Dashboard/TabUsage.js b/src/gui/src/UI/Dashboard/TabUsage.js
new file mode 100644
index 000000000..9674b2a38
--- /dev/null
+++ b/src/gui/src/UI/Dashboard/TabUsage.js
@@ -0,0 +1,277 @@
+/**
+ * Copyright (C) 2024-present Puter Technologies Inc.
+ *
+ * This file is part of Puter.
+ *
+ * Puter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+// Sort state for the usage table
+let usageTableSortState = {
+ column: 'cost', // default sort by cost
+ direction: 'desc' // default descending (highest cost first)
+};
+let usageTableData = []; // Store raw data for sorting
+
+const TabUsage = {
+ id: 'usage',
+ label: 'Usage',
+ icon: ` `,
+ html: () => {
+ return `
+
${i18n('usage')}
+ `;
+ },
+ init: ($el_window) => {
+ update_usage_details($el_window);
+ $($el_window).find('.update-usage-details').on('click', function () {
+ update_usage_details($el_window);
+ });
+
+ // Scoped click handler for usage details toggle
+ $($el_window).on('click', '.driver-usage-details', function () {
+ const $container = $(this).closest('.driver-usage');
+ $container.find('.driver-usage-details-content').toggleClass('active');
+ $(this).toggleClass('active');
+
+ // change the text of the driver-usage-details-text depending on the class
+ if ( $(this).hasClass('active') ) {
+ $(this).find('.driver-usage-details-text').text('Hide usage details');
+ } else {
+ $(this).find('.driver-usage-details-text').text('View usage details');
+ }
+ });
+
+ // Click handler for sortable table headers
+ $($el_window).on('click', '.driver-usage-details-content-table th[data-sort]', function () {
+ const column = $(this).data('sort');
+
+ // Toggle direction if same column, otherwise default to descending
+ if ( usageTableSortState.column === column ) {
+ usageTableSortState.direction = usageTableSortState.direction === 'asc' ? 'desc' : 'asc';
+ } else {
+ usageTableSortState.column = column;
+ usageTableSortState.direction = 'desc';
+ }
+
+ renderUsageTable();
+ });
+ },
+};
+
+function getSortIcon(column) {
+ const isActive = usageTableSortState.column === column;
+ const direction = usageTableSortState.direction;
+
+ if ( !isActive ) {
+ // Neutral sort icon (both arrows, dimmed)
+ return `
+
+
+
+ `;
+ } else if ( direction === 'asc' ) {
+ // Ascending icon
+ return `
+
+
+
+ `;
+ } else {
+ // Descending icon
+ return `
+
+
+
+ `;
+ }
+}
+
+function renderUsageTable() {
+ // Sort the data
+ const sortedData = [...usageTableData].sort((a, b) => {
+ let aVal, bVal;
+
+ switch ( usageTableSortState.column ) {
+ case 'resource':
+ aVal = a.resource.toLowerCase();
+ bVal = b.resource.toLowerCase();
+ break;
+ case 'cost':
+ default:
+ aVal = a.rawCost;
+ bVal = b.rawCost;
+ break;
+ }
+
+ if ( aVal < bVal ) return usageTableSortState.direction === 'asc' ? -1 : 1;
+ if ( aVal > bVal ) return usageTableSortState.direction === 'asc' ? 1 : -1;
+ return 0;
+ });
+
+ // Build the table
+ let h = '';
+
+ h += `
+
+ Resource ${getSortIcon('resource')}
+ Units
+ Cost ${getSortIcon('cost')}
+
+ `;
+
+ h += '';
+ for ( const row of sortedData ) {
+ h += `
+
+ ${row.resource}
+ ${row.formattedUnits}
+ ${row.formattedCost}
+ `;
+ }
+ h += ' ';
+ h += '
';
+
+ $('.driver-usage-details-content').html(h);
+}
+
+async function update_usage_details ($el_window) {
+ // Add spinning animation and record start time
+ const startTime = Date.now();
+ $($el_window).find('.update-usage-details-icon').css('animation', 'spin 1s linear infinite');
+
+ const monthlyUsagePromise = puter.auth.getMonthlyUsage().then(res => {
+ let monthlyAllowance = res.allowanceInfo?.monthUsageAllowance;
+ let remaining = res.allowanceInfo?.remaining;
+ let totalUsage = monthlyAllowance - remaining;
+ let totalUsagePercentage = (totalUsage / monthlyAllowance * 100).toFixed(0);
+
+ $('#total-usage').html(window.number_format(totalUsage / 100_000_000, { decimals: 2, prefix: '$' }));
+ $('#total-capacity').html(window.number_format(monthlyAllowance / 100_000_000, { decimals: 2, prefix: '$' }));
+ $('.usage-progbar-percent').html(`${totalUsagePercentage }%`);
+ $('.usage-progbar').css('width', `${totalUsagePercentage }%`);
+
+ // Store raw data for sorting
+ usageTableData = [];
+ for ( let key in res.usage ) {
+ // value must be object
+ if ( typeof res.usage[key] !== 'object' ) {
+ continue;
+ }
+
+ const rawUnits = res.usage[key].units;
+ const rawCost = res.usage[key].cost;
+
+ // Format units for display
+ let formattedUnits;
+ if ( key.startsWith('filesystem:') && key.endsWith(':bytes') ) {
+ formattedUnits = window.byte_format(rawUnits);
+ } else {
+ formattedUnits = window.number_format(rawUnits, { decimals: 0, thousandSeparator: ',' });
+ }
+
+ usageTableData.push({
+ resource: key,
+ rawUnits: rawUnits,
+ formattedUnits: formattedUnits,
+ rawCost: rawCost,
+ formattedCost: window.number_format(rawCost / 100_000_000, { decimals: 2, prefix: '$' })
+ });
+ }
+
+ renderUsageTable();
+ });
+
+ const spacePromise = puter.fs.space().then(res => {
+ let usage_percentage = (res.used / res.capacity * 100).toFixed(0);
+ usage_percentage = usage_percentage > 100 ? 100 : usage_percentage;
+
+ let general_used = res.used;
+
+ let host_usage_percentage = 0;
+ if ( res.host_used ) {
+ $('#storage-puter-used').html(window.byte_format(res.used));
+ $('#storage-puter-used-w').show();
+
+ general_used = res.host_used;
+ host_usage_percentage = ((res.host_used - res.used) / res.capacity * 100).toFixed(0);
+ }
+
+ $('#storage-used').html(window.byte_format(general_used));
+ $('#storage-capacity').html(window.byte_format(res.capacity));
+ $('#storage-used-percent').html(
+ `${usage_percentage }%${
+ host_usage_percentage > 0
+ ? ` / ${ host_usage_percentage }%` : ''}`);
+ $('#storage-bar').css('width', `${usage_percentage }%`);
+ $('#storage-bar-host').css('width', `${host_usage_percentage }%`);
+ if ( usage_percentage >= 100 ) {
+ $('#storage-bar').css({
+ 'border-top-right-radius': '3px',
+ 'border-bottom-right-radius': '3px',
+ });
+ }
+ });
+
+ // Wait for both promises to complete
+ await Promise.all([monthlyUsagePromise, spacePromise]);
+
+ // Ensure spinning continues for at least 1 second
+ const elapsed = Date.now() - startTime;
+ const minDuration = 1000; // 1 second
+ if ( elapsed < minDuration ) {
+ await new Promise(resolve => setTimeout(resolve, minDuration - elapsed));
+ }
+
+ // Remove spinning animation
+ $($el_window).find('.update-usage-details-icon').css('animation', '');
+}
+
+export default TabUsage;
\ No newline at end of file
diff --git a/src/gui/src/UI/Dashboard/UIDashboard.js b/src/gui/src/UI/Dashboard/UIDashboard.js
index 021be251b..13e7fd188 100644
--- a/src/gui/src/UI/Dashboard/UIDashboard.js
+++ b/src/gui/src/UI/Dashboard/UIDashboard.js
@@ -28,11 +28,13 @@ import UIWindowFeedback from '../UIWindowFeedback.js';
// Import tab modules
import TabFiles from './TabFiles.js';
import TabApps from './TabApps.js';
+import TabUsage from './TabUsage.js';
// Registry of all available tabs
const tabs = [
TabFiles,
TabApps,
+ TabUsage,
];
async function UIDashboard (options) {
@@ -51,14 +53,20 @@ async function UIDashboard (options) {
h += '