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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "deeploy-dapp",
"private": true,
"type": "module",
"version": "0.1.0",
"version": "0.1.1",
"scripts": {
"dev": "vite",
"dev:logs": "vite --debug",
Expand Down
1 change: 1 addition & 0 deletions src/components/create-job/JobFormWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ function JobFormWrapper({ projectName, draftJobsCount }) {
},
restartPolicy: POLICY_TYPES[0],
imagePullPolicy: POLICY_TYPES[0],
customParams: [],
},
});

Expand Down
4 changes: 4 additions & 0 deletions src/components/create-job/plugins/GenericPluginSections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import ConfigSectionTitle from '@components/job/config/ConfigSectionTitle';
import DynamicEnvSection from '@shared/jobs/DynamicEnvSection';
import FileVolumesSection from '@shared/jobs/FileVolumesSection';
import KeyValueEntriesSection from '@shared/jobs/KeyValueEntriesSection';
import CustomParametersSection from '@shared/jobs/native/CustomParametersSection';
import PortMappingSection from '@shared/PortMappingSection';
import AppParametersSection from '../sections/AppParametersSection';
import PluginEnvVariablesSection from '../sections/PluginEnvVariablesSection';
Expand Down Expand Up @@ -34,6 +35,9 @@ export default function GenericPluginSections({ name }: { name: string }) {

<ConfigSectionTitle title="Policies" />
<PoliciesSection baseName={name} />

<ConfigSectionTitle title="Custom Parameters" />
<CustomParametersSection baseName={name} />
</>
);
}
12 changes: 11 additions & 1 deletion src/components/create-job/steps/deployment/GenericDeployment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import DynamicEnvSection from '@shared/jobs/DynamicEnvSection';
import EnvVariablesCard from '@shared/jobs/EnvVariablesCard';
import FileVolumesSection from '@shared/jobs/FileVolumesSection';
import KeyValueEntriesSection from '@shared/jobs/KeyValueEntriesSection';
import CustomParametersSection from '@shared/jobs/native/CustomParametersSection';
import TargetNodesCard from '@shared/jobs/target-nodes/TargetNodesCard';
import PortMappingSection from '@shared/PortMappingSection';

