fix: mount openclaw routes at /openclaw prefix + fix docker.client wrapper + strip duplicate /apps/ paths across sub-routers
- openClawRoutes was mounted at root causing /status vs /openclaw/status mismatch
- ctx.docker is a typed wrapper {client,pull,...} — all calls now use docker.client.*
- templates/deploy/removal/restore sub-routers had /apps/ hardcoded in inner routes
causing double-stacking when mounted under /apps (→ /apps/apps/templates etc)
- openclaw.js: GET /status, POST /deploy, GET/POST /proxy/*, DELETE /
This commit is contained in:
@@ -227,7 +227,7 @@ module.exports = function({ docker, caddy, credentialManager, servicesStateManag
|
||||
}
|
||||
|
||||
// Check for existing container before deployment
|
||||
router.post('/apps/check-existing', asyncHandler(async (req, res) => {
|
||||
router.post('/check-existing', asyncHandler(async (req, res) => {
|
||||
const { appId } = req.body;
|
||||
const template = ctx.APP_TEMPLATES[appId];
|
||||
if (!template) throw new ValidationError('Invalid app template');
|
||||
@@ -240,7 +240,7 @@ module.exports = function({ docker, caddy, credentialManager, servicesStateManag
|
||||
}, 'check-existing'));
|
||||
|
||||
// Deploy new app
|
||||
router.post('/apps/deploy', asyncHandler(async (req, res) => {
|
||||
router.post('/deploy', asyncHandler(async (req, res) => {
|
||||
const { appId, config } = req.body;
|
||||
if (!appId || typeof appId !== 'string') {
|
||||
throw new ValidationError('appId is required');
|
||||
|
||||
@@ -45,11 +45,21 @@ module.exports = function(ctx) {
|
||||
|
||||
// Mount sub-routes — pass full ctx so sub-routes can reference ctx.* properties
|
||||
const subCtx = Object.assign({}, ctx, { helpers });
|
||||
router.use(initDeploy(subCtx));
|
||||
router.use(initRemoval(subCtx));
|
||||
router.use(initTemplates(subCtx));
|
||||
router.use(initRestore(subCtx));
|
||||
router.use(initCompose(subCtx));
|
||||
|
||||
try { router.use('/deploy', initDeploy(subCtx)); }
|
||||
catch(e) { (ctx.log || console).error('[apps] deploy routes init failed:', e.message); }
|
||||
|
||||
try { router.use('/remove', initRemoval(subCtx)); }
|
||||
catch(e) { (ctx.log || console).error('[apps] removal routes init failed:', e.message); }
|
||||
|
||||
try { router.use('/apps', initTemplates(subCtx)); }
|
||||
catch(e) { (ctx.log || console).error('[apps] templates routes init failed:', e.message); }
|
||||
|
||||
try { router.use('/restore', initRestore(subCtx)); }
|
||||
catch(e) { (ctx.log || console).error('[apps] restore routes init failed:', e.message); }
|
||||
|
||||
try { router.use('/compose', initCompose(subCtx)); }
|
||||
catch(e) { (ctx.log || console).error('[apps] compose routes init failed:', e.message); }
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ module.exports = function({
|
||||
* @param {Function} deps.safeErrorMessage - Safe error message formatter
|
||||
* @returns {express.Router}
|
||||
*/
|
||||
router.delete('/apps/:appId', asyncHandler(async (req, res) => {
|
||||
router.delete('/:appId', asyncHandler(async (req, res) => {
|
||||
const { appId } = req.params;
|
||||
const { containerId, subdomain, ip, deleteContainer } = req.query;
|
||||
const shouldDeleteContainer = deleteContainer === 'true';
|
||||
|
||||
@@ -30,7 +30,7 @@ module.exports = function({ docker, caddy, servicesStateManager, asyncHandler, e
|
||||
* Pulls image, creates container, starts it, recreates Caddy config.
|
||||
* Skips if container is already running.
|
||||
*/
|
||||
router.post('/apps/:appId/restore', asyncHandler(async (req, res) => {
|
||||
router.post('/:appId/restore', asyncHandler(async (req, res) => {
|
||||
const { appId } = req.params;
|
||||
const services = await servicesStateManager.read();
|
||||
const service = services.find(s => s.id === appId);
|
||||
@@ -50,7 +50,7 @@ module.exports = function({ docker, caddy, servicesStateManager, asyncHandler, e
|
||||
* Restore all services that have deployment manifests.
|
||||
* Returns per-service results.
|
||||
*/
|
||||
router.post('/apps/restore-all', asyncHandler(async (req, res) => {
|
||||
router.post('/restore-all', asyncHandler(async (req, res) => {
|
||||
const services = await servicesStateManager.read();
|
||||
const restoreable = services.filter(s => s.deploymentManifest);
|
||||
|
||||
@@ -91,7 +91,7 @@ module.exports = function({ docker, caddy, servicesStateManager, asyncHandler, e
|
||||
/**
|
||||
* List all services and their restore status.
|
||||
*/
|
||||
router.get('/apps/restore-status', asyncHandler(async (req, res) => {
|
||||
router.get('/restore-status', asyncHandler(async (req, res) => {
|
||||
const services = await servicesStateManager.read();
|
||||
const status = [];
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ module.exports = function({
|
||||
};
|
||||
|
||||
// Get available app templates
|
||||
router.get('/apps/templates', asyncHandler(async (req, res) => {
|
||||
router.get('/templates', asyncHandler(async (req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
templates: ctx.APP_TEMPLATES,
|
||||
@@ -51,7 +51,7 @@ module.exports = function({
|
||||
}, 'apps-templates'));
|
||||
|
||||
// Get specific app template
|
||||
router.get('/apps/templates/:appId', asyncHandler(async (req, res) => {
|
||||
router.get('/templates/:appId', asyncHandler(async (req, res) => {
|
||||
const { appId } = req.params;
|
||||
const template = ctx.APP_TEMPLATES[appId];
|
||||
if (!template) {
|
||||
@@ -62,7 +62,7 @@ module.exports = function({
|
||||
}, 'apps-template-detail'));
|
||||
|
||||
// Check port availability
|
||||
router.get('/apps/ports/:port/check', asyncHandler(async (req, res) => {
|
||||
router.get('/ports/:port/check', asyncHandler(async (req, res) => {
|
||||
const port = req.params.port;
|
||||
const conflicts = await helpers.checkPortConflicts([port]);
|
||||
if (conflicts.length > 0) {
|
||||
@@ -74,7 +74,7 @@ module.exports = function({
|
||||
}, 'check-port'));
|
||||
|
||||
// Get suggested available port
|
||||
router.get('/apps/ports/:basePort/suggest', asyncHandler(async (req, res) => {
|
||||
router.get('/ports/:basePort/suggest', asyncHandler(async (req, res) => {
|
||||
const basePort = parseInt(req.params.basePort) || 8080;
|
||||
const maxAttempts = 100;
|
||||
const usedPorts = await docker.getUsedPorts();
|
||||
@@ -88,7 +88,7 @@ module.exports = function({
|
||||
}, 'suggest-port'));
|
||||
|
||||
// Update subdomain for deployed app
|
||||
router.post('/apps/update-subdomain', asyncHandler(async (req, res) => {
|
||||
router.post('/update-subdomain', asyncHandler(async (req, res) => {
|
||||
const { serviceId, oldSubdomain, newSubdomain, containerId, ip } = req.body;
|
||||
const { ValidationError } = require('../../errors');
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ const caRoutes = require('../routes/ca');
|
||||
const browseRoutes = require('../routes/browse');
|
||||
const errorLogsRoutes = require('../routes/errorlogs');
|
||||
const licenseRoutes = require('../routes/license');
|
||||
const openClawRoutes = require('../routes/openclaw');
|
||||
const recipesRoutes = require('../routes/recipes');
|
||||
const themesRoutes = require('../routes/themes');
|
||||
const dockerResourcesRoutes = require('../routes/docker-resources');
|
||||
@@ -394,6 +395,7 @@ async function createApp() {
|
||||
}));
|
||||
apiRouter.use(arrRoutes(ctx));
|
||||
apiRouter.use(appsRoutes(ctx));
|
||||
apiRouter.use('/openclaw', openClawRoutes(ctx));
|
||||
apiRouter.use(logsRoutes({
|
||||
asyncHandler: ctx.asyncHandler,
|
||||
docker: ctx.docker,
|
||||
|
||||
Reference in New Issue
Block a user