mirror of
https://github.com/Kong/insomnia.git
synced 2026-04-21 06:37:36 -04:00
Available mock server types should consider organizations storage rule
This commit is contained in:
@@ -24,6 +24,7 @@ import {
|
||||
isRemoteProject,
|
||||
type Project,
|
||||
} from '../../../models/project';
|
||||
import { ORG_STORAGE_RULE, type OrgStorageRuleType } from '../../routes/organization';
|
||||
import { Icon } from '../icon';
|
||||
import { showAlert, showModal } from '../modals';
|
||||
import { AskModal } from '../modals/ask-modal';
|
||||
@@ -31,7 +32,7 @@ import { AskModal } from '../modals/ask-modal';
|
||||
interface Props {
|
||||
project: Project & { hasUncommittedOrUnpushedChanges?: boolean };
|
||||
organizationId: string;
|
||||
storage: 'cloud_only' | 'local_only' | 'cloud_plus_local';
|
||||
storage: OrgStorageRuleType;
|
||||
}
|
||||
|
||||
interface ProjectActionItem {
|
||||
@@ -48,10 +49,10 @@ export const ProjectDropdown: FC<Props> = ({ project, organizationId, storage })
|
||||
const updateProjectFetcher = useFetcher();
|
||||
const [projectType, setProjectType] = useState<'local' | 'remote' | ''>('');
|
||||
|
||||
const isRemoteProjectInconsistent = isRemoteProject(project) && storage === 'local_only';
|
||||
const isLocalProjectInconsistent = !isRemoteProject(project) && storage === 'cloud_only';
|
||||
const isRemoteProjectInconsistent = isRemoteProject(project) && storage === ORG_STORAGE_RULE.LOCAL_ONLY;
|
||||
const isLocalProjectInconsistent = !isRemoteProject(project) && storage === ORG_STORAGE_RULE.CLOUD_ONLY;
|
||||
const isProjectInconsistent = isRemoteProjectInconsistent || isLocalProjectInconsistent;
|
||||
const showStorageRestrictionMessage = storage !== 'cloud_plus_local';
|
||||
const showStorageRestrictionMessage = storage !== ORG_STORAGE_RULE.CLOUD_PLUS_LOCAL;
|
||||
|
||||
const projectActionList: ProjectActionItem[] = [
|
||||
{
|
||||
@@ -123,7 +124,7 @@ export const ProjectDropdown: FC<Props> = ({ project, organizationId, storage })
|
||||
offset={4}
|
||||
className="border select-none text-sm max-w-xs border-solid border-[--hl-sm] shadow-lg bg-[--color-bg] text-[--color-font] px-4 py-2 rounded-md overflow-y-auto max-h-[85vh] focus:outline-none"
|
||||
>
|
||||
{`This project type is not allowed by the organization owner. You can manually convert it to use ${storage === 'cloud_only' ? 'Cloud Sync' : 'Local Vault'}.`}
|
||||
{`This project type is not allowed by the organization owner. You can manually convert it to use ${storage === ORG_STORAGE_RULE.CLOUD_ONLY ? 'Cloud Sync' : 'Local Vault'}.`}
|
||||
</Tooltip>
|
||||
</TooltipTrigger>
|
||||
}
|
||||
@@ -238,13 +239,13 @@ export const ProjectDropdown: FC<Props> = ({ project, organizationId, storage })
|
||||
className="py-1 placeholder:italic w-full pl-2 pr-7 rounded-sm border border-solid border-[--hl-sm] bg-[--color-bg] text-[--color-font] focus:outline-none focus:ring-1 focus:ring-[--hl-md] transition-colors"
|
||||
/>
|
||||
</TextField>
|
||||
<RadioGroup name="type" defaultValue={storage === 'cloud_plus_local' ? project.remoteId ? 'remote' : 'local' : storage !== 'cloud_only' ? 'local' : 'remote'} className="flex flex-col gap-2">
|
||||
<RadioGroup name="type" defaultValue={storage === ORG_STORAGE_RULE.CLOUD_PLUS_LOCAL ? project.remoteId ? 'remote' : 'local' : storage !== ORG_STORAGE_RULE.CLOUD_ONLY ? 'local' : 'remote'} className="flex flex-col gap-2">
|
||||
<Label className="text-sm text-[--hl]">
|
||||
Project type
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
<Radio
|
||||
isDisabled={storage === 'local_only'}
|
||||
isDisabled={storage === ORG_STORAGE_RULE.LOCAL_ONLY}
|
||||
value="remote"
|
||||
className="data-[selected]:border-[--color-surprise] flex-1 data-[disabled]:opacity-25 data-[selected]:ring-2 data-[selected]:ring-[--color-surprise] hover:bg-[--hl-xs] focus:bg-[--hl-sm] border border-solid border-[--hl-md] rounded p-4 focus:outline-none transition-colors"
|
||||
>
|
||||
@@ -257,7 +258,7 @@ export const ProjectDropdown: FC<Props> = ({ project, organizationId, storage })
|
||||
</p>
|
||||
</Radio>
|
||||
<Radio
|
||||
isDisabled={storage === 'cloud_only'}
|
||||
isDisabled={storage === ORG_STORAGE_RULE.CLOUD_ONLY}
|
||||
value="local"
|
||||
className="data-[selected]:border-[--color-surprise] flex-1 data-[disabled]:opacity-25 data-[selected]:ring-2 data-[selected]:ring-[--color-surprise] hover:bg-[--hl-xs] focus:bg-[--hl-sm] border border-solid border-[--hl-md] rounded p-4 focus:outline-none transition-colors"
|
||||
>
|
||||
|
||||
@@ -1,25 +1,50 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Dialog, Heading, Input, Label, Link, Modal, ModalOverlay, Radio, RadioGroup, TextField } from 'react-aria-components';
|
||||
import { useFetcher, useParams, useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { invariant } from '../../../utils/invariant';
|
||||
import type { OrganizationLoaderData } from '../../routes/organization';
|
||||
import { fetchAndCacheOrganizationStorageRule, ORG_STORAGE_RULE, type OrganizationLoaderData, type OrgStorageRuleType } from '../../routes/organization';
|
||||
import type { ProjectIdLoaderData } from '../../routes/project';
|
||||
import { Icon } from '../icon';
|
||||
import { showModal } from '.';
|
||||
import { AlertModal } from './alert-modal';
|
||||
|
||||
export const MockServerSettingsModal = ({ onClose }: { onClose: () => void }) => {
|
||||
export function useAvailableMockServerType(isLocalProject: boolean) {
|
||||
const { organizationId, projectId } = useParams<{ organizationId: string; projectId: string }>();
|
||||
const { currentPlan } = useRouteLoaderData('/organization') as OrganizationLoaderData;
|
||||
const [orgStorageRule, setOrgStorageRule] = useState<OrgStorageRuleType>(ORG_STORAGE_RULE.CLOUD_PLUS_LOCAL);
|
||||
useEffect(() => {
|
||||
fetchAndCacheOrganizationStorageRule(organizationId as string).then(setOrgStorageRule);
|
||||
}, [organizationId]);
|
||||
|
||||
const isEnterprise = currentPlan?.type.includes('enterprise');
|
||||
const isSelfHostedDisabled = !isEnterprise || orgStorageRule === ORG_STORAGE_RULE.CLOUD_ONLY;
|
||||
const isCloudProjectDisabled = isLocalProject || orgStorageRule === ORG_STORAGE_RULE.LOCAL_ONLY;
|
||||
return {
|
||||
isSelfHostedDisabled,
|
||||
isCloudProjectDisabled,
|
||||
organizationId,
|
||||
projectId,
|
||||
isEnterprise,
|
||||
isLocalProject,
|
||||
};
|
||||
}
|
||||
|
||||
export const MockServerSettingsModal = ({ onClose }: { onClose: () => void }) => {
|
||||
// file://./../../routes/project.tsx#projectIdLoader
|
||||
const projectData = useRouteLoaderData('/project/:projectId') as ProjectIdLoaderData | null;
|
||||
const isLocalProject = !projectData?.activeProject?.remoteId;
|
||||
const {
|
||||
isSelfHostedDisabled,
|
||||
isCloudProjectDisabled,
|
||||
organizationId,
|
||||
projectId,
|
||||
isEnterprise,
|
||||
} = useAvailableMockServerType(isLocalProject);
|
||||
const fetcher = useFetcher({
|
||||
key: `${organizationId}-create-mock-server`,
|
||||
});
|
||||
const { currentPlan } = useRouteLoaderData('/organization') as OrganizationLoaderData;
|
||||
const projectData = useRouteLoaderData('/project/:projectId') as ProjectIdLoaderData | null;
|
||||
const isLocalProject = !projectData?.activeProject?.remoteId;
|
||||
const isEnterprise = currentPlan?.type.includes('enterprise');
|
||||
const isSelfHostedDisabled = !isEnterprise;
|
||||
const isCloudProjectDisabled = isLocalProject;
|
||||
|
||||
const canOnlyCreateSelfHosted = isLocalProject && isEnterprise;
|
||||
const defaultServerType = canOnlyCreateSelfHosted ? 'self-hosted' : 'cloud';
|
||||
const [serverType, setServerType] = useState<'self-hosted' | 'cloud'>(defaultServerType);
|
||||
@@ -83,6 +108,7 @@ export const MockServerSettingsModal = ({ onClose }: { onClose: () => void }) =>
|
||||
}
|
||||
}
|
||||
|
||||
// file://./../../routes/actions.tsx#createNewWorkspaceAction
|
||||
fetcher.submit(
|
||||
{
|
||||
name,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Button, Dialog, Heading, Input, Label, Modal, ModalOverlay, Radio, RadioGroup, TextField } from 'react-aria-components';
|
||||
import { useFetcher, useRouteLoaderData } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { database as db } from '../../../common/database';
|
||||
import { getWorkspaceLabel } from '../../../common/get-workspace-label';
|
||||
@@ -9,13 +8,14 @@ import * as models from '../../../models/index';
|
||||
import type { MockServer } from '../../../models/mock-server';
|
||||
import { isRequest } from '../../../models/request';
|
||||
import { isEnvironment, isMockServer, isScratchpad, type Workspace } from '../../../models/workspace';
|
||||
import type { OrganizationLoaderData } from '../../routes/organization';
|
||||
import type { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { Link } from '../base/link';
|
||||
import { PromptButton } from '../base/prompt-button';
|
||||
import { Icon } from '../icon';
|
||||
import { MarkdownEditor } from '../markdown-editor';
|
||||
import { showModal } from '.';
|
||||
import { AlertModal } from './alert-modal';
|
||||
import { useAvailableMockServerType } from './mock-server-settings-modal';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
@@ -24,14 +24,20 @@ interface Props {
|
||||
}
|
||||
|
||||
export const WorkspaceSettingsModal = ({ workspace, mockServer, onClose }: Props) => {
|
||||
// file://./../../routes/workspace.tsx#workspaceLoader
|
||||
const workspaceLoaderData = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData | null;
|
||||
const isLocalProject = !workspaceLoaderData?.activeProject?.remoteId;
|
||||
const {
|
||||
isSelfHostedDisabled,
|
||||
isCloudProjectDisabled,
|
||||
organizationId,
|
||||
projectId,
|
||||
isEnterprise,
|
||||
} = useAvailableMockServerType(isLocalProject);
|
||||
const isScratchpadWorkspace = isScratchpad(workspace);
|
||||
const { currentPlan } = useRouteLoaderData('/organization') as OrganizationLoaderData;
|
||||
const isEnterprise = currentPlan?.type.includes('enterprise');
|
||||
const isSelfHostedDisabled = !isEnterprise;
|
||||
|
||||
const activeWorkspaceName = workspace.name;
|
||||
|
||||
const { organizationId, projectId } = useParams<{ organizationId: string; projectId: string }>();
|
||||
const workspaceFetcher = useFetcher();
|
||||
const mockServerFetcher = useFetcher();
|
||||
const workspacePatcher = (workspaceId: string, patch: Partial<Workspace>) => {
|
||||
@@ -42,6 +48,7 @@ export const WorkspaceSettingsModal = ({ workspace, mockServer, onClose }: Props
|
||||
});
|
||||
};
|
||||
const mockServerPatcher = (mockServerId: string, patch: Partial<MockServer>) => {
|
||||
// file://./../../routes/actions.tsx#updateMockServerAction
|
||||
mockServerFetcher.submit({ ...patch, mockServerId }, {
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspace._id}/mock-server/update`,
|
||||
method: 'post',
|
||||
@@ -148,6 +155,7 @@ export const WorkspaceSettingsModal = ({ workspace, mockServer, onClose }: Props
|
||||
<div className="flex gap-2">
|
||||
<Radio
|
||||
value="cloud"
|
||||
isDisabled={isCloudProjectDisabled}
|
||||
className="flex-1 data-[selected]:border-[--color-surprise] data-[selected]:ring-2 data-[selected]:ring-[--color-surprise] data-[disabled]:opacity-25 hover:bg-[--hl-xs] focus:bg-[--hl-sm] border border-solid border-[--hl-md] rounded p-4 focus:outline-none transition-colors"
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
@@ -160,6 +168,7 @@ export const WorkspaceSettingsModal = ({ workspace, mockServer, onClose }: Props
|
||||
</Radio>
|
||||
<Radio
|
||||
value="self-hosted"
|
||||
isDisabled={isSelfHostedDisabled}
|
||||
className="flex-1 data-[selected]:border-[--color-surprise] data-[selected]:ring-2 data-[selected]:ring-[--color-surprise] data-[disabled]:opacity-25 hover:bg-[--hl-xs] focus:bg-[--hl-sm] border border-solid border-[--hl-md] rounded p-4 focus:outline-none transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -249,22 +249,6 @@ async function migrateProjectsUnderOrganization(personalOrganizationId: string,
|
||||
}
|
||||
};
|
||||
|
||||
async function syncStorageRule(sessionId: string, organizationId: string) {
|
||||
try {
|
||||
const storageRule = await insomniaFetch<StorageRule | undefined>({
|
||||
method: 'GET',
|
||||
path: `/v1/organizations/${organizationId}/storage-rule`,
|
||||
sessionId,
|
||||
});
|
||||
|
||||
invariant(storageRule, 'Failed to load storageRule');
|
||||
|
||||
inMemoryStorageRuleCache.set(organizationId, storageRule);
|
||||
} catch (error) {
|
||||
console.log('[storageRule] Failed to load storage rules', error);
|
||||
}
|
||||
}
|
||||
|
||||
export const indexLoader: LoaderFunction = async () => {
|
||||
const { id: sessionId, accountId } = await userSession.getOrCreate();
|
||||
if (sessionId) {
|
||||
@@ -307,20 +291,6 @@ export const syncOrganizationsAction: ActionFunction = async () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export const syncOrganizationStorageRuleAction: ActionFunction = async ({ params }) => {
|
||||
const { organizationId } = params;
|
||||
|
||||
invariant(organizationId, 'Organization ID is required');
|
||||
|
||||
const { id: sessionId } = await userSession.getOrCreate();
|
||||
|
||||
if (sessionId) {
|
||||
await syncStorageRule(sessionId, organizationId);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export interface OrganizationLoaderData {
|
||||
organizations: Organization[];
|
||||
user?: UserProfileResponse;
|
||||
@@ -366,9 +336,17 @@ export interface Billing {
|
||||
accessDenied: boolean;
|
||||
}
|
||||
|
||||
export const DefaultStorage = 'cloud_plus_local';
|
||||
export enum ORG_STORAGE_RULE {
|
||||
CLOUD_PLUS_LOCAL = 'cloud_plus_local',
|
||||
CLOUD_ONLY = 'cloud_only',
|
||||
LOCAL_ONLY = 'local_only',
|
||||
};
|
||||
|
||||
// https://stackoverflow.com/a/59496175/5714454
|
||||
export type OrgStorageRuleType = `${ORG_STORAGE_RULE}`;
|
||||
|
||||
export interface StorageRule {
|
||||
storage: 'cloud_plus_local' | 'cloud_only' | 'local_only';
|
||||
storage: OrgStorageRuleType;
|
||||
isOverridden: boolean;
|
||||
}
|
||||
|
||||
@@ -377,7 +355,7 @@ export interface OrganizationFeatureLoaderData {
|
||||
billingPromise: Promise<Billing>;
|
||||
}
|
||||
export interface OrganizationStorageLoaderData {
|
||||
storagePromise: Promise<'cloud_plus_local' | 'cloud_only' | 'local_only'>;
|
||||
storagePromise: Promise<OrgStorageRuleType>;
|
||||
}
|
||||
|
||||
// Create an in-memory storage to store the storage rules
|
||||
@@ -385,39 +363,53 @@ export const inMemoryStorageRuleCache: Map<string, StorageRule> = new Map<string
|
||||
|
||||
export const organizationStorageLoader: LoaderFunction = async ({ params }): Promise<OrganizationStorageLoaderData> => {
|
||||
const { organizationId } = params as { organizationId: string };
|
||||
return {
|
||||
storagePromise: fetchAndCacheOrganizationStorageRule(organizationId),
|
||||
};
|
||||
};
|
||||
|
||||
export const syncOrganizationStorageRuleAction: ActionFunction = async ({ params }) => {
|
||||
const { organizationId } = params;
|
||||
await fetchAndCacheOrganizationStorageRule(organizationId, true);
|
||||
return null;
|
||||
};
|
||||
|
||||
export async function fetchAndCacheOrganizationStorageRule(
|
||||
organizationId: string | undefined,
|
||||
forceFetch: boolean = false,
|
||||
): Promise<OrgStorageRuleType> {
|
||||
invariant(organizationId, 'Organization ID is required');
|
||||
|
||||
if (isScratchpadOrganizationId(organizationId)) {
|
||||
return ORG_STORAGE_RULE.LOCAL_ONLY;
|
||||
}
|
||||
if (!forceFetch) {
|
||||
const storageRule = inMemoryStorageRuleCache.get(organizationId);
|
||||
if (storageRule) {
|
||||
return storageRule.storage;
|
||||
}
|
||||
}
|
||||
const { id: sessionId } = await userSession.getOrCreate();
|
||||
|
||||
const storageRule = inMemoryStorageRuleCache.get(organizationId);
|
||||
|
||||
if (storageRule) {
|
||||
return {
|
||||
storagePromise: Promise.resolve(storageRule.storage),
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise fetch from the API
|
||||
try {
|
||||
const storageRuleResponse = insomniaFetch<StorageRule | undefined>({
|
||||
method: 'GET',
|
||||
path: `/v1/organizations/${organizationId}/storage-rule`,
|
||||
sessionId,
|
||||
});
|
||||
|
||||
// Return the value
|
||||
return {
|
||||
storagePromise: storageRuleResponse.then(res => {
|
||||
if (res) {
|
||||
inMemoryStorageRuleCache.set(organizationId, res);
|
||||
}
|
||||
return res?.storage || DefaultStorage;
|
||||
}),
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
storagePromise: Promise.resolve(DefaultStorage),
|
||||
};
|
||||
}
|
||||
};
|
||||
return await insomniaFetch<StorageRule | undefined>({
|
||||
method: 'GET',
|
||||
path: `/v1/organizations/${organizationId}/storage-rule`,
|
||||
sessionId,
|
||||
onlyResolveOnSuccess: true,
|
||||
}).then(
|
||||
res => {
|
||||
if (res) {
|
||||
inMemoryStorageRuleCache.set(organizationId, res);
|
||||
}
|
||||
return res?.storage || ORG_STORAGE_RULE.CLOUD_PLUS_LOCAL;
|
||||
},
|
||||
err => {
|
||||
console.log('[storageRule] Failed to load storage rules', err.message);
|
||||
return ORG_STORAGE_RULE.CLOUD_PLUS_LOCAL;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export const organizationPermissionsLoader: LoaderFunction = async ({ params }): Promise<OrganizationFeatureLoaderData> => {
|
||||
const { organizationId } = params as { organizationId: string };
|
||||
|
||||
@@ -90,7 +90,7 @@ import { TimeFromNow } from '../components/time-from-now';
|
||||
import { useInsomniaEventStreamContext } from '../context/app/insomnia-event-stream-context';
|
||||
import { useLoaderDeferData } from '../hooks/use-loader-defer-data';
|
||||
import { useOrganizationPermissions } from '../hooks/use-organization-features';
|
||||
import { DefaultStorage, type OrganizationLoaderData, type OrganizationStorageLoaderData, useOrganizationLoaderData } from './organization';
|
||||
import { ORG_STORAGE_RULE, type OrganizationLoaderData, type OrganizationStorageLoaderData, useOrganizationLoaderData } from './organization';
|
||||
import { useRootLoaderData } from './root';
|
||||
|
||||
interface TeamProject {
|
||||
@@ -642,6 +642,7 @@ const ProjectRoute: FC = () => {
|
||||
useEffect(() => {
|
||||
if (!isScratchpadOrganizationId(organizationId)) {
|
||||
const load = storageRuleFetcher.load;
|
||||
// file://./organization.tsx#organizationStorageLoader
|
||||
load(`/organization/${organizationId}/storage-rule`);
|
||||
}
|
||||
}, [organizationId, storageRuleFetcher.load]);
|
||||
@@ -650,7 +651,7 @@ const ProjectRoute: FC = () => {
|
||||
|
||||
const { storagePromise } = storageRuleFetcher.data || {};
|
||||
|
||||
const [storage = DefaultStorage] = useLoaderDeferData(storagePromise);
|
||||
const [storage = ORG_STORAGE_RULE.CLOUD_PLUS_LOCAL] = useLoaderDeferData(storagePromise);
|
||||
|
||||
const [projectListFilter, setProjectListFilter] = useLocalStorage(`${organizationId}:project-list-filter`, '');
|
||||
const [workspaceListFilter, setWorkspaceListFilter] = useLocalStorage(`${projectId}:workspace-list-filter`, '');
|
||||
@@ -960,11 +961,11 @@ const ProjectRoute: FC = () => {
|
||||
},
|
||||
},
|
||||
];
|
||||
const defaultStorageSelection = storage === 'local_only' ? 'local' : 'remote';
|
||||
const isRemoteProjectInconsistent = activeProject && isRemoteProject(activeProject) && storage === 'local_only';
|
||||
const isLocalProjectInconsistent = activeProject && !isRemoteProject(activeProject) && storage === 'cloud_only';
|
||||
const defaultStorageSelection = storage === ORG_STORAGE_RULE.LOCAL_ONLY ? 'local' : 'remote';
|
||||
const isRemoteProjectInconsistent = activeProject && isRemoteProject(activeProject) && storage === ORG_STORAGE_RULE.LOCAL_ONLY;
|
||||
const isLocalProjectInconsistent = activeProject && !isRemoteProject(activeProject) && storage === ORG_STORAGE_RULE.CLOUD_ONLY;
|
||||
const isProjectInconsistent = isRemoteProjectInconsistent || isLocalProjectInconsistent;
|
||||
const showStorageRestrictionMessage = storage !== 'cloud_plus_local';
|
||||
const showStorageRestrictionMessage = storage !== ORG_STORAGE_RULE.CLOUD_PLUS_LOCAL;
|
||||
|
||||
useEffect(() => {
|
||||
window.main.landingPageRendered(LandingPage.ProjectDashboard);
|
||||
@@ -1593,7 +1594,7 @@ const ProjectRoute: FC = () => {
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
<Radio
|
||||
isDisabled={storage === 'local_only'}
|
||||
isDisabled={storage === ORG_STORAGE_RULE.LOCAL_ONLY}
|
||||
value="remote"
|
||||
className="flex-1 data-[selected]:border-[--color-surprise] data-[selected]:ring-2 data-[selected]:ring-[--color-surprise] data-[disabled]:opacity-25 hover:bg-[--hl-xs] focus:bg-[--hl-sm] border border-solid border-[--hl-md] rounded p-4 focus:outline-none transition-colors"
|
||||
>
|
||||
@@ -1606,7 +1607,7 @@ const ProjectRoute: FC = () => {
|
||||
</p>
|
||||
</Radio>
|
||||
<Radio
|
||||
isDisabled={storage === 'cloud_only'}
|
||||
isDisabled={storage === ORG_STORAGE_RULE.CLOUD_ONLY}
|
||||
value="local"
|
||||
className="flex-1 data-[selected]:border-[--color-surprise] data-[selected]:ring-2 data-[selected]:ring-[--color-surprise] data-[disabled]:opacity-25 hover:bg-[--hl-xs] focus:bg-[--hl-sm] border border-solid border-[--hl-md] rounded p-4 focus:outline-none transition-colors"
|
||||
>
|
||||
@@ -1723,13 +1724,13 @@ const ProjectRoute: FC = () => {
|
||||
className="py-1 placeholder:italic w-full pl-2 pr-7 rounded-sm border border-solid border-[--hl-sm] bg-[--color-bg] text-[--color-font] focus:outline-none focus:ring-1 focus:ring-[--hl-md] transition-colors"
|
||||
/>
|
||||
</TextField>
|
||||
<RadioGroup name="type" defaultValue={storage === 'cloud_plus_local' ? activeProject?.remoteId ? 'remote' : 'local' : storage !== 'cloud_only' ? 'local' : 'remote'} className="flex flex-col gap-2">
|
||||
<RadioGroup name="type" defaultValue={storage === ORG_STORAGE_RULE.CLOUD_PLUS_LOCAL ? activeProject?.remoteId ? 'remote' : 'local' : storage !== ORG_STORAGE_RULE.CLOUD_ONLY ? 'local' : 'remote'} className="flex flex-col gap-2">
|
||||
<Label className="text-sm text-[--hl]">
|
||||
Project type
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
<Radio
|
||||
isDisabled={storage === 'local_only'}
|
||||
isDisabled={storage === ORG_STORAGE_RULE.LOCAL_ONLY}
|
||||
value="remote"
|
||||
className="data-[selected]:border-[--color-surprise] flex-1 data-[disabled]:opacity-25 data-[selected]:ring-2 data-[selected]:ring-[--color-surprise] hover:bg-[--hl-xs] focus:bg-[--hl-sm] border border-solid border-[--hl-md] rounded p-4 focus:outline-none transition-colors"
|
||||
>
|
||||
@@ -1742,7 +1743,7 @@ const ProjectRoute: FC = () => {
|
||||
</p>
|
||||
</Radio>
|
||||
<Radio
|
||||
isDisabled={storage === 'cloud_only'}
|
||||
isDisabled={storage === ORG_STORAGE_RULE.CLOUD_ONLY}
|
||||
value="local"
|
||||
className="data-[selected]:border-[--color-surprise] flex-1 data-[disabled]:opacity-25 data-[selected]:ring-2 data-[selected]:ring-[--color-surprise] hover:bg-[--hl-xs] focus:bg-[--hl-sm] border border-solid border-[--hl-md] rounded p-4 focus:outline-none transition-colors"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user