Compare commits
4 Commits
6adbd88b22
...
026d0c8d6b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
026d0c8d6b | ||
|
|
33845c70d7 | ||
|
|
fa18c72cf7 | ||
|
|
c5aa4b5332 |
@@ -4,6 +4,31 @@
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Silkscreen:wght@400;700&display=swap');
|
||||
|
||||
/* === Cross-link Button === */
|
||||
.cross-link-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
border: 2px solid var(--mc-dark);
|
||||
background: #2e4a8b;
|
||||
color: var(--mc-text-aqua);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
font-family: 'Silkscreen', 'Courier New', monospace;
|
||||
text-shadow: 1px 1px 0 var(--mc-dark);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.1s;
|
||||
box-shadow: inset 0 1px 0 #4466bb, inset 0 -1px 0 #1a2a66;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cross-link-btn:hover {
|
||||
background: #3e5a9b;
|
||||
color: white;
|
||||
}
|
||||
|
||||
:root {
|
||||
--mc-dark: #1a1a1a;
|
||||
--mc-darker: #0e0e0e;
|
||||
|
||||
@@ -22,6 +22,8 @@ function App() {
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [, forceRender] = useState(0);
|
||||
|
||||
const turtleDashboardUrl = import.meta.env.VITE_TURTLE_DASHBOARD_URL || `${window.location.protocol}//${window.location.hostname}:4444`;
|
||||
|
||||
useEffect(() => {
|
||||
connect();
|
||||
}, [connect]);
|
||||
@@ -34,10 +36,6 @@ function App() {
|
||||
|
||||
const staleSecs = lastUpdate ? Math.floor((Date.now() - lastUpdate) / 1000) : null;
|
||||
|
||||
useEffect(() => {
|
||||
connect();
|
||||
}, [connect]);
|
||||
|
||||
const renderPanelContent = () => {
|
||||
switch (panelTab) {
|
||||
case 'inventory':
|
||||
@@ -60,6 +58,16 @@ function App() {
|
||||
<div className="app-header">
|
||||
<h1>⛏️ Inventory Manager</h1>
|
||||
<div className="header-right">
|
||||
{/* Cross-link to Turtle Dashboard */}
|
||||
<a
|
||||
href={turtleDashboardUrl}
|
||||
className="cross-link-btn"
|
||||
title="Open Turtle Control Dashboard"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
🐢 Turtles
|
||||
</a>
|
||||
{/* Settings gear button */}
|
||||
<button
|
||||
className="settings-gear"
|
||||
|
||||
@@ -7,6 +7,7 @@ services:
|
||||
- server-data:/data
|
||||
environment:
|
||||
- API_KEY=${API_KEY:-}
|
||||
- TURTLE_SERVER_URL=${TURTLE_SERVER_URL:-}
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3001/api/health',r=>{process.exit(r.statusCode===200?0:1)}).on('error',()=>process.exit(1))"]
|
||||
|
||||
@@ -966,6 +966,78 @@ wss.on('close', () => {
|
||||
clearInterval(WS_PING_INTERVAL);
|
||||
});
|
||||
|
||||
// ========== Cross-Project Integration API ==========
|
||||
// These endpoints allow the RemoteTurtle system to query inventory state
|
||||
|
||||
const TURTLE_SERVER_URL = process.env.TURTLE_SERVER_URL || ''; // e.g. http://turtle-server:3001
|
||||
|
||||
// Find where a specific item is stored (for turtles to pick up items)
|
||||
app.get('/api/integration/locate-item', (req, res) => {
|
||||
const { name, minCount } = req.query;
|
||||
if (!name) return res.status(400).json({ error: 'Item name required (?name=minecraft:diamond)' });
|
||||
|
||||
const items = inventoryState.itemList || [];
|
||||
const match = items.find(i => i.name === name);
|
||||
if (!match || match.count < (parseInt(minCount) || 1)) {
|
||||
return res.json({ found: false, available: match ? match.count : 0 });
|
||||
}
|
||||
res.json({ found: true, name: match.name, count: match.count, displayName: match.displayName });
|
||||
});
|
||||
|
||||
// Search items by partial name (for turtle autocomplete/fuzzy matching)
|
||||
app.get('/api/integration/search-items', (req, res) => {
|
||||
const { q, limit } = req.query;
|
||||
if (!q) return res.status(400).json({ error: 'Search query required (?q=diamond)' });
|
||||
|
||||
const items = inventoryState.itemList || [];
|
||||
const query = q.toLowerCase();
|
||||
const results = items
|
||||
.filter(i => i.name.toLowerCase().includes(query) || (i.displayName || '').toLowerCase().includes(query))
|
||||
.sort((a, b) => b.count - a.count)
|
||||
.slice(0, parseInt(limit) || 20);
|
||||
|
||||
res.json({ results });
|
||||
});
|
||||
|
||||
// Get storage summary (available space, total items) for turtle decision-making
|
||||
app.get('/api/integration/storage-status', (req, res) => {
|
||||
res.json({
|
||||
grandTotal: inventoryState.grandTotal || 0,
|
||||
chestCount: inventoryState.chestCount || 0,
|
||||
totalSlots: inventoryState.totalSlots || 0,
|
||||
usedSlots: inventoryState.usedSlots || 0,
|
||||
freeSlots: (inventoryState.totalSlots || 0) - (inventoryState.usedSlots || 0),
|
||||
lastUpdate,
|
||||
bridgeConnected: bridgeClients.size > 0,
|
||||
});
|
||||
});
|
||||
|
||||
// Get full item list (for turtle dump target selection)
|
||||
app.get('/api/integration/items', (req, res) => {
|
||||
const items = inventoryState.itemList || [];
|
||||
res.json({ items: items.map(i => ({ name: i.name, count: i.count })) });
|
||||
});
|
||||
|
||||
// Get alerts (so turtles know what items are running low)
|
||||
app.get('/api/integration/low-stock', (req, res) => {
|
||||
const triggered = (alertsState || []).filter(a => a.triggered !== false);
|
||||
res.json({ alerts: triggered });
|
||||
});
|
||||
|
||||
// Proxy to turtle server for combined dashboard info
|
||||
app.get('/api/integration/turtle-status', async (req, res) => {
|
||||
if (!TURTLE_SERVER_URL) {
|
||||
return res.json({ configured: false, message: 'TURTLE_SERVER_URL not configured' });
|
||||
}
|
||||
try {
|
||||
const resp = await fetch(`${TURTLE_SERVER_URL}/api/turtles`);
|
||||
const data = await resp.json();
|
||||
res.json({ configured: true, ...data });
|
||||
} catch (err) {
|
||||
res.status(502).json({ configured: true, error: `Cannot reach turtle server: ${err.message}` });
|
||||
}
|
||||
});
|
||||
|
||||
// ========== Start Server ==========
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
@@ -973,6 +1045,9 @@ server.listen(PORT, HOST, () => {
|
||||
console.log(`\nBridge HTTP endpoint: http://localhost:${PORT}/api/bridge/state`);
|
||||
console.log(`Bridge WebSocket: ws://localhost:${PORT}/ws/bridge`);
|
||||
console.log(`Web client WebSocket: ws://localhost:${PORT}/ws`);
|
||||
if (TURTLE_SERVER_URL) {
|
||||
console.log(`🐢 Turtle server integration: ${TURTLE_SERVER_URL}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
|
||||
Reference in New Issue
Block a user