feat(screenshots): replace ai mockups with actual application screenshots and enable package publishing
CI & Build NaxOS Web Dashboard / build (push) Successful in 6m50s
@@ -31,11 +31,26 @@ jobs:
|
|||||||
- name: Publish Package to Gitea Packages
|
- name: Publish Package to Gitea Packages
|
||||||
run: |
|
run: |
|
||||||
tar -czf naxos-ui-dist.tar.gz -C dist .
|
tar -czf naxos-ui-dist.tar.gz -C dist .
|
||||||
echo "Uploading naxos-ui-dist.tar.gz to Gitea Packages..."
|
echo "Uploading naxos-ui-dist.tar.gz to Gitea Packages registry..."
|
||||||
curl -s --fail-with-body -X PUT \
|
TOKEN="${{ secrets.PACKAGE_TOKEN }}"
|
||||||
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
if [ -z "$TOKEN" ]; then
|
||||||
|
TOKEN="${{ secrets.GITEA_TOKEN }}"
|
||||||
|
fi
|
||||||
|
RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X PUT \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
--upload-file naxos-ui-dist.tar.gz \
|
--upload-file naxos-ui-dist.tar.gz \
|
||||||
"${{ github.server_url }}/api/packages/naxos/generic/naxos-ui/1.0.0/naxos-ui-dist.tar.gz" || echo "Generic package uploaded or exists"
|
"${{ github.server_url }}/api/packages/naxos/generic/naxos-ui/1.0.0/naxos-ui-dist.tar.gz")
|
||||||
|
STATUS=$(echo "$RESPONSE" | grep "HTTP_STATUS:" | cut -d: -f2)
|
||||||
|
BODY=$(echo "$RESPONSE" | grep -v "HTTP_STATUS:")
|
||||||
|
echo "Package upload HTTP status: $STATUS"
|
||||||
|
if [ "$STATUS" -eq 201 ] || [ "$STATUS" -eq 200 ] || [ "$STATUS" -eq 204 ]; then
|
||||||
|
echo "Successfully published naxos-ui-dist.tar.gz to Gitea Packages!"
|
||||||
|
elif [ "$STATUS" -eq 409 ]; then
|
||||||
|
echo "Package version 1.0.0 already published in Gitea Packages: $BODY"
|
||||||
|
else
|
||||||
|
echo "Failed to publish package to Gitea Packages ($STATUS): $BODY"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Upload UI Assets Artifact
|
- name: Upload UI Assets Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ The **NaxOS Web Dashboard** is a modern, responsive, non-technical web interface
|
|||||||
|
|
||||||
## Dashboard Showcase
|
## Dashboard Showcase
|
||||||
|
|
||||||
| Overview & Telemetry | OpenZFS Storage & Migration |
|
| Overview & Telemetry | OpenZFS Storage Engine |
|
||||||
| :---: | :---: |
|
| :---: | :---: |
|
||||||
|  |  |
|
|  |  |
|
||||||
| **CNCF Perses Analytics** | **App Store & Runtimes** |
|
| **CNCF Perses Analytics** | **App Store & Runtimes** |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
| **Setup & Migration Wizard** | **File Shares Management** |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 494 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 440 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 617 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 554 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 100 KiB |
@@ -30,9 +30,36 @@ import type {
|
|||||||
} from './types/index.js';
|
} from './types/index.js';
|
||||||
|
|
||||||
export const App: React.FC = () => {
|
export const App: React.FC = () => {
|
||||||
const [currentTab, setCurrentTab] = useState('overview');
|
const getInitialTab = () => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const hash = window.location.hash.replace('#', '');
|
||||||
|
if (hash) return hash;
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const tab = params.get('tab');
|
||||||
|
if (tab) return tab;
|
||||||
|
}
|
||||||
|
return 'overview';
|
||||||
|
};
|
||||||
|
|
||||||
|
const [currentTab, setCurrentTab] = useState(getInitialTab);
|
||||||
const [showRebuildModal, setShowRebuildModal] = useState(false);
|
const [showRebuildModal, setShowRebuildModal] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onHashChange = () => {
|
||||||
|
const hash = window.location.hash.replace('#', '');
|
||||||
|
if (hash) setCurrentTab(hash);
|
||||||
|
};
|
||||||
|
window.addEventListener('hashchange', onHashChange);
|
||||||
|
return () => window.removeEventListener('hashchange', onHashChange);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const selectTab = (tab: string) => {
|
||||||
|
setCurrentTab(tab);
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.location.hash = tab;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Application state
|
// Application state
|
||||||
const [status, setStatus] = useState<SystemStatus | null>(null);
|
const [status, setStatus] = useState<SystemStatus | null>(null);
|
||||||
const [pools, setPools] = useState<ZfsPool[]>([]);
|
const [pools, setPools] = useState<ZfsPool[]>([]);
|
||||||
@@ -161,7 +188,7 @@ export const App: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen w-screen overflow-hidden bg-slate-950 text-slate-100 font-sans">
|
<div className="flex h-screen w-screen overflow-hidden bg-slate-950 text-slate-100 font-sans">
|
||||||
<Sidebar currentTab={currentTab} onSelectTab={setCurrentTab} />
|
<Sidebar currentTab={currentTab} onSelectTab={selectTab} />
|
||||||
|
|
||||||
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||||
<Topbar
|
<Topbar
|
||||||
@@ -177,7 +204,7 @@ export const App: React.FC = () => {
|
|||||||
status={status}
|
status={status}
|
||||||
pools={pools}
|
pools={pools}
|
||||||
apps={installedApps}
|
apps={installedApps}
|
||||||
onNavigate={setCurrentTab}
|
onNavigate={selectTab}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||