Expand All @@ -15,7 +16,12 @@ function GenericDeployment({ isEditingRunningJob }: { isEditingRunningJob?: bool
<div className="col gap-6">
<SlateCard title="App Identity">
<div className="flex gap-4">
<InputWithLabel name="deployment.jobAlias" label="Alias" placeholder="My App" />
<InputWithLabel
name="deployment.jobAlias"
label="Alias"
placeholder="My App"
isDisabled={isEditingRunningJob}
/>
</div>
</SlateCard>

Expand Down Expand Up @@ -53,6 +59,10 @@ function GenericDeployment({ isEditingRunningJob }: { isEditingRunningJob?: bool
<SlateCard title="Policies">
<PoliciesSection />
</SlateCard>

<SlateCard title="Custom Parameters">
<CustomParametersSection baseName="deployment" />
</SlateCard>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ function NativeDeployment({ isEditingRunningJob }: { isEditingRunningJob?: boole
return (
<div className="col gap-6">
<SlateCard title="App Identity">
<InputWithLabel name="deployment.jobAlias" label="Alias" placeholder="My App" />
<InputWithLabel
name="deployment.jobAlias"
label="Alias"
placeholder="My App"
isDisabled={isEditingRunningJob}
/>
</SlateCard>

<TargetNodesCard isEditingRunningJob={isEditingRunningJob} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,12 @@ function ServiceDeployment({ isEditingRunningJob }: { isEditingRunningJob?: bool
<div className="col gap-6">
<SlateCard title="Service Identity">
<div className="flex gap-4">
<InputWithLabel name="deployment.jobAlias" label="Alias" placeholder="Service" />
<InputWithLabel
name="deployment.jobAlias"
label="Alias"
placeholder="Service"
isDisabled={isEditingRunningJob}
/>
</div>
</SlateCard>

Expand Down
43 changes: 31 additions & 12 deletions src/components/edit-job/JobEditFormWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@ import Plugins from '@components/create-job/steps/Plugins';
import Services from '@components/create-job/steps/Services';
import Specifications from '@components/create-job/steps/Specifications';
import { BOOLEAN_TYPES } from '@data/booleanTypes';
import { PLUGIN_SIGNATURE_TYPES } from '@data/pluginSignatureTypes';
import { getRunningService } from '@data/containerResources';
import { CR_VISIBILITY_OPTIONS } from '@data/crVisibilityOptions';
import { zodResolver } from '@hookform/resolvers/zod';
import { DeploymentContextType, useDeploymentContext } from '@lib/contexts/deployment';
import { boolToBooleanType, isGenericPlugin, NATIVE_PLUGIN_DEFAULT_RESPONSE_KEYS, titlecase } from '@lib/deeploy-utils';
import {
boolToBooleanType,
GENERIC_JOB_RESERVED_KEYS,
isGenericPlugin,
NATIVE_PLUGIN_DEFAULT_RESPONSE_KEYS,
titlecase,
} from '@lib/deeploy-utils';
import { Step, STEPS } from '@lib/steps/steps';
import { jobSchema } from '@schemas/index';
import JobFormHeaderInterface from '@shared/jobs/JobFormHeaderInterface';
Expand Down Expand Up @@ -111,6 +118,9 @@ export default function JobEditFormWrapper({
// Policies
restartPolicy: titlecase(config.RESTART_POLICY!),
imagePullPolicy: titlecase(config.IMAGE_PULL_POLICY!),

// Custom Parameters
customParams: formatCustomParams(config, GENERIC_JOB_RESERVED_KEYS),
});

const getGenericPluginSchemaDefaults = (config: JobConfig) => ({
Expand All @@ -122,18 +132,27 @@ export default function JobEditFormWrapper({
...getGenericSpecificDeploymentDefaults(config),
});

const getNativePluginSchemaDefaults = (pluginInfo: AppsPlugin & { signature: string }) => ({
basePluginType: BasePluginType.Native,
const getNativePluginSchemaDefaults = (pluginInfo: AppsPlugin & { signature: string }) => {
const isKnownSignature = PLUGIN_SIGNATURE_TYPES.includes(
pluginInfo.signature as (typeof PLUGIN_SIGNATURE_TYPES)[number],
);

// Signature
pluginSignature: pluginInfo.signature,
return {
basePluginType: BasePluginType.Native,

// Tunneling
...getBaseSchemaTunnelingDefaults(pluginInfo.instance_conf),
// Signature - if not in the predefined list, select CUSTOM and pre-fill customPluginSignature
pluginSignature: isKnownSignature
? pluginInfo.signature
: PLUGIN_SIGNATURE_TYPES[PLUGIN_SIGNATURE_TYPES.length - 1],
customPluginSignature: isKnownSignature ? undefined : pluginInfo.signature,

// Custom Parameters
customParams: formatCustomParams(pluginInfo.instance_conf),
});
// Tunneling
...getBaseSchemaTunnelingDefaults(pluginInfo.instance_conf),

// Custom Parameters
customParams: formatCustomParams(pluginInfo.instance_conf, NATIVE_PLUGIN_DEFAULT_RESPONSE_KEYS),
};
};

const getBaseSchemaDefaults = (config: JobConfig = jobConfig) => ({
jobType: job.resources.jobType,
Expand Down Expand Up @@ -252,11 +271,11 @@ export default function JobEditFormWrapper({
];
};

const formatCustomParams = (config: JobConfig) => {
const formatCustomParams = (config: JobConfig, reservedKeys: (keyof JobConfig)[]) => {
const customParams: CustomParameterEntry[] = [];

Object.entries(config).forEach(([key, value]) => {
if (!NATIVE_PLUGIN_DEFAULT_RESPONSE_KEYS.includes(key as keyof JobConfig)) {
if (!reservedKeys.includes(key as keyof JobConfig)) {
const valueType = typeof value === 'string' ? 'string' : 'json';

let parsedValue: string = '';
Expand Down
3 changes: 2 additions & 1 deletion src/data/pipelineInputTypes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export const PIPELINE_INPUT_TYPES = [
'Void',
'Loopback',
'JeevesListener',
'JeevesApiListener',
'JeevesEmbedAgentListener',
'JeevesLlmAgentListener',
'void',
] as const;
61 changes: 59 additions & 2 deletions src/lib/deeploy-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
import {
BasePluginType,
ContainerDeploymentType,
CustomParameterEntry,
GenericPlugin,
NativePlugin,
PluginType,
Expand Down Expand Up @@ -55,6 +56,41 @@ export const NATIVE_PLUGIN_DEFAULT_RESPONSE_KEYS: (keyof JobConfig)[] = [
'NGROK_USE_API',
];

// Keys that are system-managed and cannot be edited by users
export const SYSTEM_MANAGED_JOB_CONFIG_KEYS: (keyof JobConfig)[] = [
'CHAINSTORE_PEERS',
'CHAINSTORE_RESPONSE_KEY',
'INSTANCE_ID',
'TUNNEL_ENGINE',
'NGROK_AUTH_TOKEN',
'NGROK_EDGE_LABEL',
'NGROK_USE_API',
];

// Keys that are editable via dedicated UI sections in generic job deployment
export const GENERIC_JOB_UI_MANAGED_KEYS: (keyof JobConfig)[] = [
'ENV',
'DYNAMIC_ENV',
'VOLUMES',
'FILE_VOLUMES',
'CONTAINER_RESOURCES',
'PORT',
'TUNNEL_ENGINE_ENABLED',
'CLOUDFLARE_TOKEN',
'RESTART_POLICY',
'IMAGE_PULL_POLICY',
'IMAGE',
'VCS_DATA',
'CR_DATA',
'BUILD_AND_RUN_COMMANDS',
];

// Combined: all keys that should be excluded from custom parameters for generic jobs
export const GENERIC_JOB_RESERVED_KEYS: (keyof JobConfig)[] = [
...SYSTEM_MANAGED_JOB_CONFIG_KEYS,
...GENERIC_JOB_UI_MANAGED_KEYS,
];

export const getDiscountPercentage = (_paymentMonthsCount: number): number => {
// Disabled for now
return 0;
Expand Down Expand Up @@ -287,6 +323,20 @@ const formatNativeJobCustomParams = (plugin: NativePlugin) => {
return customParams;
};

const formatGenericJobCustomParams = (customParams: CustomParameterEntry[]) => {
const formatted: Record<string, any> = {};

if (!_.isEmpty(customParams)) {
customParams.forEach((param) => {
if (param.key) {
formatted[param.key] = parseIfJson(param.value);
}
});
}

return formatted;
};

export const formatGenericPluginConfigAndSignature = (
resources: {
cpu: number;
Expand Down Expand Up @@ -377,6 +427,8 @@ export const formatGenericJobPayload = (
deployment,
);

const customParams = formatGenericJobCustomParams(deployment.customParams);

const nonce = generateDeeployNonce();

return {
Expand All @@ -391,6 +443,7 @@ export const formatGenericJobPayload = (
{
plugin_signature: pluginSignature,
...pluginConfig,
...customParams,
},
],
pipeline_input_type: 'void',
Expand Down Expand Up @@ -424,16 +477,20 @@ export const formatNativeJobPayload = (
// Build plugins array
const plugins = deployment.plugins.map((plugin) => {
if (plugin.basePluginType === BasePluginType.Generic) {
const secondaryPluginNodeResources = formatContainerResources(workerType, (plugin as GenericPlugin).ports);
const genericPlugin = plugin as GenericPlugin;
const secondaryPluginNodeResources = formatContainerResources(workerType, genericPlugin.ports);

const { pluginConfig, pluginSignature } = formatGenericPluginConfigAndSignature(
secondaryPluginNodeResources,
plugin as GenericPlugin,
genericPlugin,
);

const customParams = formatGenericJobCustomParams(genericPlugin.customParams);

return {
plugin_signature: pluginSignature,
...pluginConfig,
...customParams,
};
}

Expand Down
4 changes: 4 additions & 0 deletions src/schemas/steps/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ const genericAppDeploymentSchemaWihtoutRefinements = baseDeploymentSchema.extend
fileVolumes: validations.fileVolumes,
restartPolicy: validations.restartPolicy,
imagePullPolicy: validations.imagePullPolicy,
customParams: validations.customParams,
});

export const genericAppDeploymentSchema = applyDeploymentTypeRefinements(
Expand Down Expand Up @@ -352,6 +353,9 @@ const genericPluginSchema = z.object({
// Policies
restartPolicy: z.enum(POLICY_TYPES, { required_error: 'Value is required' }),
imagePullPolicy: z.enum(POLICY_TYPES, { required_error: 'Value is required' }),

// Custom Parameters
customParams: validations.customParams,
});

const nativePluginSchema = z.object({
Expand Down
12 changes: 11 additions & 1 deletion src/shared/jobs/KeyValueEntriesSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ export default function KeyValueEntriesSection({
onChange={async (e) => {
const value = e.target.value;
field.onChange(value);

// Re-validate on change if field has error to clear it immediately
if (hasError) {
await trigger(`${name}.${index}.key`);
}
}}
onBlur={async () => {
field.onBlur();
Expand Down Expand Up @@ -183,9 +188,14 @@ export default function KeyValueEntriesSection({
<StyledInput
placeholder={placeholders[1]}
value={field.value ?? ''}
onChange={(e) => {
onChange={async (e) => {
const value = e.target.value;
field.onChange(value);

// Re-validate on change if field has error to clear it immediately
if (hasError) {
await trigger(`${name}.${index}.value`);
}
}}
onBlur={async () => {
field.onBlur();
Expand Down
27 changes: 18 additions & 9 deletions src/shared/jobs/deployment-type/WorkerSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,30 @@ export default function WorkerSection({
try {
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`);

if (!response.ok) {
throw new Error(`GitHub repository lookup failed with status ${response.status}`);
if (response.ok) {
// 200 - Repository is public
setValue(`${baseName}.deploymentType.repositoryVisibility`, 'public');
REPOS_CACHE[trimmedUrl] = 'public';
} else if (response.status === 404) {
// 404 - Repository is private or doesn't exist
setValue(`${baseName}.deploymentType.repositoryVisibility`, 'private');
REPOS_CACHE[trimmedUrl] = 'private';
} else {
// Other errors (e.g., 403 rate limit) - default to public (credentials optional)
// If it's actually private, user will get an error during deployment
console.warn(`GitHub API returned status ${response.status}, defaulting to public`);
setValue(`${baseName}.deploymentType.repositoryVisibility`, 'public');
// Don't cache rate-limited results
}

// console.log('Repository visibility is: public');
setValue(`${baseName}.deploymentType.repositoryVisibility`, 'public');
REPOS_CACHE[trimmedUrl] = 'public';
} catch (error: any) {
if (error?.name === 'AbortError') {
return;
}

// console.log('Repository visibility is: private');
setValue(`${baseName}.deploymentType.repositoryVisibility`, 'private');
REPOS_CACHE[trimmedUrl] = 'private';
// Network error - default to public (credentials optional)
console.error('Failed to check repository visibility:', error);
setValue(`${baseName}.deploymentType.repositoryVisibility`, 'public');
// Don't cache network errors
}
};

Expand Down
Loading