Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions .github/workflows/build-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ jobs:

- name: Build module bundle (MF2)
run: pnpm run build
env:
NKZ_VERSION_HASH: ${{ github.sha }}

- name: Upload bundle artifact
uses: actions/upload-artifact@v4
Expand All @@ -92,7 +94,7 @@ jobs:
path: dist/
retention-days: 30

- name: Deploy bundle to MinIO
- name: Deploy bundle to MinIO (versioned)
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
env:
MINIO_ENDPOINT_URL: ${{ secrets.MINIO_ENDPOINT_URL }}
Expand All @@ -105,11 +107,30 @@ jobs:
echo "::notice::MinIO secrets not set. Skipping bundle upload."
exit 0
fi
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-8)
pip install -q awscli
EP="${MINIO_ENDPOINT_URL}"
B="s3://${{ env.MINIO_BUCKET }}/${{ env.MODULE_PREFIX }}"
aws --endpoint-url "${EP}" s3 sync dist/ "${B}/" \
--cache-control "public, max-age=300"
aws --endpoint-url "${EP}" s3 sync "dist/" "${B}/${SHORT_SHA}/" \
--cache-control "public, max-age=31536000, immutable"

- name: Activate version
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
env:
MINIO_ENDPOINT_URL: ${{ secrets.MINIO_ENDPOINT_URL }}
MINIO_ACCESS_KEY: ${{ secrets.MINIO_ACCESS_KEY }}
MINIO_SECRET_KEY: ${{ secrets.MINIO_SECRET_KEY }}
run: |
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-8)
if [ -z "${MINIO_ENDPOINT_URL:-}" ] || [ -z "${MINIO_ACCESS_KEY:-}" ] || [ -z "${MINIO_SECRET_KEY:-}" ]; then
echo "::notice::MinIO secrets not set. Skipping activation."
exit 0
fi
curl -s -X POST "${{ secrets.API_URL }}/api/modules/datahub/deploy" \
-H "Content-Type: application/json" \
-H "X-Internal-Token: ${{ secrets.NKZ_INTERNAL_TOKEN }}" \
-d "{\"version\": \"${SHORT_SHA}\"}" \
|| echo "WARNING: deploy activation failed (API may not support /deploy yet)"

