tacel-app-storage
v1.0.1
Published
Windows File Explorer-like UI for Tacel App-Storage share access with SMB service account authentication
Downloads
97
Maintainers
Readme
tacel-app-storage
A Windows File Explorer-like UI for Tacel App-Storage share access with SMB service account authentication.
This module contains NO sensitive data. All credentials, paths, and configuration are provided by the consuming application at runtime.
Table of Contents
- Release Notes
- Architecture Overview
- Folder Structure
- Security Model
- Backend Usage
- Frontend Usage
- API Reference
- Theming
- Features To Implement
Release Notes
v1.0.1 (2026-03-05)
- Fixed queue dialog live updates so row/group controls stay responsive.
- Added grouped transfer queue interaction hardening (expand, pause/resume/cancel).
- Added blocking delete overlay with progress text in frontend for long delete operations.
- Switched backend recursive delete to safer async/chunked removal for large folders.
- Reduced unnecessary explorer refresh churn after uploads.
Architecture Overview
This module has two parts:
| Part | File | Process | Purpose |
|------|------|---------|---------|
| Backend | app-storage-api.js | Main | IPC handler factory — SMB connection, file operations |
| Frontend | app-storage.js + app-storage.css | Renderer | Windows Explorer-like UI component |
┌─────────────────────────────────────────────────────────────────────────────┐
│ YOUR APP (provides all sensitive configuration) │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ Main Process ││
│ │ ││
│ │ createAppStorageAPI(ipcMain, { ││
│ │ channelPrefix: 'app-storage', ││
│ │ sharePath: '\\\\192.168.1.5\\TacelAppsStorage', ← App provides ││
│ │ credentials: { ││
│ │ username: 'DOMAIN\\ServiceAccount', ← App provides ││
│ │ password: getSecurePassword() ← App provides ││
│ │ } ││
│ │ }) ││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ │ │
│ │ IPC │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ Renderer Process ││
│ │ ││
│ │ new AppStorageExplorer(container, { ││
│ │ channelPrefix: 'app-storage', ││
│ │ ipcInvoke: window.electron.ipcRenderer.invoke ││
│ │ }) ││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘Folder Structure
The TacelAppsStorage share has a 5-layer architecture:
TacelAppsStorage/
│
├── _System/ ← Infrastructure (logs, backups, config)
│ ├── Logs/
│ │ ├── OfficeHQ/
│ │ ├── ShipWorks/
│ │ ├── TechPortal/
│ │ ├── WireHub/
│ │ ├── SignalLine/
│ │ ├── AdminPro/
│ │ └── NCRManagement/
│ ├── Backups/
│ │ ├── Daily/
│ │ ├── Weekly/
│ │ └── Monthly/
│ ├── Config/
│ ├── Migrations/
│ └── Audit/
│
├── Platform/ ← Application data
│ ├── Apps/ ← Individual app folders (each app controls its own structure)
│ │ ├── OfficeHQ/
│ │ ├── ShipWorks/
│ │ ├── TechPortal/
│ │ ├── WireHub/
│ │ ├── SignalLine/
│ │ ├── AdminPro/
│ │ ├── NCRManagement/
│ │ ├── ShipStatusPro/
│ │ ├── PIU/
│ │ ├── MRP/
│ │ └── LWU/
│ │
│ └── Shared/ ← Feature-based shared data (cross-app)
│ ├── RMA/
│ │ ├── Active/ ← Active RMA folders (RMA-2024-001, etc.)
│ │ └── Internal/
│ │ ├── Evaluation/
│ │ └── Repair/
│ ├── NCR/
│ ├── Forms/
│ │ ├── Templates/
│ │ ├── Files/
│ │ └── Previews/
│ ├── Customers/
│ ├── Suppliers/
│ ├── Products/
│ ├── Tickets/
│ └── Chat/
│ └── Attachments/
│
├── _Temp/ ← Temporary files and processing queues
│ ├── OfficeHQ/
│ ├── ShipWorks/
│ ├── TechPortal/
│ ├── WireHub/
│ ├── SignalLine/
│ ├── AdminPro/
│ ├── NCRManagement/
│ └── Processing/
│
├── Updates/ ← Auto-update packages for each app
│ ├── OfficeHQ/
│ │ └── app/
│ ├── ShipWorks/
│ │ └── app/
│ ├── TechPortal/
│ │ └── app/
│ ├── WireHub/
│ │ └── app/
│ ├── SignalLine/
│ │ └── app/
│ ├── AdminPro/
│ │ └── app/
│ ├── NCRManagement/
│ │ └── app/
│ ├── ShipStatusPro/
│ │ └── app/
│ ├── PIU/
│ │ └── app/
│ └── LWU/
│ └── app/
│
└── Archive/ ← Old/discontinued data (flattened)
├── RMA/
├── NCR/
├── Apps/
├── Projects/
└── Legacy/Layer Descriptions
| Layer | Folder | Purpose |
|-------|--------|---------|
| System | _System/ | Infrastructure files — logs, backups, config, migrations, audit trails |
| Platform | Platform/ | Application data — Apps/ for individual apps, Shared/ for cross-app features |
| Temp | _Temp/ | Temporary files and processing queues — auto-cleaned periodically |
| Updates | Updates/ | Auto-update packages for each app |
| Archive | Archive/ | Old/discontinued data — flattened structure for simplicity |
Apps List
| App | Folder Name | Description |
|-----|-------------|-------------|
| Office-HQ | OfficeHQ | Office management |
| ShipWorks | ShipWorks | Shipping operations |
| Tech-Portal | TechPortal | Technical support |
| Wire-Hub | WireHub | Wire department |
| Signal Line | SignalLine | Signal line management |
| Admin-Pro | AdminPro | Administration |
| NCR-Management | NCRManagement | NCR management |
| ShipStatus Pro | ShipStatusPro | Shipping status |
| PIU | PIU | PIU system |
| MRP | MRP | MRP system |
| LWU | LWU | LWU system |
Security Model
What the Module Contains
| Item | In Module? | Notes | |------|-----------|-------| | Folder structure metadata | ✅ Yes | Names, descriptions, hierarchy | | File icons and UI | ✅ Yes | Visual components | | IPC handler logic | ✅ Yes | File operations code |
What the Module Does NOT Contain
| Item | In Module? | Where It Lives | |------|-----------|----------------| | Share IP address | ❌ No | App's config | | Service account username | ❌ No | App's config | | Service account password | ❌ No | App's safeStorage | | UNC paths | ❌ No | App provides at runtime | | User lists or roles | ❌ No | App's auth system |
Authentication Flow
- App starts → retrieves credentials from secure storage
- App calls
createAppStorageAPI()withcredentialsProvider(or staticcredentials) - Each operation → connect → do work → disconnect (no persistent connection)
- Only module-created sessions are disconnected (existing user mappings are preserved)
- Credentials never stored in module code
Backend Usage
// In your Electron main process
const { createAppStorageAPI } = require('tacel-app-storage/app-storage-api');
createAppStorageAPI(ipcMain, {
// Required: IPC channel prefix (must match frontend)
channelPrefix: 'app-storage',
// Required: UNC path to the share (YOUR app provides this)
sharePath: '\\\\192.168.1.5\\TacelAppsStorage',
// Preferred: credential provider (resolved at operation time)
credentialsProvider: () => ({
username: getServiceUserFromSecureStore(),
password: getPasswordFromSafeStorage()
}),
// Optional alternative: static credentials object
// credentials: { username: 'DOMAIN\\ServiceAccount', password: '...' },
// Optional: Paths that cannot be accessed
restrictedPaths: ['_System/Config', '_System/Audit'],
// Optional security behavior (recommended defaults)
connectionMode: 'operationScoped', // default: connect per operation, then disconnect
allowShellOpen: false, // default: disable system-app open
allowOpenInExplorer: false, // default: disable reveal in Explorer
clearCachedCredentials: false // default: do NOT clear global Windows cached creds
});IPC Channels Registered
Using prefix app-storage as an example:
| Channel | Args | Returns | Description |
|---------|------|---------|-------------|
| app-storage:list | { relativePath } | { ok, items[], currentPath } | List directory contents |
| app-storage:search | { query, relativePath?, extensions?, modifiedFrom?, modifiedTo?, includeDirectories?, maxResults? } | { ok, results[], truncated } | Recursive search with filters |
| app-storage:read | { relativePath } | { ok, data, info } | Read file as base64 |
| app-storage:preview | { relativePath, maxBytes? } | { ok, data, info, mimeType, totalSize, bytesRead, truncated } | Read preview-safe payload |
| app-storage:write | { relativePath, data, overwrite? } | { ok, info } | Write file from base64 |
| app-storage:upload-start | { relativePath, overwrite?, totalSize? } | { ok, uploadId, chunkSize } | Start chunked upload session |
| app-storage:upload-chunk | { uploadId, data } | { ok, bytesWritten, totalSize } | Append chunk to upload session |
| app-storage:upload-finish | { uploadId } | { ok, info } | Finalize chunked upload |
| app-storage:upload-abort | { uploadId } | { ok, aborted } | Cancel chunked upload and cleanup temp file |
| app-storage:delete | { relativePath, recursive? } | { ok } | Delete file or folder |
| app-storage:rename | { relativePath, newName } | { ok, info } | Rename file or folder |
| app-storage:copy | { sourcePath, destPath, overwrite? } | { ok, info } | Copy file or folder |
| app-storage:move | { sourcePath, destPath, overwrite? } | { ok, info } | Move file or folder |
| app-storage:create-folder | { relativePath } | { ok, info } | Create new folder |
| app-storage:exists | { relativePath } | { ok, exists, info? } | Check if path exists |
| app-storage:open | { relativePath } | { ok } | Open with system app (disabled unless allowShellOpen: true) |
| app-storage:open-in-explorer | { relativePath } | { ok } | Open in Windows Explorer (disabled unless allowOpenInExplorer: true) |
| app-storage:get-info | { relativePath } | { ok, info } | Get detailed file/folder info |
| app-storage:get-folder-structure | — | { ok, structure } | Get known folder structure |
Item Info Shape
{
name: 'document.pdf',
path: 'Platform/Shared/RMA/Active/RMA-2024-001/document.pdf',
isDirectory: false,
size: 245000, // bytes (null for folders)
modified: '2024-02-24T10:30:00.000Z',
created: '2024-02-20T08:00:00.000Z',
extension: 'pdf', // null for folders
// For directories only:
itemCount: 5, // total items
folderCount: 2, // subfolders
fileCount: 3 // files
}Frontend Usage
<!-- Include the CSS -->
<link rel="stylesheet" href="path/to/app-storage.css">
<!-- Include Font Awesome (required for icons) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<!-- Include the JS -->
<script src="path/to/app-storage.js"></script>// Create an instance with FULL control
const explorer = new AppStorageExplorer(document.getElementById('explorer-container'), {
// ══════════════════════════════════════════════════════════════════
// REQUIRED
// ══════════════════════════════════════════════════════════════════
channelPrefix: 'app-storage',
ipcInvoke: window.electron.ipcRenderer.invoke,
// ══════════════════════════════════════════════════════════════════
// NAVIGATION
// ══════════════════════════════════════════════════════════════════
initialPath: 'Platform/Shared/RMA',
rootLabel: 'App-Storage',
// ══════════════════════════════════════════════════════════════════
// PERMISSIONS — Control what users can do
// ══════════════════════════════════════════════════════════════════
allowUpload: true,
allowDelete: true,
allowRename: true,
allowCreateFolder: true,
allowCopy: true,
allowMove: true,
allowDownload: true,
allowCreateFile: true, // Allow creating new files
allowFavorites: true, // Allow adding/removing favorites
allowProperties: true, // Allow viewing properties
allowOpenFile: false, // Allow opening files with system app (disabled by default)
// ══════════════════════════════════════════════════════════════════
// DISPLAY — Control what UI elements are shown
// ══════════════════════════════════════════════════════════════════
viewMode: 'details', // 'list', 'grid', or 'details'
showHidden: false,
showSidebar: true,
showToolbar: true,
showStatusBar: true,
showBreadcrumb: true,
showFavorites: true, // Show favorites section in sidebar
showFolderTree: true, // Show folder tree in sidebar
// ══════════════════════════════════════════════════════════════════
// CONTEXT MENUS — Full control over right-click menus
// ══════════════════════════════════════════════════════════════════
enableContextMenu: true, // Enable right-click menus at all
enableEmptySpaceContextMenu: true, // Enable right-click on empty space
enableItemContextMenu: true, // Enable right-click on items
// Custom context menu builders (return array of menu items, or null for default)
buildItemContextMenu: (explorer, item) => {
// Return custom menu items or null for default
return [
{ icon: 'fa-eye', label: 'Preview', action: () => console.log('Preview', item) },
{ separator: true },
{ icon: 'fa-copy', label: 'Copy', action: () => explorer.copySelected() }
];
},
buildEmptySpaceContextMenu: (explorer) => null, // Use default
// ══════════════════════════════════════════════════════════════════
// FILE FILTERING — Control what files are shown
// ══════════════════════════════════════════════════════════════════
allowedExtensions: ['pdf', 'docx', 'xlsx'], // Only show these (null = all)
blockedExtensions: ['exe', 'bat', 'cmd'], // Hide these (null = none)
minFileSize: 1024, // Min 1KB
maxFileSize: 52428800, // Max 50MB
minModifiedDate: new Date('2024-01-01'), // Only files after this date
maxModifiedDate: null, // No max date
// Custom filter function
filterItem: (item) => {
// Return true to show, false to hide
return !item.name.startsWith('.');
},
// ══════════════════════════════════════════════════════════════════
// FILE TEMPLATES — Control what file types can be created
// ══════════════════════════════════════════════════════════════════
// null = use defaults, array = custom templates, false = disable
fileTemplates: [
{ type: 'folder', label: 'Folder', icon: 'fa-folder-plus', extension: null },
{ type: 'separator' },
{ type: 'txt', label: 'Text File', icon: 'fa-file-alt', extension: 'txt', content: '' },
{ type: 'xlsx', label: 'Excel', icon: 'fa-file-excel', extension: 'xlsx', content: null }
],
// ══════════════════════════════════════════════════════════════════
// TOOLBAR — Control which toolbar buttons are shown
// ══════════════════════════════════════════════════════════════════
toolbarButtons: {
back: true,
forward: true,
up: true,
refresh: true,
newFolder: true,
upload: true,
copy: true,
cut: true,
paste: true,
delete: true,
viewList: true,
viewGrid: true,
viewDetails: true
},
// Custom toolbar renderer
renderToolbar: (explorer) => null, // Return HTMLElement or null for default
// ══════════════════════════════════════════════════════════════════
// COLUMNS — Control which columns are shown in details view
// ══════════════════════════════════════════════════════════════════
columns: {
icon: true,
name: true,
modified: true,
type: true,
size: true
},
// Custom columns
customColumns: [
{ key: 'owner', label: 'Owner', render: (item) => item.owner || 'Unknown' }
],
// ══════════════════════════════════════════════════════════════════
// CALLBACKS — Hook into every action (return false to prevent)
// ══════════════════════════════════════════════════════════════════
onNavigate: (path, items) => console.log('Navigated to:', path),
onSelect: (selectedItems) => console.log('Selected:', selectedItems),
onOpen: (item) => { /* return false to prevent */ },
onError: (error) => console.error('Error:', error),
onContextMenu: (event, item) => { /* return false to prevent */ },
onUpload: (files) => { /* return false to prevent */ },
onDelete: (items) => { /* return false to prevent */ },
onRename: (item, newName) => { /* return false to prevent */ },
onCopy: (items) => {},
onCut: (items) => {},
onPaste: (items, destPath) => { /* return false to prevent */ },
onCreateFolder: (name) => { /* return false to prevent */ },
onCreateFile: (template, name) => { /* return false to prevent */ },
onDownload: (item) => { /* return false to prevent */ },
onFavoriteAdd: (path, label) => { /* return false to prevent */ },
onFavoriteRemove: (path) => { /* return false to prevent */ },
onViewChange: (viewMode) => {},
onSortChange: (column, direction) => {},
// ══════════════════════════════════════════════════════════════════
// CUSTOM RENDERERS — Override how things are displayed
// ══════════════════════════════════════════════════════════════════
renderItem: (item, explorer) => null, // Custom item renderer
renderIcon: (item) => null, // Custom icon: return class or HTML
renderSidebar: (explorer) => null, // Custom sidebar
renderStatusBar: (items, selected) => null, // Custom status bar text
renderBreadcrumb: (explorer) => null, // Custom breadcrumb
renderEmptyState: () => null, // Custom empty folder message
renderLoadingState: () => null, // Custom loading indicator
renderErrorState: (error) => null, // Custom error display
// ══════════════════════════════════════════════════════════════════
// LOCALIZATION — Customize all text labels
// ══════════════════════════════════════════════════════════════════
labels: {
back: 'Back',
forward: 'Forward',
up: 'Up',
refresh: 'Refresh',
newFolder: 'New Folder',
upload: 'Upload',
copy: 'Copy',
cut: 'Cut',
paste: 'Paste',
delete: 'Delete',
open: 'Open',
openWith: 'Open With...',
download: 'Download',
rename: 'Rename',
properties: 'Properties',
addToFavorites: 'Add to Favorites',
removeFromFavorites: 'Remove from Favorites',
selectAll: 'Select All',
view: 'View',
sortBy: 'Sort by',
new: 'New',
listView: 'List View',
gridView: 'Grid View',
detailsView: 'Details View',
sortName: 'Name',
sortModified: 'Date Modified',
sortType: 'Type',
sortSize: 'Size',
loading: 'Loading...',
emptyFolder: 'This folder is empty',
favorites: 'Favorites',
folders: 'Folders'
}
});Public Methods
// Navigation
explorer.navigate('Platform/Apps/OfficeHQ');
explorer.goBack();
explorer.goForward();
explorer.goUp();
explorer.refresh();
// Selection
explorer.selectAll();
explorer.clearSelection();
const selected = explorer.getSelectedItems();
// View
explorer.setViewMode('grid'); // 'list', 'grid', 'details'
// File Operations
await explorer.createFolder('New Folder');
await explorer.deleteSelected();
await explorer.renameItem('old/path', 'newName');
explorer.copySelected();
explorer.cutSelected();
await explorer.paste();
await explorer.uploadFiles(fileList);
await explorer.downloadFile('path/to/file.pdf');
await explorer.openFile('path/to/file.pdf');
await explorer.openInExplorer();
// Cleanup
explorer.destroy();Theming
Override CSS variables to match your app's theme:
.ase-explorer {
/* Colors */
--ase-primary: #0078d4;
--ase-primary-hover: #106ebe;
--ase-primary-light: #e5f3ff;
--ase-danger: #d13438;
--ase-success: #107c10;
/* Backgrounds */
--ase-bg: #ffffff;
--ase-bg-sidebar: #f3f3f3;
--ase-bg-toolbar: #f9f9f9;
--ase-bg-hover: #f5f5f5;
--ase-bg-selected: #cce8ff;
/* Borders */
--ase-border: #e1e1e1;
--ase-border-radius: 4px;
/* Text */
--ase-text: #1a1a1a;
--ase-text-muted: #666666;
/* Typography */
--ase-font: 'Segoe UI', sans-serif;
--ase-font-size: 13px;
/* Sizing */
--ase-sidebar-width: 220px;
--ase-toolbar-height: 40px;
--ase-row-height: 28px;
--ase-grid-item-size: 80px;
}Example: Office-HQ Theme (Gold)
.ase-explorer {
--ase-primary: #c9a227;
--ase-primary-hover: #b8922a;
--ase-primary-light: #faf8f0;
--ase-bg-sidebar: #faf8f0;
--ase-border: #e0d5b8;
}Example: Tech-Portal Theme (Dark)
.ase-explorer {
--ase-primary: #10b981;
--ase-bg: #1a1a2e;
--ase-bg-sidebar: #16162a;
--ase-bg-toolbar: #1e1e3f;
--ase-bg-hover: #2a2a4a;
--ase-bg-selected: #10b98133;
--ase-border: #3a3a5a;
--ase-text: #e0e0e0;
--ase-text-muted: #888888;
}Features To Implement
✅ Implemented (Core)
- [x] Backend IPC API with connect-per-operation pattern
- [x] List directory contents
- [x] Read/write files (base64)
- [x] Delete files and folders
- [x] Rename files and folders
- [x] Copy/move files and folders
- [x] Create folders
- [x] Check if path exists
- [x] Open file with system app
- [x] Get file/folder info
- [x] Folder structure metadata
- [x] Restricted paths support
- [x] Frontend Explorer UI
- [x] Sidebar tree view (resizable)
- [x] Breadcrumb navigation
- [x] Details/Grid/List view modes
- [x] Toolbar with actions
- [x] Status bar
- [x] Selection (single, multi with Ctrl)
- [x] Context menu (right-click on items)
- [x] Context menu (right-click on empty space)
- [x] Drag-and-drop upload
- [x] Navigation history (back/forward)
- [x] Clipboard (copy/cut/paste)
- [x] Column sorting — Click column headers to sort
- [x] Favorites/Quick Access — Pin folders with persistence
- [x] Create new files — Text, Markdown, Word, Excel, PowerPoint, HTML, CSS, JS, JSON, XML, CSV
- [x] Properties dialog — View file/folder properties
- [x] File filtering — By extension, size, date, custom function
- [x] Full customization — Every component, menu, label is configurable
- [x] Custom context menus — Build your own or extend defaults
- [x] Custom renderers — Override any UI component
- [x] Localization — All labels customizable
- [x] Callbacks — Hook into every action with ability to prevent
🔲 To Implement
- [ ] Search — Search files by name within current folder or recursively
- [ ] Preview pane — Preview images, PDFs, text files in sidebar
- [ ] Keyboard shortcuts — Delete, F2 rename, Ctrl+C/X/V, Enter to open
- [ ] Drag-and-drop move — Drag items to move within explorer
- [ ] Multi-select with Shift — Range selection
- [ ] Column resizing — Drag column borders
- [ ] Recent files — Show recently accessed files
- [ ] Address bar — Editable path input
- [ ] Compress/Extract — ZIP operations
- [ ] File preview thumbnails — Show image thumbnails in grid view
- [ ] Progress indicator — Show progress for large operations
- [ ] Undo/Redo — Undo delete, rename, move operations
- [ ] Dual-pane mode — Two explorers side by side
License
MIT © Tacel Ltd
