Skip to content
Closed
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
14 changes: 8 additions & 6 deletions runtime/drivers/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ var spec = drivers.Spec{
Description: "Connect to Azure Blob Storage.",
DocsURL: "https://docs.rilldata.com/build/connectors/data-source/azure",
ConfigProperties: []*drivers.PropertySpec{
{
Key: "azure_storage_connection_string",
Type: drivers.StringPropertyType,
DisplayName: "Azure Connection String",
Description: "Azure connection string for storage account",
Placeholder: "Paste your Azure connection string here",
Secret: true,
},
{
Key: "azure_storage_account",
Type: drivers.StringPropertyType,
Expand All @@ -38,13 +46,7 @@ var spec = drivers.Spec{
Type: drivers.StringPropertyType,
Secret: true,
},
{
Key: "azure_storage_connection_string",
Type: drivers.StringPropertyType,
Secret: true,
},
},
// Important: Any edits to the below properties must be accompanied by changes to the client-side form validation schemas.
SourceProperties: []*drivers.PropertySpec{
{
Key: "path",
Expand Down
32 changes: 25 additions & 7 deletions web-common/src/features/sources/modal/AddDataForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import { connectorStepStore } from "./connectorStepStore";
import FormRenderer from "./FormRenderer.svelte";
import YamlPreview from "./YamlPreview.svelte";
import AzureMultiStepForm from "./AzureMultiStepForm.svelte";
import GCSMultiStepForm from "./GCSMultiStepForm.svelte";
import { AddDataFormManager } from "./AddDataFormManager";
import { hasOnlyDsn } from "./utils";
Expand Down Expand Up @@ -383,13 +384,30 @@
enhance={paramsEnhance}
onSubmit={paramsSubmit}
>
<GCSMultiStepForm
properties={filteredParamsProperties}
{paramsForm}
paramsErrors={$paramsErrors}
{onStringInputChange}
{handleFileUpload}
/>
{#if connector.name === "gcs"}
<GCSMultiStepForm
properties={filteredParamsProperties}
{paramsForm}
paramsErrors={$paramsErrors}
{onStringInputChange}
{handleFileUpload}
/>
{:else if connector.name === "azure"}
<AzureMultiStepForm
properties={filteredParamsProperties}
{paramsForm}
paramsErrors={$paramsErrors}
{onStringInputChange}
/>
{:else}
<FormRenderer
properties={filteredParamsProperties}
form={paramsForm}
errors={$paramsErrors}
{onStringInputChange}
uploadFile={handleFileUpload}
/>
{/if}
</AddDataFormSection>
{:else}
<!-- GCS Step 2: Source configuration -->
Expand Down
12 changes: 9 additions & 3 deletions web-common/src/features/sources/modal/AddDataFormManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,18 @@ export class AddDataFormManager {

const isSourceForm = formType === "source";
const isConnectorForm = formType === "connector";
const isMultiStep = MULTI_STEP_CONNECTORS.includes(connector.name ?? "");

// Base properties
// For multi-step connectors (e.g., gcs, azure), step 1 always configures the connector,
// even when the overall formType is "source". So prefer configProperties here.
this.properties =
(isSourceForm
? connector.sourceProperties
: connector.configProperties?.filter((p) => p.key !== "dsn")) ?? [];
(isMultiStep
? connector.configProperties
: isSourceForm
? connector.sourceProperties
: connector.configProperties
)?.filter((p) => p.key !== "dsn") ?? [];

// Filter properties based on connector type
this.filteredParamsProperties = (() => {
Expand Down
2 changes: 2 additions & 0 deletions web-common/src/features/sources/modal/AddDataModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
});

function goToConnectorForm(connector: V1ConnectorDriver) {
// Ensure multi-step connectors always start on the connector step
resetConnectorStep();
const state = {
step: 2,
selectedConnector: connector,
Expand Down
164 changes: 164 additions & 0 deletions web-common/src/features/sources/modal/AzureMultiStepForm.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<script lang="ts">
import Input from "@rilldata/web-common/components/forms/Input.svelte";
import Checkbox from "@rilldata/web-common/components/forms/Checkbox.svelte";
import InformationalField from "@rilldata/web-common/components/forms/InformationalField.svelte";
import Radio from "@rilldata/web-common/components/forms/Radio.svelte";
import { ConnectorDriverPropertyType } from "@rilldata/web-common/runtime-client";
import { normalizeErrors } from "./utils";
import { AZURE_AUTH_OPTIONS, type AzureAuthMethod } from "./constants";

export let properties: any[] = [];
export let paramsForm: any;
export let paramsErrors: Record<string, any>;
export let onStringInputChange: (e: Event) => void;

let azureAuthMethod: AzureAuthMethod = "connection_string";

const filteredParamsProperties = properties;

function clearFields(keys: string[]) {
paramsForm.update(
($form) => {
for (const key of keys) {
$form[key] = "";
}
return $form;
},
{ taint: false },
);
}

$: if (azureAuthMethod === "connection_string") {
clearFields([
"azure_storage_account",
"azure_storage_key",
"azure_storage_sas_token",
]);
} else if (azureAuthMethod === "storage_key") {
clearFields(["azure_storage_connection_string", "azure_storage_sas_token"]);
} else if (azureAuthMethod === "sas_token") {
clearFields(["azure_storage_connection_string", "azure_storage_key"]);
} else if (azureAuthMethod === "public") {
clearFields([
"azure_storage_connection_string",
"azure_storage_account",
"azure_storage_key",
"azure_storage_sas_token",
]);
}
</script>

<div>
<div class="py-1.5 first:pt-0 last:pb-0">
<div class="text-sm font-medium mb-4">Authentication</div>
<Radio
bind:value={azureAuthMethod}
options={AZURE_AUTH_OPTIONS}
name="azure-auth-method"
>
<svelte:fragment slot="custom-content" let:option>
{#if option.value === "connection_string"}
<Input
id="azure_storage_connection_string"
label="Connection String"
placeholder="Enter your Azure storage connection string"
optional={false}
secret={true}
hint="Paste an Azure Storage connection string"
errors={normalizeErrors(
paramsErrors.azure_storage_connection_string,
)}
bind:value={$paramsForm.azure_storage_connection_string}
alwaysShowError
/>
{:else if option.value === "storage_key"}
<div class="space-y-3">
<Input
id="azure_storage_account"
label="Storage Account Name"
placeholder="Enter your Azure storage account name"
optional={false}
secret={false}
hint="Exact name of the Azure storage account"
errors={normalizeErrors(paramsErrors.azure_storage_account)}
bind:value={$paramsForm.azure_storage_account}
alwaysShowError
/>
<Input
id="azure_storage_key"
label="Storage Account Key"
placeholder="Enter your storage account key"
optional={false}
secret={true}
hint="Primary or secondary storage account key"
errors={normalizeErrors(paramsErrors.azure_storage_key)}
bind:value={$paramsForm.azure_storage_key}
alwaysShowError
/>
</div>
{:else if option.value === "sas_token"}
<div class="space-y-3">
<Input
id="azure_storage_account"
label="Storage Account Name"
placeholder="Enter your Azure storage account name"
optional={false}
secret={false}
hint="Account hosting the blob container"
errors={normalizeErrors(paramsErrors.azure_storage_account)}
bind:value={$paramsForm.azure_storage_account}
alwaysShowError
/>
<Input
id="azure_storage_sas_token"
label="SAS Token"
placeholder="Enter your SAS token"
optional={false}
secret={true}
hint="Shared Access Signature token with blob permissions"
errors={normalizeErrors(paramsErrors.azure_storage_sas_token)}
bind:value={$paramsForm.azure_storage_sas_token}
alwaysShowError
/>
</div>
{/if}
</svelte:fragment>
</Radio>
</div>

{#each filteredParamsProperties as property (property.key)}
{@const propertyKey = property.key ?? ""}
{#if propertyKey !== "path" && propertyKey !== "azure_storage_connection_string" && propertyKey !== "azure_storage_account" && propertyKey !== "azure_storage_key" && propertyKey !== "azure_storage_sas_token"}
<div class="py-1.5 first:pt-0 last:pb-0">
{#if property.type === ConnectorDriverPropertyType.TYPE_STRING || property.type === ConnectorDriverPropertyType.TYPE_NUMBER}
<Input
id={propertyKey}
label={property.displayName}
placeholder={property.placeholder}
optional={!property.required}
secret={property.secret}
hint={property.hint}
errors={normalizeErrors(paramsErrors[propertyKey])}
bind:value={$paramsForm[propertyKey]}
onInput={(_, e) => onStringInputChange(e)}
alwaysShowError
/>
{:else if property.type === ConnectorDriverPropertyType.TYPE_BOOLEAN}
<Checkbox
id={propertyKey}
bind:checked={$paramsForm[propertyKey]}
label={property.displayName}
hint={property.hint}
optional={!property.required}
/>
{:else if property.type === ConnectorDriverPropertyType.TYPE_INFORMATIONAL}
<InformationalField
description={property.description}
hint={property.hint}
href={property.docsUrl}
/>
{/if}
</div>
{/if}
{/each}
</div>
30 changes: 29 additions & 1 deletion web-common/src/features/sources/modal/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export const CONNECTION_TAB_OPTIONS: { value: string; label: string }[] = [
];

export type GCSAuthMethod = "credentials" | "hmac";
export type AzureAuthMethod =
| "connection_string"
| "storage_key"
| "sas_token"
| "public";

export const GCS_AUTH_OPTIONS: {
value: GCSAuthMethod;
Expand All @@ -39,6 +44,29 @@ export const GCS_AUTH_OPTIONS: {
},
];

export const AZURE_AUTH_OPTIONS: {
value: AzureAuthMethod;
label: string;
description: string;
hint?: string;
}[] = [
{
value: "connection_string",
label: "Connection String",
description: "Alternative for cloud deployment",
},
{
value: "storage_key",
label: "Storage Account Key",
description: "Recommended for cloud deployment",
},
{
value: "sas_token",
label: "Shared Access Signature (SAS) Token",
description: "Most secure, fine-grained control",
},
];

// pre-defined order for sources
export const SOURCES = [
"athena",
Expand Down Expand Up @@ -67,7 +95,7 @@ export const OLAP_ENGINES = [
export const ALL_CONNECTORS = [...SOURCES, ...OLAP_ENGINES];

// Connectors that support multi-step forms (connector -> source)
export const MULTI_STEP_CONNECTORS = ["gcs"];
export const MULTI_STEP_CONNECTORS = ["gcs", "azure"];

export const FORM_HEIGHT_TALL = "max-h-[38.5rem] min-h-[38.5rem]";
export const FORM_HEIGHT_DEFAULT = "max-h-[34.5rem] min-h-[34.5rem]";
Expand Down
9 changes: 6 additions & 3 deletions web-common/src/features/sources/modal/yupSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,21 @@ export const getYupSchema = {
}),

azure: yup.object().shape({
azure_storage_connection_string: yup.string(),
azure_storage_account: yup.string(),
azure_storage_key: yup.string(),
azure_storage_sas_token: yup.string(),
path: yup
.string()
.matches(
/^azure:\/\//,
"Must be an Azure URI (e.g. azure://container/path)",
)
.required("Path is required"),
azure_storage_account: yup.string(),
.optional(),
name: yup
.string()
.matches(VALID_NAME_PATTERN, INVALID_NAME_MESSAGE)
.required("Source name is required"),
.optional(),
}),

postgres: yup.object().shape({
Expand Down
Loading