build-backend:
name: Build and push backend image
Expand Down
10 changes: 5 additions & 5 deletions src/DataHubPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ const DataHubPageInner: React.FC = () => {
const closeSidebar = useCallback(() => setSidebarOpen(false), []);

const sidebarContent = (
<aside className="w-64 shrink-0 border-r border-white/10 bg-slate-950 flex flex-col h-full">
<div className="h-12 shrink-0 flex items-center justify-between px-4 border-b border-white/10">
<h2 className="text-sm font-semibold text-slate-300 tracking-wide uppercase">
<aside className="w-64 shrink-0 border-r border-slate-700/50 bg-slate-950 flex flex-col h-full">
<div className="h-12 shrink-0 flex items-center justify-between px-4 border-b border-slate-700/50">
<h2 className="text-sm font-semibold text-slate-200 tracking-wide uppercase">
{t('tree.sidebarTitle')}
</h2>
{isMobile && (
Expand Down Expand Up @@ -164,13 +164,13 @@ const DataHubPageInner: React.FC = () => {
)}

{/* View switcher */}
<div className="shrink-0 flex items-center gap-1 px-3 py-2 border-b border-white/10 bg-slate-950">
<div className="shrink-0 flex items-center gap-1.5 px-3 py-2 border-b border-slate-700/50 bg-slate-950">
{(['dashboard', 'catalog', 'inspector'] as const).map((v) => (
<button
key={v}
type="button"
onClick={() => setView(v)}
className={`px-3 py-1 text-xs rounded-md transition-colors ${
className={`px-3 py-1.5 text-sm rounded-md transition-colors ${
view === v
? 'bg-slate-700 text-slate-100'
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-800'
Expand Down
14 changes: 14 additions & 0 deletions src/Module.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
import { defineModule } from '@nekazari/module-kit';
import { lazy } from 'react';
import { i18n } from '@nekazari/sdk';
import { moduleSlots } from './slots';
import pkg from '../package.json';
import en from './locales/en.json';
import es from './locales/es.json';

// Register translations with the shared i18next singleton.
// The host initializes i18next with HTTP backend (loadPath: '/locales/{{lng}}/{{ns}}.json'),
// but module translations are bundled in JS — they must be added via addResourceBundle().
// Without this, useTranslation('datahub') falls through to a failed HTTP fetch and returns
// the raw key string.
// Guard: only runs in browser (typeof window !== 'undefined') and when the i18n singleton
// has been properly initialized (addResourceBundle is available). The build-time manifest
// emitter runs this module in Node.js where the shared i18next singleton is not yet wired.
if (typeof window !== 'undefined' && typeof i18n?.addResourceBundle === 'function') {
i18n.addResourceBundle('en', 'datahub', en, true, true);
i18n.addResourceBundle('es', 'datahub', es, true, true);
}

const DataHubPage = lazy(() => import('./DataHubPage'));

export default defineModule({
Expand Down
24 changes: 12 additions & 12 deletions src/components/DataTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const DataTree: React.FC<DataTreeProps> = ({

return (
<div className="flex flex-col h-full min-h-0">
<div className="p-2 border-b border-slate-700">
<div className="p-2 border-b border-slate-700/50">
<input
type="search"
placeholder={t('tree.searchPlaceholder')}
Expand All @@ -76,13 +76,13 @@ export const DataTree: React.FC<DataTreeProps> = ({
/>
</div>
<div className="flex-1 overflow-auto p-2">
{isLoading && <p className="text-sm text-slate-500">{t('tree.loading')}</p>}
{isLoading && <p className="text-sm text-slate-400">{t('tree.loading')}</p>}
{error && <p className="text-sm text-red-400">{t('tree.errorLoad')}</p>}
{!isLoading && !error && entities.length === 0 && (
<p className="text-sm text-slate-500">{t('tree.empty')}</p>
<p className="text-sm text-slate-400">{t('tree.empty')}</p>
)}
{!isLoading && !error && entities.length > 0 && (
<ul className="space-y-1 text-sm">
<ul className="space-y-1.5 text-sm">
{entities.map((e: DataHubEntity) => (
<li key={e.id} className="space-y-0.5">
<div
Expand All @@ -102,17 +102,17 @@ export const DataTree: React.FC<DataTreeProps> = ({
onSelect(e, first?.name ?? '');
}
}}
className={`flex items-center gap-2 px-2 py-1.5 rounded cursor-pointer transition-colors ${
className={`flex items-center gap-2 px-2 py-2 rounded cursor-pointer transition-colors ${
selectedEntity?.id === e.id
? 'bg-slate-700 ring-1 ring-emerald-500/40'
: 'hover:bg-slate-800'
}`}
>
<span className="font-medium text-slate-200 truncate">{e.name}</span>
<span className="font-medium text-slate-100 truncate">{e.name}</span>
<span className="text-slate-500 shrink-0 text-xs">{e.type}</span>
</div>
{selectedEntity?.id === e.id && timeseriesAttributes(e.attributes).length > 0 && (
<ul className="pl-3 text-xs">
<ul className="pl-3 text-sm">
{timeseriesAttributes(e.attributes).map((attr) => {
const handleDragStart = (ev: React.DragEvent<HTMLElement>) => {
const payload = JSON.stringify({
Expand Down Expand Up @@ -141,23 +141,23 @@ export const DataTree: React.FC<DataTreeProps> = ({
onAddToCanvas?.(e, attr.name);
}
}}
className={`py-1 rounded px-1.5 cursor-grab active:cursor-grabbing transition-colors ${
className={`py-1.5 rounded px-2 cursor-grab active:cursor-grabbing transition-colors ${
selectedAttribute === attr.name
? 'bg-emerald-900 text-emerald-200 ring-1 ring-emerald-500/40'
: 'text-slate-400 hover:bg-slate-800 hover:text-slate-200'
: 'text-slate-300 hover:bg-slate-800 hover:text-slate-100'
}`}
>
<span>{attr.name}</span>
{ATTRIBUTE_UNIT[attr.name] && (
<span className="text-slate-500 ml-1">({ATTRIBUTE_UNIT[attr.name]})</span>
<span className="text-slate-400 ml-1">({ATTRIBUTE_UNIT[attr.name]})</span>
)}
{attr.source && attr.source !== 'timescale' && (
<span className="text-slate-600 ml-1 text-[10px]">
<span className="text-slate-500 ml-1 text-xs">
[{attr.source}]
</span>
)}
{hasActivePanel && (
<span className="ml-auto text-[10px] text-emerald-300 bg-emerald-900 px-1.5 py-0.5 rounded-full opacity-0 group-hover:opacity-100 transition-opacity">
<span className="ml-auto text-xs text-emerald-300 bg-emerald-900 px-2 py-0.5 rounded-full opacity-0 group-hover:opacity-100 transition-opacity">
+ {t('tree.addToPanel')}
</span>
)}
Expand Down
Loading
Loading