mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-12 15:03:55 +09:00
(1) New %sk-state placeholder + .sk-state class in _design-system.scss (mono uppercase state chip, 7 tone modifiers); the 5 page-local copies (.mon-state/.sec-state/.inc-state+.inc-impact/.git-chip/.bk-type) now @extend it - class names and JSX untouched, compiled output verified equivalent. (2) Dead connectionColors export removed from connectionRules.js (zero consumers). (3) _email.scss globals evicted: .status-badge deleted (its unique content was dead variants + a phantom ::before dot artifact in two text badges - strict improvement), .empty-state moved to _empty-state.scss via an html-prefixed bridge that preserves the cascade over page-partial copies. (4) TokenModal blocks in _servers.scss re-tokened (--bg-code scripts/connection string, eyebrow labels, green status dot) - no other selectors in the file changed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
213 lines
5.8 KiB
JavaScript
213 lines
5.8 KiB
JavaScript
/**
|
|
* Connection validation rules for workflow nodes
|
|
*
|
|
* Valid connections:
|
|
* - domain (output) → dockerApp (input): Domain routes to app
|
|
* - dockerApp (database) → database (input): App connects to database
|
|
* - dockerApp (output) → dockerApp (input): App-to-app connection
|
|
* - service (output) → dockerApp (input): Service connects to app
|
|
* - service (output) → database (input): Service connects to database
|
|
*/
|
|
|
|
// Connection rules matrix: [sourceType][sourceHandle] → [targetType][targetHandle]
|
|
const automationTargets = {
|
|
dockerApp: ['input'],
|
|
service: ['input'],
|
|
script: ['input'],
|
|
notification: ['input'],
|
|
logic_if: ['input']
|
|
};
|
|
|
|
const connectionRules = {
|
|
domain: {
|
|
output: {
|
|
dockerApp: ['input'],
|
|
service: ['input']
|
|
}
|
|
},
|
|
dockerApp: {
|
|
output: {
|
|
dockerApp: ['input'],
|
|
service: ['input']
|
|
},
|
|
database: {
|
|
database: ['input']
|
|
}
|
|
},
|
|
service: {
|
|
output: {
|
|
dockerApp: ['input'],
|
|
database: ['input'],
|
|
service: ['input']
|
|
}
|
|
},
|
|
trigger: {
|
|
output: automationTargets
|
|
},
|
|
script: {
|
|
output: automationTargets
|
|
},
|
|
notification: {
|
|
output: automationTargets
|
|
},
|
|
logic_if: {
|
|
true: automationTargets,
|
|
false: automationTargets
|
|
},
|
|
database: {
|
|
// Database has no source handles, cannot connect to anything
|
|
}
|
|
};
|
|
|
|
// Labels for connection types
|
|
export const connectionLabels = {
|
|
'domain-dockerApp': 'Routes to',
|
|
'domain-service': 'Routes to',
|
|
'dockerApp-dockerApp': 'Connects to',
|
|
'dockerApp-database': 'Uses DB',
|
|
'service-dockerApp': 'Provides',
|
|
'service-database': 'Uses',
|
|
'service-service': 'Connects to',
|
|
'trigger-script': 'Triggers',
|
|
'trigger-notification': 'Triggers',
|
|
'trigger-dockerApp': 'Starts',
|
|
'script-script': 'Next',
|
|
'script-notification': 'Then notify',
|
|
'logic_if-script': 'If so',
|
|
'logic_if-notification': 'If so notify'
|
|
};
|
|
|
|
/**
|
|
* Find a node by ID from the nodes array
|
|
*/
|
|
const findNode = (nodeId, nodes) => {
|
|
return nodes.find(n => n.id === nodeId);
|
|
};
|
|
|
|
/**
|
|
* Check if a connection is valid based on node types and handles
|
|
* @param {Object} connection - { source, target, sourceHandle, targetHandle }
|
|
* @param {Array} nodes - Array of nodes
|
|
* @returns {boolean}
|
|
*/
|
|
export const isValidConnection = (connection, nodes) => {
|
|
const { source, target, sourceHandle, targetHandle } = connection;
|
|
|
|
// Prevent self-connections
|
|
if (source === target) {
|
|
return false;
|
|
}
|
|
|
|
const sourceNode = findNode(source, nodes);
|
|
const targetNode = findNode(target, nodes);
|
|
|
|
if (!sourceNode || !targetNode) {
|
|
return false;
|
|
}
|
|
|
|
const sourceType = sourceNode.type;
|
|
const targetType = targetNode.type;
|
|
|
|
// Check if source type exists in rules
|
|
if (!connectionRules[sourceType]) {
|
|
return false;
|
|
}
|
|
|
|
// Check if source handle exists in rules
|
|
const sourceHandleRules = connectionRules[sourceType][sourceHandle];
|
|
if (!sourceHandleRules) {
|
|
return false;
|
|
}
|
|
|
|
// Check if target type is allowed from this source handle
|
|
const allowedTargetHandles = sourceHandleRules[targetType];
|
|
if (!allowedTargetHandles) {
|
|
return false;
|
|
}
|
|
|
|
// Check if target handle is in allowed list
|
|
return allowedTargetHandles.includes(targetHandle);
|
|
};
|
|
|
|
/**
|
|
* Get an error message for an invalid connection
|
|
* @param {Object} connection - { source, target, sourceHandle, targetHandle }
|
|
* @param {Array} nodes - Array of nodes
|
|
* @returns {string|null} - Error message or null if valid
|
|
*/
|
|
export const getConnectionError = (connection, nodes) => {
|
|
const { source, target, sourceHandle, targetHandle } = connection;
|
|
|
|
if (source === target) {
|
|
return 'Cannot connect a node to itself';
|
|
}
|
|
|
|
const sourceNode = findNode(source, nodes);
|
|
const targetNode = findNode(target, nodes);
|
|
|
|
if (!sourceNode || !targetNode) {
|
|
return 'Invalid node reference';
|
|
}
|
|
|
|
const sourceType = sourceNode.type;
|
|
const targetType = targetNode.type;
|
|
|
|
// Check if source type can make outgoing connections
|
|
if (!connectionRules[sourceType] || Object.keys(connectionRules[sourceType]).length === 0) {
|
|
return `${formatNodeType(sourceType)} cannot create outgoing connections`;
|
|
}
|
|
|
|
// Check if source handle exists
|
|
if (!connectionRules[sourceType][sourceHandle]) {
|
|
return `Invalid source handle "${sourceHandle}"`;
|
|
}
|
|
|
|
// Check if target type is valid for this source
|
|
const sourceHandleRules = connectionRules[sourceType][sourceHandle];
|
|
if (!sourceHandleRules[targetType]) {
|
|
return `Cannot connect ${formatNodeType(sourceType)} to ${formatNodeType(targetType)}`;
|
|
}
|
|
|
|
// Check target handle
|
|
const allowedTargetHandles = sourceHandleRules[targetType];
|
|
if (!allowedTargetHandles.includes(targetHandle)) {
|
|
return `Invalid connection point on ${formatNodeType(targetType)}`;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Get the connection type key for labels/colors
|
|
* @param {string} sourceType - Source node type
|
|
* @param {string} targetType - Target node type
|
|
* @returns {string}
|
|
*/
|
|
export const getConnectionType = (sourceType, targetType) => {
|
|
return `${sourceType}-${targetType}`;
|
|
};
|
|
|
|
/**
|
|
* Format node type for display
|
|
*/
|
|
const formatNodeType = (type) => {
|
|
const labels = {
|
|
dockerApp: 'Docker App',
|
|
database: 'Database',
|
|
domain: 'Domain',
|
|
service: 'Service',
|
|
trigger: 'Trigger',
|
|
script: 'Script',
|
|
notification: 'Notification',
|
|
logic_if: 'Logic'
|
|
};
|
|
return labels[type] || type;
|
|
};
|
|
|
|
export default {
|
|
isValidConnection,
|
|
getConnectionError,
|
|
getConnectionType,
|
|
connectionLabels
|
|
};
|