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
16 changes: 14 additions & 2 deletions Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
## under the License.
##

ARG CONSOLE_VERSION=development

# Stage 1: Base image with package manager
FROM registry.access.redhat.com/ubi10/ubi-minimal:latest AS base

RUN microdnf -y install nodejs && \
RUN microdnf -y install nodejs tar gzip && \
microdnf clean all && \
npm install -g corepack && \
corepack enable && \
Expand All @@ -34,6 +36,8 @@ WORKDIR /monorepo
# Stage 2: Install all dependencies
FROM base AS dependencies

ARG CONSOLE_VERSION

# Copy workspace configuration first
# These files change less frequently than source code
COPY pnpm-workspace.yaml ./
Expand All @@ -51,6 +55,12 @@ COPY components/console/package.json ./components/console/
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile

# Download and install the skupper-console from a released tarball
RUN curl -L -o /tmp/console-embed.tgz --progress-bar \
"https://github.com/skupperproject/skupper-console/releases/download/${CONSOLE_VERSION}/console-embed.tgz" && \
mkdir skupper-console && \
tar -xzf /tmp/console-embed.tgz -C skupper-console/ && rm /tmp/console-embed.tgz

# Stage 3: Build shared packages
FROM dependencies AS shared-builder

Expand Down Expand Up @@ -80,6 +90,8 @@ WORKDIR /app
COPY --from=management-controller-deploy /deployed/management-controller ./
# Copy console as sibling to /app (code expects ../console/dist)
COPY --from=management-controller-deploy /deployed/console ./console/dist
# Copy skupper-console to /app
COPY --from=dependencies /monorepo/skupper-console ./skupper-console

RUN useradd --uid 10000 runner
USER 10000
Expand Down Expand Up @@ -113,4 +125,4 @@ RUN useradd --uid 10000 runner
USER 10000

EXPOSE 8085
CMD ["node", "index.js"]
CMD ["node", "index.js"]
2 changes: 2 additions & 0 deletions components/console/src/pages/VANs/VANs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ const VANs = () => {
const handleVANConsole = (van) => {
// TODO: Implement VAN Console functionality
console.log('Open VAN Console for:', van);
// Open VAN console in a new tab to avoid React Router
window.open(`/console/${van.id}/index.html`, '_blank');
};

const handleDeployClick = async (van) => {
Expand Down
1 change: 1 addition & 0 deletions components/management-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"express": "^4.22.1",
"express-session": "^1.19.0",
"formidable": "^3.5.4",
"http-proxy": "^1.18.1",
"jose": "^6.1.3",
"js-yaml": "^4.1.1",
"morgan": "^1.10.1",
Expand Down
84 changes: 83 additions & 1 deletion components/management-controller/src/mc-apiserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import formidable from 'formidable';
import yaml from 'js-yaml';
import bodyParser from 'body-parser';
import { X509Certificate } from 'node:crypto';
import httpProxy from 'http-proxy';
import { ClientFromPool, queryWithContext } from './db.js';
import * as resourceTemplates from './resource-templates.js';
import { LoadSecret } from '@skupperx/modules/kube'
Expand All @@ -41,7 +42,7 @@ import * as common from '@skupperx/modules/common'
import { StartWatchServer } from './watch-server.js';
import ViteExpress from 'vite-express';
import { createManagementOidcAuth } from './auth/management-oidc.js';
import { NotifyTransaction } from './notify.js';
import { NotifyTransaction, RegisterNotification } from './notify.js';

const __dirname = import.meta.dirname;
/** Deployed image: sources live in `/app/src`, console bundle in `/app/console/dist`. Monorepo dev: `components/console` (two levels up from `components/management-controller/src`). */
Expand All @@ -51,6 +52,9 @@ const VITE_CONSOLE_ROOT = fs.existsSync(
? path.resolve(__dirname, '../console')
: path.resolve(__dirname, '../../console');

/** Path to skupper-console static files */
const SKUPPER_CONSOLE_ROOT = '/app/skupper-console';

const API_PREFIX = '/api/v1alpha1/';
const API_PORT = 8085;
const app = express();
Expand All @@ -63,6 +67,8 @@ const sessionParser = session({
store: memoryStore,
});

const vanProxy = {}; // { Id: { vanId, backboneName } }

app.use(sessionParser);

const link_config_map_yaml = function(name, data) {
Expand Down Expand Up @@ -624,6 +630,51 @@ export async function Start(is_standalone) {
adminApi.Initialize(router, auth);
userApi.Initialize(router, auth);

// Proxy requests to the target collector API
const proxy = httpProxy.createProxyServer({
selfHandleResponse: false,
});
proxy.on('error', (err, req, res) => {
Log(`Proxy error: ${err.message}`);
res.status(500).send(err.message);
});
router.all('/console/:vid*/api/v2alpha1*', auth.protect(), (req, res) => {
let vid = req.params.vid;
if (!vanProxy[vid]) {
return res.status(404).send(`Van ${vid} not found`);
}
let targetVan = vanProxy[vid];
let targetUrl = `http://skupper-console-${targetVan.vanId}.colo-${targetVan.backboneName}:8080`;
Log(`Proxying ${req.url} to ${targetUrl}`);
req.url = req.url.slice(`/console/${vid}`.length);
proxy.web(req, res, {
target: targetUrl,
changeOrigin: true,
});
});

// Serve skupper-console static files under /console/:vanId
router.use('/console/:vanId*', auth.protect(), (req, res, next) => {
let vid = req.params.vanId;
if (!vanProxy[vid]) {
return res.status(404).send(`Van ${vid} not found`);
}

// Serve static files from skupper-console build directory
express.static(SKUPPER_CONSOLE_ROOT, {
index: 'index.html',
fallthrough: true
})(req, res, (err) => {
if (err) {
return next(err);
}
// If no static file matched, serve index.html for SPA routing
if (!res.headersSent) {
res.sendFile(path.join(SKUPPER_CONSOLE_ROOT, 'index.html'));
}
});
});

// route any unauthenticated requests to the login page (catches SPA navigation requests)
router.get('*', auth.protect());

Expand All @@ -640,4 +691,35 @@ export async function Start(is_standalone) {
await ViteExpress.bind(app, server);

await StartWatchServer(server, sessionParser, app, router);
RegisterNotification('ApplicationNetworks', onApplicationNetworks, true);
}

async function onApplicationNetworks(event, id) {
if (!id) {
return;
}
if (event == 'DELETE') {
delete vanProxy[id];
return;
}
const client = await ClientFromPool("system");
try {
// Retrieve VanId and Backbone Name
const result = await client.query(
"SELECT A.VanId, B.Name FROM ApplicationNetworks A JOIN Backbones B ON A.Backbone = B.Id WHERE A.Id = $1",
[id]
);
if (result.rowCount == 1) {
const vanId = result.rows[0].vanid;
const backboneName = result.rows[0].name;
vanProxy[id] = {
vanId: vanId,
backboneName: backboneName
};
}
} catch (err) {
Log(`Error retrieving VanId and Backbone Name for ApplicationNetwork ${id}: ${err.message}`);
} finally {
client.release();
}
Comment thread
fgiorgetti marked this conversation as resolved.
}
Loading