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
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
export let open = false;
export let organization: string;
export let project: string;
export let hideTrigger = false;

const FORM_ID = "github-connect-form";

Expand Down Expand Up @@ -159,26 +160,36 @@
}
}}
>
<Dialog.Trigger asChild let:builder>
<Button
builders={[builder]}
type="primary"
class="w-fit mt-1"
loading={$userStatus.isFetching}
onClick={() => void githubAccessManager.ensureGithubAccess()}
>
<Github className="w-4 h-4" />
Connect to GitHub
</Button>
</Dialog.Trigger>
{#if !hideTrigger}
<Dialog.Trigger asChild let:builder>
<Button
builders={[builder]}
type="primary"
class="w-fit mt-1"
loading={$userStatus.isFetching}
onClick={() => void githubAccessManager.ensureGithubAccess()}
>
<Github className="w-4 h-4" />
Connect to GitHub
</Button>
</Dialog.Trigger>
{/if}
<Dialog.Content class="translate-y-[-200px]">
<Dialog.Header>
<div class="flex flex-row gap-x-2 items-center">
<Github size="40px" />
<div class="flex flex-col gap-y-1">
<Dialog.Title>Connect to GitHub</Dialog.Title>
<Dialog.Description>
Connect this project to a new repo.
Enable version control and collaboration for your project.
<a
href="https://docs.rilldata.com/deploy/deploy-dashboard/github-101"
target="_blank"
rel="noreferrer noopener"
class="text-primary-600 hover:underline"
>
Learn more
</a>
</Dialog.Description>
</div>
</div>
Expand Down
162 changes: 162 additions & 0 deletions web-admin/src/features/projects/status/ProjectAiConnector.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<script lang="ts">
import Spinner from "@rilldata/web-common/features/entity-management/Spinner.svelte";
import { EntityStatus } from "@rilldata/web-common/features/entity-management/types";
import { createRuntimeServiceGetInstance } from "@rilldata/web-common/runtime-client";
import { runtime } from "@rilldata/web-common/runtime-client/runtime-store";
import RillFilled from "@rilldata/web-common/components/icons/RillFilled.svelte";
import ConnectorIcon from "@rilldata/web-common/components/icons/ConnectorIcon.svelte";

$: ({ instanceId } = $runtime);

$: instanceQuery = createRuntimeServiceGetInstance(instanceId);
$: ({ data: instanceData, isLoading, error } = $instanceQuery);

// Get the active AI connector name
$: aiConnector = instanceData?.instance?.aiConnector;

// Check if this is a user-configured connector (exists in projectConnectors)
$: userConnectorConfig = instanceData?.instance?.projectConnectors?.find(
(c) => c.name === aiConnector,
);

$: isUserConfigured = !!userConnectorConfig;

// Extract config properties for user-provided connectors (excluding secrets)
$: configDetails = getConfigDetails(userConnectorConfig?.config);

function getConfigDetails(
config: Record<string, unknown> | undefined,
): { label: string; value: string }[] {
if (!config) return [];

const details: { label: string; value: string }[] = [];
const displayOrder = ["model", "base_url", "api_type", "api_version"];

for (const key of displayOrder) {
const value = config[key];
if (value && typeof value === "string" && !isSecretValue(value)) {
details.push({
label: getDisplayLabel(key),
value: value,
});
}
}

return details;
}

function getDisplayLabel(key: string): string {
const labels: Record<string, string> = {
model: "Model",
base_url: "Base URL",
api_type: "API Type",
api_version: "API Version",
};
return labels[key] ?? key;
}

function isSecretValue(value: string): boolean {
// Don't display templated values or anything that looks like a secret
return (
value.includes("{{") ||
value.includes("}}") ||
value.toLowerCase().includes("secret") ||
value.startsWith("sk-")
);
}

function getDriverDisplayName(driver: string | undefined): string {
if (!driver) return "AI";
const names: Record<string, string> = {
openai: "OpenAI",
admin: "Rill AI",
};
return names[driver] ?? driver;
}
</script>

<section class="ai-connector">
<h3 class="ai-label">AI</h3>
{#if isLoading}
<div class="py-1">
<Spinner status={EntityStatus.Running} size="16px" />
</div>
{:else if error}
<div class="py-0.5">
<span class="text-red-600">Error loading AI connector</span>
</div>
{:else if isUserConfigured}
<div class="ai-display">
<div class="ai-header">
<ConnectorIcon size="16px" />
<span class="ai-name">
{getDriverDisplayName(userConnectorConfig?.type)}
</span>
</div>
{#if configDetails.length > 0}
<div class="ai-details">
{#each configDetails as detail}
<div class="ai-detail-row">
<span class="ai-detail-label">{detail.label}:</span>
<span class="ai-detail-value">{detail.value}</span>
</div>
{/each}
</div>
{/if}
</div>
{:else}
<div class="ai-display">
<div class="ai-header">
<RillFilled size="16" />
<span class="ai-name">
Rill AI
<span class="ai-default">(default)</span>
</span>
</div>
</div>
{/if}
</section>

<style lang="postcss">
.ai-connector {
@apply flex flex-col gap-y-1;
}

.ai-label {
@apply text-[10px] leading-none font-semibold uppercase;
@apply text-gray-500;
}

.ai-display {
@apply flex flex-col gap-y-1;
}

.ai-header {
@apply flex items-center gap-x-1.5;
}

.ai-name {
@apply text-[12px] font-semibold text-gray-800;
}

.ai-default {
@apply text-gray-500 font-normal;
}

.ai-details {
@apply flex flex-col gap-y-0.5 pl-6;
}

.ai-detail-row {
@apply flex items-center gap-x-1;
@apply text-[11px] text-gray-600;
}

.ai-detail-label {
@apply font-mono;
}

.ai-detail-value {
@apply text-gray-800;
}
</style>
104 changes: 104 additions & 0 deletions web-admin/src/features/projects/status/ProjectOlapConnector.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<script lang="ts">
import Spinner from "@rilldata/web-common/features/entity-management/Spinner.svelte";
import { EntityStatus } from "@rilldata/web-common/features/entity-management/types";
import { connectorIconMapping } from "@rilldata/web-common/features/connectors/connector-icon-mapping";
import { createRuntimeServiceGetInstance } from "@rilldata/web-common/runtime-client";
import { runtime } from "@rilldata/web-common/runtime-client/runtime-store";
import ConnectorIcon from "@rilldata/web-common/components/icons/ConnectorIcon.svelte";

$: ({ instanceId } = $runtime);

$: instanceQuery = createRuntimeServiceGetInstance(instanceId);
$: ({ data: instanceData, isLoading, error } = $instanceQuery);

// Get the active OLAP connector name
$: olapConnector = instanceData?.instance?.olapConnector ?? "duckdb";

// Check if this is a user-configured connector (exists in projectConnectors)
$: isUserConfigured = instanceData?.instance?.projectConnectors?.some(
(c) => c.name === olapConnector,
);

// Find the connector to get the driver type
$: connectorConfig = instanceData?.instance?.projectConnectors?.find(
(c) => c.name === olapConnector,
);

// For user-configured connectors, use the driver type; otherwise use the connector name
$: driverType = connectorConfig?.type ?? olapConnector;

// Get display name (capitalize first letter)
$: displayName = getDisplayName(driverType);

// Get the icon component for the connector
$: IconComponent =
connectorIconMapping[driverType as keyof typeof connectorIconMapping];

function getDisplayName(driver: string): string {
const displayNames: Record<string, string> = {
duckdb: "DuckDB",
clickhouse: "ClickHouse",
clickhousecloud: "ClickHouse Cloud",
druid: "Apache Druid",
pinot: "Apache Pinot",
bigquery: "BigQuery",
snowflake: "Snowflake",
postgres: "PostgreSQL",
mysql: "MySQL",
athena: "Athena",
redshift: "Redshift",
motherduck: "MotherDuck",
};
return displayNames[driver] ?? driver;
}
</script>

<section class="olap-connector">
<h3 class="olap-label">OLAP Engine</h3>
{#if isLoading}
<div class="py-1">
<Spinner status={EntityStatus.Running} size="16px" />
</div>
{:else if error}
<div class="py-0.5">
<span class="text-red-600">Error loading OLAP connector</span>
</div>
{:else}
<div class="olap-display">
{#if IconComponent}
<svelte:component this={IconComponent} size="16px" />
{:else}
<ConnectorIcon size="16px" />
{/if}
<span class="olap-name">
{displayName}
{#if !isUserConfigured}
<span class="olap-default">(default)</span>
{/if}
</span>
</div>
{/if}
</section>

<style lang="postcss">
.olap-connector {
@apply flex flex-col gap-y-1;
}

.olap-label {
@apply text-[10px] leading-none font-semibold uppercase;
@apply text-gray-500;
}

.olap-display {
@apply flex items-center gap-x-1.5;
}

.olap-name {
@apply text-[12px] font-semibold text-gray-800;
}

.olap-default {
@apply text-gray-500 font-normal;
}
</style>
2 changes: 1 addition & 1 deletion web-admin/src/features/projects/status/display-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export const deploymentChipDisplays: Record<V1DeploymentStatus, StatusDisplay> =
[V1DeploymentStatus.DEPLOYMENT_STATUS_RUNNING]: {
icon: CheckCircle,
iconProps: { className: "text-primary-600 hover:text-primary-500" },
text: "Ready",
text: "Running",
textClass: "text-primary-600",
wrapperClass: "bg-primary-50 border-primary-300",
},
Expand Down
37 changes: 37 additions & 0 deletions web-admin/src/features/projects/status/project-info/InfoRow.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script lang="ts">
export let label: string;
</script>

<div class="info-row">
<dt class="info-label">{label}</dt>
<dd class="info-value">
<slot />
</dd>
</div>

<style lang="postcss">
.info-row {
@apply flex items-center;
@apply border-b border-slate-200;
@apply min-h-[44px];
}

.info-row:last-of-type {
@apply border-b-0;
}

.info-label {
@apply w-[140px] flex-shrink-0;
@apply px-4 py-3;
@apply text-sm font-medium text-gray-600;
@apply bg-slate-50;
@apply border-r border-slate-200;
@apply whitespace-nowrap;
}

.info-value {
@apply flex-1 px-4 py-3;
@apply text-sm;
@apply m-0; /* Reset default dd margin */
}
</style>
Loading
Loading