168 lines
5.3 KiB
JavaScript
168 lines
5.3 KiB
JavaScript
/**
|
|
* Profile Manager
|
|
* Handles multiple WMS profiles (AD, LIMAGRAIN, ...) defined in .env.
|
|
*
|
|
* Shared settings (same for all profiles):
|
|
* - WMS_API_AUTH OAuth client Basic auth header
|
|
* - WMS_APPLICATION Application name (e.g. "EasyWMS")
|
|
* - WMS_API_PATH URL path to the ApplicationService API
|
|
* - WMS_TOKEN_PATH URL path to the OAuth token endpoint
|
|
* - WORKFLOW_API_PATH URL path to the AD API
|
|
*
|
|
* Per-profile settings (prefixed by profile name):
|
|
* - <PROFILE>_HOST Hostname or IP (e.g. "10.255.255.2", "p4swms.mss.mecalux.com")
|
|
* - <PROFILE>_USERNAME
|
|
* - <PROFILE>_PASSWORD
|
|
* - <PROFILE>_TENANT
|
|
* - <PROFILE>_SAAS "true" | "false" (default false). When true the WMS is
|
|
* cloud-hosted — local filesystem logs are not reachable,
|
|
* so log tools return an error instead of probing paths.
|
|
*
|
|
* Profile registry:
|
|
* - WMS_PROFILES Comma-separated list of profile names (e.g. "AD,LIMAGRAIN")
|
|
* - DEFAULT_WMS_PROFILE Profile activated at startup (optional)
|
|
*/
|
|
|
|
const DEFAULTS = {
|
|
apiPath: '/ApplicationService/api',
|
|
tokenPath: '/EasySTS/OAuth/Token',
|
|
workflowApiPath: '/AD/api',
|
|
application: 'EasyWMS',
|
|
protocol: 'https',
|
|
};
|
|
|
|
const listeners = [];
|
|
let profiles = {};
|
|
let currentProfileName = null;
|
|
|
|
/**
|
|
* Parse .env into an in-memory profile registry.
|
|
* Called once at startup; safe to call again after env changes (tests).
|
|
*/
|
|
function loadProfiles() {
|
|
profiles = {};
|
|
currentProfileName = null;
|
|
|
|
const names = (process.env.WMS_PROFILES || '')
|
|
.split(',')
|
|
.map(s => s.trim())
|
|
.filter(Boolean);
|
|
|
|
if (names.length === 0) {
|
|
console.error('[Profile] WARNING: WMS_PROFILES is empty — no profiles loaded');
|
|
return;
|
|
}
|
|
|
|
const shared = {
|
|
auth: process.env.WMS_API_AUTH,
|
|
application: process.env.WMS_APPLICATION || DEFAULTS.application,
|
|
apiPath: process.env.WMS_API_PATH || DEFAULTS.apiPath,
|
|
tokenPath: process.env.WMS_TOKEN_PATH || DEFAULTS.tokenPath,
|
|
workflowApiPath: process.env.WORKFLOW_API_PATH || DEFAULTS.workflowApiPath,
|
|
};
|
|
|
|
for (const name of names) {
|
|
const host = process.env[`${name}_HOST`];
|
|
const username = process.env[`${name}_USERNAME`];
|
|
const password = process.env[`${name}_PASSWORD`];
|
|
const tenant = process.env[`${name}_TENANT`];
|
|
const saasRaw = process.env[`${name}_SAAS`];
|
|
const saas = saasRaw != null && /^(true|1|yes)$/i.test(saasRaw.trim());
|
|
|
|
if (!host || !username || !password || !tenant) {
|
|
console.error(`[Profile] WARNING: profile "${name}" is incomplete (missing HOST/USERNAME/PASSWORD/TENANT) — skipped`);
|
|
continue;
|
|
}
|
|
|
|
const base = `${DEFAULTS.protocol}://${host}`;
|
|
profiles[name] = {
|
|
name,
|
|
host,
|
|
username,
|
|
password,
|
|
tenant,
|
|
saas,
|
|
auth: shared.auth,
|
|
application: shared.application,
|
|
apiBaseUrl: `${base}${shared.apiPath}`,
|
|
tokenUrl: `${base}${shared.tokenPath}`,
|
|
workflowApiBase: `${base}${shared.workflowApiPath}`,
|
|
};
|
|
}
|
|
|
|
const defaultName = process.env.DEFAULT_WMS_PROFILE;
|
|
if (defaultName && profiles[defaultName]) {
|
|
currentProfileName = defaultName;
|
|
console.error(`[Profile] Loaded ${Object.keys(profiles).length} profile(s). Active: ${currentProfileName}`);
|
|
} else if (defaultName) {
|
|
console.error(`[Profile] DEFAULT_WMS_PROFILE="${defaultName}" is not a valid profile — no profile active`);
|
|
} else {
|
|
console.error(`[Profile] Loaded ${Object.keys(profiles).length} profile(s). No default selected — Claude must call switch_wms_profile before any API call`);
|
|
}
|
|
}
|
|
|
|
function listProfiles() {
|
|
return Object.keys(profiles).sort();
|
|
}
|
|
|
|
function getCurrentName() {
|
|
return currentProfileName;
|
|
}
|
|
|
|
/**
|
|
* Return the active profile config. Throws a helpful error if none is selected,
|
|
* so tool responses tell Claude exactly what to do next.
|
|
*/
|
|
function getCurrent() {
|
|
if (!currentProfileName) {
|
|
const available = listProfiles();
|
|
const hint = available.length > 0
|
|
? `Available profiles: ${available.join(', ')}. Call switch_wms_profile with one of them.`
|
|
: 'No profiles configured in .env (WMS_PROFILES is empty).';
|
|
throw new Error(`No WMS profile selected. ${hint}`);
|
|
}
|
|
return profiles[currentProfileName];
|
|
}
|
|
|
|
/**
|
|
* Switch to a different profile. Notifies listeners (services) so they can
|
|
* reset token + invalidate caches tied to the previous profile.
|
|
*/
|
|
function switchTo(name) {
|
|
if (!profiles[name]) {
|
|
const available = listProfiles();
|
|
throw new Error(`Unknown profile "${name}". Available: ${available.join(', ') || '(none)'}`);
|
|
}
|
|
if (name === currentProfileName) {
|
|
return profiles[name];
|
|
}
|
|
const previous = currentProfileName;
|
|
currentProfileName = name;
|
|
console.error(`[Profile] Switched: ${previous || '(none)'} → ${name}`);
|
|
for (const listener of listeners) {
|
|
try {
|
|
listener({ previous, current: name });
|
|
} catch (err) {
|
|
console.error(`[Profile] Listener error: ${err.message}`);
|
|
}
|
|
}
|
|
return profiles[name];
|
|
}
|
|
|
|
/**
|
|
* Register a callback fired whenever the active profile changes.
|
|
* Used by services to invalidate caches / reset tokens.
|
|
*/
|
|
function onSwitch(listener) {
|
|
listeners.push(listener);
|
|
}
|
|
|
|
module.exports = {
|
|
loadProfiles,
|
|
listProfiles,
|
|
getCurrent,
|
|
getCurrentName,
|
|
switchTo,
|
|
onSwitch,
|
|
};
|