fix (common): address mock server issues and improve the UI (#5517)
Co-authored-by: nivedin <nivedinp@gmail.com> Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com> Co-authored-by: mirarifhasan <arif.ishan05@gmail.com>
This commit is contained in:
parent
213c5436bc
commit
c0e3ff49b3
35 changed files with 1136 additions and 321 deletions
|
|
@ -18,10 +18,10 @@ import {
|
|||
import { WorkspaceType } from 'src/types/WorkspaceTypes';
|
||||
|
||||
// Regex pattern for mock server name validation
|
||||
// Allows letters, numbers, spaces, dots, underscores, and hyphens
|
||||
const MOCK_SERVER_NAME_PATTERN = /^[a-zA-Z0-9 ._-]+$/;
|
||||
// Allows letters, numbers, spaces, dots, brackets, underscores, and hyphens
|
||||
const MOCK_SERVER_NAME_PATTERN = /^[a-zA-Z0-9 .()[\]{}<>_-]+$/;
|
||||
const MOCK_SERVER_NAME_ERROR_MESSAGE =
|
||||
'Name can only contain letters, numbers, spaces, dots, underscores, and hyphens';
|
||||
'Name can only contain letters, numbers, spaces, dots, brackets, underscores, and hyphens';
|
||||
|
||||
@ObjectType()
|
||||
export class MockServer {
|
||||
|
|
|
|||
|
|
@ -55,11 +55,12 @@ export class MockServerResolver {
|
|||
}
|
||||
|
||||
@ResolveField(() => MockServerCollection, {
|
||||
nullable: true,
|
||||
description: 'Returns the collection of the mock server',
|
||||
})
|
||||
async collection(
|
||||
@Parent() mockServer: MockServer,
|
||||
): Promise<MockServerCollection> {
|
||||
): Promise<MockServerCollection | null> {
|
||||
const collection = await this.mockServerService.getMockServerCollection(
|
||||
mockServer.id,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ describe('MockServerService', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('should return error when collection not found', async () => {
|
||||
test('should return null when collection not found', async () => {
|
||||
mockPrisma.mockServer.findUnique.mockResolvedValue(dbMockServer);
|
||||
mockPrisma.userCollection.findUnique.mockResolvedValue(null);
|
||||
|
||||
|
|
@ -343,9 +343,9 @@ describe('MockServerService', () => {
|
|||
dbMockServer.id,
|
||||
);
|
||||
|
||||
expect(E.isLeft(result)).toBe(true);
|
||||
if (E.isLeft(result)) {
|
||||
expect(result.left).toBe(MOCK_SERVER_INVALID_COLLECTION);
|
||||
expect(E.isRight(result)).toBe(true);
|
||||
if (E.isRight(result)) {
|
||||
expect(result.right).toBe(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -863,6 +863,7 @@ describe('MockServerService', () => {
|
|||
} as any;
|
||||
|
||||
test('should return example by ID header', async () => {
|
||||
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
|
||||
mockPrisma.userCollection.findMany.mockResolvedValue([]); // No child collections
|
||||
mockPrisma.userRequest.findMany.mockResolvedValue([userRequest] as any);
|
||||
|
||||
|
|
@ -882,6 +883,7 @@ describe('MockServerService', () => {
|
|||
});
|
||||
|
||||
test('should return example by name header', async () => {
|
||||
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
|
||||
mockPrisma.userCollection.findMany.mockResolvedValue([]); // No child collections
|
||||
mockPrisma.userRequest.findMany.mockResolvedValue([userRequest] as any);
|
||||
|
||||
|
|
@ -915,6 +917,7 @@ describe('MockServerService', () => {
|
|||
},
|
||||
};
|
||||
|
||||
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
|
||||
mockPrisma.userCollection.findMany.mockResolvedValue([]); // No child collections
|
||||
mockPrisma.userRequest.findMany.mockResolvedValue([
|
||||
requestWith404,
|
||||
|
|
@ -936,6 +939,7 @@ describe('MockServerService', () => {
|
|||
});
|
||||
|
||||
test('should match exact path', async () => {
|
||||
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
|
||||
mockPrisma.userRequest.findMany.mockResolvedValue([userRequest] as any);
|
||||
mockPrisma.userCollection.findMany.mockResolvedValue([]);
|
||||
|
||||
|
|
@ -964,6 +968,7 @@ describe('MockServerService', () => {
|
|||
},
|
||||
};
|
||||
|
||||
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
|
||||
mockPrisma.userRequest.findMany.mockResolvedValue([
|
||||
variableRequest,
|
||||
] as any);
|
||||
|
|
@ -979,6 +984,7 @@ describe('MockServerService', () => {
|
|||
});
|
||||
|
||||
test('should return error when no examples found', async () => {
|
||||
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
|
||||
mockPrisma.userRequest.findMany.mockResolvedValue([]);
|
||||
mockPrisma.userCollection.findMany.mockResolvedValue([]);
|
||||
|
||||
|
|
@ -1004,6 +1010,7 @@ describe('MockServerService', () => {
|
|||
},
|
||||
};
|
||||
|
||||
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
|
||||
mockPrisma.userRequest.findMany.mockResolvedValue([
|
||||
multipleExamples,
|
||||
] as any);
|
||||
|
|
@ -1036,6 +1043,7 @@ describe('MockServerService', () => {
|
|||
},
|
||||
};
|
||||
|
||||
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
|
||||
mockPrisma.userCollection.findMany.mockResolvedValue([]); // No child collections
|
||||
mockPrisma.userRequest.findMany.mockResolvedValue([simpleRequest] as any);
|
||||
|
||||
|
|
@ -1070,6 +1078,7 @@ describe('MockServerService', () => {
|
|||
},
|
||||
};
|
||||
|
||||
mockPrisma.teamCollection.findUnique.mockResolvedValue(teamCollection);
|
||||
mockPrisma.teamCollection.findMany.mockResolvedValue([]); // No child collections
|
||||
mockPrisma.teamRequest.findMany.mockResolvedValue([teamRequest] as any);
|
||||
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ export class MockServerService {
|
|||
const collection = await this.prisma.userCollection.findUnique({
|
||||
where: { id: mockServer.collectionID },
|
||||
});
|
||||
if (!collection) return E.left(MOCK_SERVER_INVALID_COLLECTION);
|
||||
if (!collection) return E.right(null);
|
||||
return E.right({
|
||||
id: collection.id,
|
||||
title: collection.title,
|
||||
|
|
@ -231,7 +231,7 @@ export class MockServerService {
|
|||
const collection = await this.prisma.teamCollection.findUnique({
|
||||
where: { id: mockServer.collectionID },
|
||||
});
|
||||
if (!collection) return E.left(MOCK_SERVER_INVALID_COLLECTION);
|
||||
if (!collection) return E.right(null);
|
||||
return E.right({
|
||||
id: collection.id,
|
||||
title: collection.title,
|
||||
|
|
@ -599,6 +599,12 @@ export class MockServerService {
|
|||
// This is used by both custom header lookup and candidate fetching
|
||||
const collectionIds = await this.getCollectionIds(mockServer);
|
||||
|
||||
if (collectionIds.length === 0) {
|
||||
return E.left(
|
||||
`The collection associated with this mock has been deleted.`,
|
||||
);
|
||||
}
|
||||
|
||||
// OPTIMIZATION: Fetch all requests with examples once (single DB query)
|
||||
// This is shared between custom header lookup and candidate matching
|
||||
const requests = await this.fetchRequestsWithExamples(
|
||||
|
|
@ -848,6 +854,13 @@ export class MockServerService {
|
|||
private async getAllUserCollectionIds(
|
||||
rootCollectionId: string,
|
||||
): Promise<string[]> {
|
||||
// First verify the root collection exists
|
||||
const rootCollection = await this.prisma.userCollection.findUnique({
|
||||
where: { id: rootCollectionId },
|
||||
});
|
||||
|
||||
if (!rootCollection) return []; // Collection doesn't exist
|
||||
|
||||
const ids = [rootCollectionId];
|
||||
const children = await this.prisma.userCollection.findMany({
|
||||
where: { parentID: rootCollectionId },
|
||||
|
|
@ -868,6 +881,13 @@ export class MockServerService {
|
|||
private async getAllTeamCollectionIds(
|
||||
rootCollectionId: string,
|
||||
): Promise<string[]> {
|
||||
// First verify the root collection exists
|
||||
const rootCollection = await this.prisma.teamCollection.findUnique({
|
||||
where: { id: rootCollectionId },
|
||||
});
|
||||
|
||||
if (!rootCollection) return []; // Collection doesn't exist
|
||||
|
||||
const ids = [rootCollectionId];
|
||||
const children = await this.prisma.teamCollection.findMany({
|
||||
where: { parentID: rootCollectionId },
|
||||
|
|
|
|||
|
|
@ -855,6 +855,7 @@
|
|||
"authentication": "Authentication"
|
||||
},
|
||||
"mock_server": {
|
||||
"confirm_delete_log": "Are you sure you want to delete this log?",
|
||||
"create_mock_server": "Configure Mock Server",
|
||||
"mock_server_configuration": "Mock Server Configuration",
|
||||
"mock_server_name": "Mock Server Name",
|
||||
|
|
@ -885,6 +886,7 @@
|
|||
"private_description": "Only authenticated users can access this mock server",
|
||||
"select_collection": "Select a collection",
|
||||
"select_collection_error": "Please select a collection",
|
||||
"invalid_collection_error": "Failed to create a mock server for the collection.",
|
||||
"url_copied": "URL copied to clipboard",
|
||||
"make_public": "Make Public",
|
||||
"view_logs": "View logs",
|
||||
|
|
@ -1079,6 +1081,7 @@
|
|||
"ai_request_naming_style_custom": "Custom",
|
||||
"ai_request_naming_style_custom_placeholder": "Enter your custom naming style template...",
|
||||
"experimental_scripting_sandbox": "Experimental scripting sandbox",
|
||||
"enable_experimental_mock_servers": "Enable Mock Servers",
|
||||
"sync": "Synchronise",
|
||||
"sync_collections": "Collections",
|
||||
"sync_description": "These settings are synced to cloud.",
|
||||
|
|
@ -1375,6 +1378,7 @@
|
|||
"loading": "Loading...",
|
||||
"message_received": "Message: {message} arrived on topic: {topic}",
|
||||
"mqtt_subscription_failed": "Something went wrong while subscribing to topic: {topic}",
|
||||
"no_content_found": "No content found",
|
||||
"none": "None",
|
||||
"nothing_found": "Nothing found for",
|
||||
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
|
||||
|
|
|
|||
|
|
@ -214,9 +214,11 @@ declare module 'vue' {
|
|||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
||||
IconLucideBrush: typeof import('~icons/lucide/brush')['default']
|
||||
IconLucideCheck: typeof import('~icons/lucide/check')['default']
|
||||
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
||||
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
||||
IconLucideCircleCheck: typeof import('~icons/lucide/circle-check')['default']
|
||||
IconLucideCopy: typeof import('~icons/lucide/copy')['default']
|
||||
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
||||
IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']
|
||||
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
|
||||
|
|
@ -260,6 +262,7 @@ declare module 'vue' {
|
|||
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default']
|
||||
MockServerCreateMockServer: typeof import('./components/mockServer/CreateMockServer.vue')['default']
|
||||
MockServerEditMockServer: typeof import('./components/mockServer/EditMockServer.vue')['default']
|
||||
MockServerLogSection: typeof import('./components/mockServer/LogSection.vue')['default']
|
||||
MockServerMockServerDashboard: typeof import('./components/mockServer/MockServerDashboard.vue')['default']
|
||||
MockServerMockServerLogs: typeof import('./components/mockServer/MockServerLogs.vue')['default']
|
||||
MonacoScriptEditor: typeof import('./components/MonacoScriptEditor.vue')['default']
|
||||
|
|
|
|||
|
|
@ -135,7 +135,10 @@
|
|||
@keyup.p="propertiesAction?.$el.click()"
|
||||
@keyup.t="runCollectionAction?.$el.click()"
|
||||
@keyup.s="sortAction?.$el.click()"
|
||||
@keyup.m="mockServerAction?.$el.click()"
|
||||
@keyup.m="
|
||||
ENABLE_EXPERIMENTAL_MOCK_SERVERS &&
|
||||
mockServerAction?.$el.click()
|
||||
"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
|
|
@ -177,7 +180,11 @@
|
|||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-if="!hasNoTeamAccess && isRootCollection"
|
||||
v-if="
|
||||
!hasNoTeamAccess &&
|
||||
isRootCollection &&
|
||||
ENABLE_EXPERIMENTAL_MOCK_SERVERS
|
||||
"
|
||||
ref="mockServerAction"
|
||||
:icon="IconServer"
|
||||
:label="t('mock_server.create_mock_server')"
|
||||
|
|
@ -321,6 +328,7 @@ import IconArrowUpDown from "~icons/lucide/arrow-up-down"
|
|||
import { CurrentSortValuesService } from "~/services/current-sort.service"
|
||||
import { useService } from "dioc/vue"
|
||||
import { useMockServerStatus } from "~/composables/mockServer"
|
||||
import { useSetting } from "@composables/settings"
|
||||
import { platform } from "~/platform"
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
|
||||
|
|
@ -456,9 +464,16 @@ const isCollectionLoading = computed(() => {
|
|||
})
|
||||
|
||||
// Mock Server Status
|
||||
const ENABLE_EXPERIMENTAL_MOCK_SERVERS = useSetting(
|
||||
"ENABLE_EXPERIMENTAL_MOCK_SERVERS"
|
||||
)
|
||||
const { getMockServerStatus } = useMockServerStatus()
|
||||
|
||||
const mockServerStatus = computed(() => {
|
||||
if (!ENABLE_EXPERIMENTAL_MOCK_SERVERS.value) {
|
||||
return { exists: false, isActive: false }
|
||||
}
|
||||
|
||||
const collectionId =
|
||||
props.collectionsType === "my-collections"
|
||||
? (props.data as HoppCollection).id
|
||||
|
|
|
|||
|
|
@ -33,7 +33,11 @@ import { defineStep } from "~/composables/step-components"
|
|||
import AllCollectionImport from "~/components/importExport/ImportExportSteps/AllCollectionImport.vue"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { appendRESTCollections, restCollections$ } from "~/newstore/collections"
|
||||
import {
|
||||
appendRESTCollections,
|
||||
restCollections$,
|
||||
setRESTCollections,
|
||||
} from "~/newstore/collections"
|
||||
|
||||
import IconInsomnia from "~icons/hopp/insomnia"
|
||||
import IconPostman from "~icons/hopp/postman"
|
||||
|
|
@ -46,6 +50,11 @@ import { useReadonlyStream } from "~/composables/stream"
|
|||
import IconUser from "~icons/lucide/user"
|
||||
|
||||
import { getTeamCollectionJSON } from "~/helpers/backend/helpers"
|
||||
import {
|
||||
importUserCollectionsFromJSON,
|
||||
fetchAndConvertUserCollections,
|
||||
} from "~/helpers/backend/mutations/UserCollection"
|
||||
import { ReqType } from "~/helpers/backend/graphql"
|
||||
|
||||
import { platform } from "~/platform"
|
||||
|
||||
|
|
@ -101,7 +110,7 @@ const showImportFailedError = () => {
|
|||
const handleImportToStore = async (collections: HoppCollection[]) => {
|
||||
const importResult =
|
||||
props.collectionsType.type === "my-collections"
|
||||
? importToPersonalWorkspace(collections)
|
||||
? await importToPersonalWorkspace(collections)
|
||||
: await importToTeamsWorkspace(collections)
|
||||
|
||||
if (E.isRight(importResult)) {
|
||||
|
|
@ -111,11 +120,58 @@ const handleImportToStore = async (collections: HoppCollection[]) => {
|
|||
}
|
||||
}
|
||||
|
||||
const importToPersonalWorkspace = (collections: HoppCollection[]) => {
|
||||
appendRESTCollections(collections)
|
||||
return E.right({
|
||||
success: true,
|
||||
})
|
||||
const importToPersonalWorkspace = async (collections: HoppCollection[]) => {
|
||||
// If user is logged in, try to import to backend first
|
||||
if (currentUser.value) {
|
||||
try {
|
||||
const transformedCollection = collections.map((collection) =>
|
||||
translateToPersonalCollectionFormat(collection)
|
||||
)
|
||||
|
||||
const res = await importUserCollectionsFromJSON(
|
||||
JSON.stringify(transformedCollection),
|
||||
ReqType.Rest
|
||||
)()
|
||||
|
||||
if (E.isRight(res)) {
|
||||
// Backend import succeeded, now fetch and persist collections in store
|
||||
const fetchResult = await fetchAndConvertUserCollections(ReqType.Rest)
|
||||
|
||||
if (E.isRight(fetchResult)) {
|
||||
// Replace local collections with backend collections
|
||||
setRESTCollections(fetchResult.right)
|
||||
} else {
|
||||
console.warn(
|
||||
"Failed to fetch collections from backend after import:",
|
||||
fetchResult.left
|
||||
)
|
||||
// Still append to local store as fallback
|
||||
appendRESTCollections(collections)
|
||||
}
|
||||
|
||||
return E.right({ success: true })
|
||||
}
|
||||
// Backend import failed, fall back to local storage
|
||||
console.warn(
|
||||
"Backend import failed, falling back to local storage:",
|
||||
res.left
|
||||
)
|
||||
appendRESTCollections(collections)
|
||||
return E.right({ success: true })
|
||||
} catch (error) {
|
||||
// Backend import failed, fall back to local storage
|
||||
console.warn(
|
||||
"Backend import failed, falling back to local storage:",
|
||||
error
|
||||
)
|
||||
appendRESTCollections(collections)
|
||||
return E.right({ success: true })
|
||||
}
|
||||
} else {
|
||||
// User not logged in, use local storage
|
||||
appendRESTCollections(collections)
|
||||
return E.right({ success: true })
|
||||
}
|
||||
}
|
||||
|
||||
function translateToTeamCollectionFormat(x: HoppCollection) {
|
||||
|
|
@ -140,6 +196,28 @@ function translateToTeamCollectionFormat(x: HoppCollection) {
|
|||
return obj
|
||||
}
|
||||
|
||||
function translateToPersonalCollectionFormat(x: HoppCollection) {
|
||||
const folders: HoppCollection[] = (x.folders ?? []).map(
|
||||
translateToPersonalCollectionFormat
|
||||
)
|
||||
|
||||
const data = {
|
||||
auth: x.auth,
|
||||
headers: x.headers,
|
||||
variables: x.variables,
|
||||
}
|
||||
|
||||
const obj = {
|
||||
...x,
|
||||
folders,
|
||||
data,
|
||||
}
|
||||
|
||||
if (x.id) obj.id = x.id
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
const importToTeamsWorkspace = async (collections: HoppCollection[]) => {
|
||||
if (!hasTeamWriteAccess.value || !selectedTeamID.value) {
|
||||
return E.left({
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ import { TeamWorkspace } from "~/services/workspace.service"
|
|||
import IconSparkle from "~icons/lucide/sparkles"
|
||||
import IconThumbsDown from "~icons/lucide/thumbs-down"
|
||||
import IconThumbsUp from "~icons/lucide/thumbs-up"
|
||||
import { handleTokenValidation } from "~/helpers/handleTokenValidation";
|
||||
import { handleTokenValidation } from "~/helpers/handleTokenValidation"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ import { useToast } from "~/composables/toast"
|
|||
import { ImporterOrExporter } from "~/components/importExport/types"
|
||||
import { FileSource } from "~/helpers/import-export/import/import-sources/FileSource"
|
||||
import { GistSource } from "~/helpers/import-export/import/import-sources/GistSource"
|
||||
import {
|
||||
importUserCollectionsFromJSON,
|
||||
fetchAndConvertUserCollections,
|
||||
} from "~/helpers/backend/mutations/UserCollection"
|
||||
import { ReqType } from "~/helpers/backend/graphql"
|
||||
|
||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||
import IconUser from "~icons/lucide/user"
|
||||
|
|
@ -28,6 +33,7 @@ import { platform } from "~/platform"
|
|||
import {
|
||||
appendGraphqlCollections,
|
||||
graphqlCollections$,
|
||||
setGraphqlCollections,
|
||||
} from "~/newstore/collections"
|
||||
import { hoppGqlCollectionsImporter } from "~/helpers/import-export/import/hoppGql"
|
||||
import { gqlCollectionsExporter } from "~/helpers/import-export/export/gqlCollections"
|
||||
|
|
@ -71,7 +77,7 @@ const GqlCollectionsHoppImporter: ImporterOrExporter = {
|
|||
)()
|
||||
|
||||
if (E.isRight(validatedCollection)) {
|
||||
handleImportToStore(validatedCollection.right)
|
||||
await handleImportToStore(validatedCollection.right)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_IMPORT_COLLECTION",
|
||||
|
|
@ -110,7 +116,7 @@ const GqlCollectionsGistImporter: ImporterOrExporter = {
|
|||
return
|
||||
}
|
||||
|
||||
handleImportToStore(res.right)
|
||||
await handleImportToStore(res.right)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_IMPORT_COLLECTION",
|
||||
|
|
@ -231,9 +237,83 @@ const showImportFailedError = () => {
|
|||
toast.error(t("import.failed"))
|
||||
}
|
||||
|
||||
const handleImportToStore = (gqlCollections: HoppCollection[]) => {
|
||||
appendGraphqlCollections(gqlCollections)
|
||||
toast.success(t("state.file_imported"))
|
||||
const handleImportToStore = async (gqlCollections: HoppCollection[]) => {
|
||||
// If user is logged in, try to import to backend first
|
||||
if (currentUser.value) {
|
||||
try {
|
||||
const transformedCollection = gqlCollections.map((collection) =>
|
||||
translateToPersonalCollectionFormat(collection)
|
||||
)
|
||||
|
||||
const res = await importUserCollectionsFromJSON(
|
||||
JSON.stringify(transformedCollection),
|
||||
ReqType.Gql
|
||||
)()
|
||||
|
||||
if (E.isRight(res)) {
|
||||
// Backend import succeeded, now fetch and persist collections in store
|
||||
const fetchResult = await fetchAndConvertUserCollections(ReqType.Gql)
|
||||
|
||||
if (E.isRight(fetchResult)) {
|
||||
// Replace local collections with backend collections
|
||||
setGraphqlCollections(fetchResult.right)
|
||||
} else {
|
||||
console.warn(
|
||||
"Failed to fetch collections from backend after import:",
|
||||
fetchResult.left
|
||||
)
|
||||
// Still append to local store as fallback
|
||||
appendGraphqlCollections(gqlCollections)
|
||||
}
|
||||
|
||||
toast.success(t("state.file_imported"))
|
||||
return
|
||||
}
|
||||
// Backend import failed, fall back to local storage
|
||||
console.warn(
|
||||
"Backend import failed, falling back to local storage:",
|
||||
res.left
|
||||
)
|
||||
appendGraphqlCollections(gqlCollections)
|
||||
toast.success(t("state.file_imported"))
|
||||
return
|
||||
} catch (error) {
|
||||
// Backend import failed, fall back to local storage
|
||||
console.warn(
|
||||
"Backend import failed, falling back to local storage:",
|
||||
error
|
||||
)
|
||||
appendGraphqlCollections(gqlCollections)
|
||||
toast.success(t("state.file_imported"))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// User not logged in, use local storage
|
||||
appendGraphqlCollections(gqlCollections)
|
||||
toast.success(t("state.file_imported"))
|
||||
}
|
||||
}
|
||||
|
||||
function translateToPersonalCollectionFormat(x: HoppCollection) {
|
||||
const folders: HoppCollection[] = (x.folders ?? []).map(
|
||||
translateToPersonalCollectionFormat
|
||||
)
|
||||
|
||||
const data = {
|
||||
auth: x.auth,
|
||||
headers: x.headers,
|
||||
variables: x.variables,
|
||||
}
|
||||
|
||||
const obj = {
|
||||
...x,
|
||||
folders,
|
||||
data,
|
||||
}
|
||||
|
||||
if (x.id) obj.id = x.id
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
|
|||
|
|
@ -1083,12 +1083,7 @@ const createMockServer = (payload: {
|
|||
}) => {
|
||||
// Import the mock server store dynamically to avoid circular dependencies
|
||||
import("~/newstore/mockServers").then(({ showCreateMockServerModal$ }) => {
|
||||
// For personal collections, use the collection's _ref_id or id
|
||||
// For child collections, we need to get the root collection ID
|
||||
let collectionID =
|
||||
payload.collection.id ||
|
||||
payload.collection._ref_id ||
|
||||
payload.collectionIndex
|
||||
let collectionID = payload.collection.id ?? undefined
|
||||
|
||||
// If this is a child collection (folder), we need to get the root collection ID
|
||||
if (payload.collectionIndex.includes("/")) {
|
||||
|
|
@ -1096,7 +1091,7 @@ const createMockServer = (payload: {
|
|||
const rootIndex = payload.collectionIndex.split("/")[0]
|
||||
const rootCollection = myCollections.value[parseInt(rootIndex)]
|
||||
if (rootCollection) {
|
||||
collectionID = rootCollection.id || rootCollection._ref_id || rootIndex
|
||||
collectionID = rootCollection.id ?? undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ import { RESTTabService } from "~/services/tab/rest"
|
|||
import { getMethodLabelColor } from "~/helpers/rest/labelColoring"
|
||||
import { WorkspaceService } from "~/services/workspace.service"
|
||||
import { KernelInterceptorService } from "~/services/kernel-interceptor.service"
|
||||
import { handleTokenValidation } from "~/helpers/handleTokenValidation";
|
||||
import { handleTokenValidation } from "~/helpers/handleTokenValidation"
|
||||
|
||||
const t = useI18n()
|
||||
const interceptorService = useService(KernelInterceptorService)
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@
|
|||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="ENABLE_EXPERIMENTAL_MOCK_SERVERS"
|
||||
:id="'mock-servers'"
|
||||
:icon="IconServer"
|
||||
:label="`${t('tab.mock_servers')}`"
|
||||
|
|
@ -75,10 +76,16 @@ import IconCode from "~icons/lucide/code"
|
|||
import IconServer from "~icons/lucide/server"
|
||||
import { ref } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useSetting } from "@composables/settings"
|
||||
import MockServerDashboard from "~/components/mockServer/MockServerDashboard.vue"
|
||||
import { useMockServerWorkspaceSync } from "~/composables/mockServerWorkspace"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const ENABLE_EXPERIMENTAL_MOCK_SERVERS = useSetting(
|
||||
"ENABLE_EXPERIMENTAL_MOCK_SERVERS"
|
||||
)
|
||||
|
||||
type RequestOptionTabs =
|
||||
| "history"
|
||||
| "collections"
|
||||
|
|
@ -88,4 +95,7 @@ type RequestOptionTabs =
|
|||
| "mock-servers"
|
||||
|
||||
const selectedNavigationTab = ref<RequestOptionTabs>("collections")
|
||||
|
||||
// Ensure mock servers are kept in sync with workspace changes globally
|
||||
useMockServerWorkspaceSync()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -102,15 +102,25 @@
|
|||
</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<div
|
||||
class="flex-1 px-3 py-2 border border-divider rounded bg-primaryLight text-body font-mono"
|
||||
class="flex-1 px-3 py-2 border border-divider rounded bg-primaryLight"
|
||||
>
|
||||
{{ mockServerBaseUrl }}
|
||||
{{
|
||||
existingMockServer?.serverUrlPathBased ||
|
||||
existingMockServer?.serverUrlDomainBased ||
|
||||
""
|
||||
}}
|
||||
</div>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.copy')"
|
||||
:icon="copyIcon"
|
||||
@click="copyToClipboard(mockServerBaseUrl)"
|
||||
@click="
|
||||
copyToClipboard(
|
||||
existingMockServer?.serverUrlPathBased ||
|
||||
existingMockServer?.serverUrlDomainBased ||
|
||||
''
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -121,11 +131,11 @@
|
|||
</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<span
|
||||
class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"
|
||||
class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium"
|
||||
:class="
|
||||
existingMockServer?.isActive
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-gray-100 text-gray-800'
|
||||
? 'bg-green-600/20 text-green-200 border border-green-900/50'
|
||||
: 'bg-gray-600/20 text-gray-200 border border-gray-900/50'
|
||||
"
|
||||
>
|
||||
<span
|
||||
|
|
@ -147,7 +157,7 @@
|
|||
</div>
|
||||
|
||||
<!-- New Mock Server Form -->
|
||||
<div v-else class="flex flex-col space-y-4">
|
||||
<div v-else class="flex flex-col space-y-6">
|
||||
<HoppSmartInput
|
||||
v-model="mockServerName"
|
||||
v-focus
|
||||
|
|
@ -226,10 +236,14 @@
|
|||
</div>
|
||||
|
||||
<!-- Help Text -->
|
||||
<div class="p-4 bg-primaryLight rounded-md border border-dividerLight">
|
||||
<p class="text-sm text-secondary">
|
||||
<Icon-lucide-info class="inline w-4 h-4 mr-2 text-accent" />
|
||||
{{ t("mock_server.description") }}
|
||||
<div
|
||||
class="py-4 px-3 bg-primaryLight rounded-md border border-dividerLight shadow-sm"
|
||||
>
|
||||
<p class="text-secondary flex space-x-2 items-start">
|
||||
<Icon-lucide-info class="svg-icons text-accent" />
|
||||
<span>
|
||||
{{ t("mock_server.description") }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -237,12 +251,6 @@
|
|||
|
||||
<template #footer>
|
||||
<div class="flex justify-end space-x-2">
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.cancel')"
|
||||
outline
|
||||
@click="closeModal"
|
||||
/>
|
||||
|
||||
<!-- Start/Stop Server Button for existing mock server -->
|
||||
<HoppButtonPrimary
|
||||
v-if="isExistingMockServer"
|
||||
|
|
@ -256,12 +264,6 @@
|
|||
@click="toggleMockServer"
|
||||
/>
|
||||
|
||||
<HoppButtonSecondary
|
||||
v-if="isExistingMockServer"
|
||||
:label="t('mock_server.view_logs')"
|
||||
@click="showLogs = true"
|
||||
/>
|
||||
|
||||
<!-- Create Mock Server Button for new mock server -->
|
||||
<HoppButtonPrimary
|
||||
v-else
|
||||
|
|
@ -272,56 +274,48 @@
|
|||
@click="createMockServer"
|
||||
/>
|
||||
|
||||
<!-- Close button shown after server creation -->
|
||||
<HoppButtonSecondary
|
||||
v-if="showCloseButton"
|
||||
:label="t('action.close')"
|
||||
:label="t('action.cancel')"
|
||||
outline
|
||||
@click="closeModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</HoppSmartModal>
|
||||
<MockServerLogs
|
||||
v-if="showLogs && existingMockServer"
|
||||
:show="showLogs"
|
||||
:mock-server-i-d="existingMockServer.id"
|
||||
@close="showLogs = false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import {
|
||||
showCreateMockServerModal$,
|
||||
mockServers$,
|
||||
addMockServer,
|
||||
updateMockServer as updateMockServerInStore,
|
||||
} from "~/newstore/mockServers"
|
||||
import { restCollections$ } from "~/newstore/collections"
|
||||
import { TeamCollectionsService } from "~/services/team-collection.service"
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { refAutoReset } from "@vueuse/core"
|
||||
import { useService } from "dioc/vue"
|
||||
import { WorkspaceService } from "~/services/workspace.service"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { computed, ref, watch } from "vue"
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
import { MockServer, WorkspaceType } from "~/helpers/backend/graphql"
|
||||
import {
|
||||
createMockServer as createMockServerMutation,
|
||||
updateMockServer,
|
||||
} from "~/helpers/backend/mutations/MockServer"
|
||||
import { MockServer, WorkspaceType } from "~/helpers/backend/graphql"
|
||||
import { copyToClipboard as copyToClipboardHelper } from "~/helpers/utils/clipboard"
|
||||
import { refAutoReset } from "@vueuse/core"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { restCollections$ } from "~/newstore/collections"
|
||||
import {
|
||||
addMockServer,
|
||||
mockServers$,
|
||||
showCreateMockServerModal$,
|
||||
updateMockServer as updateMockServerInStore,
|
||||
} from "~/newstore/mockServers"
|
||||
import { TeamCollectionsService } from "~/services/team-collection.service"
|
||||
import { WorkspaceService } from "~/services/workspace.service"
|
||||
|
||||
// Icons
|
||||
import IconServer from "~icons/lucide/server"
|
||||
import IconPlay from "~icons/lucide/play"
|
||||
import IconSquare from "~icons/lucide/square"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconCheck from "~icons/lucide/check"
|
||||
import MockServerLogs from "~/components/mockServer/MockServerLogs.vue"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconPlay from "~icons/lucide/play"
|
||||
import IconServer from "~icons/lucide/server"
|
||||
import IconSquare from "~icons/lucide/square"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
|
@ -354,7 +348,6 @@ const showCloseButton = ref(false)
|
|||
const createdServer = ref<MockServer | null>(null)
|
||||
const delayInMsVal = ref<string>("0")
|
||||
const isPublic = ref<boolean>(true)
|
||||
const showLogs = ref(false)
|
||||
const selectedCollectionID = ref("")
|
||||
const selectedCollectionName = ref("")
|
||||
const tippyActions = ref<TippyComponent | null>(null)
|
||||
|
|
@ -362,16 +355,26 @@ const tippyActions = ref<TippyComponent | null>(null)
|
|||
// Props computed from modal data
|
||||
const show = computed(() => modalData.value.show)
|
||||
const collectionID = computed(() => modalData.value.collectionID)
|
||||
const collectionName = computed(
|
||||
() => modalData.value.collectionName || "Unknown Collection"
|
||||
)
|
||||
const collectionName = computed(() => {
|
||||
// Prefer name provided by modalData (pre-selected from caller)
|
||||
if (modalData.value.collectionName) return modalData.value.collectionName
|
||||
|
||||
// Find existing mock server for this collection
|
||||
// If user selected a collection inside the modal, use that
|
||||
if (selectedCollectionName.value) return selectedCollectionName.value
|
||||
|
||||
// Try finding the collection from availableCollections using effectiveCollectionID
|
||||
const id = effectiveCollectionID.value
|
||||
if (!id) return "Unknown Collection"
|
||||
|
||||
const coll = availableCollections.value.find((c: any) => (c as any).id === id)
|
||||
return (coll as any)?.name || (coll as any)?.title || "Unknown Collection"
|
||||
})
|
||||
|
||||
// Find existing mock server for the effective collection (pre-selected or user-selected)
|
||||
const existingMockServer = computed(() => {
|
||||
if (!collectionID.value) return null
|
||||
return mockServers.value.find(
|
||||
(server) => server.collectionID === collectionID.value
|
||||
)
|
||||
const collId = effectiveCollectionID.value
|
||||
if (!collId) return null
|
||||
return mockServers.value.find((server) => server.collectionID === collId)
|
||||
})
|
||||
|
||||
const isExistingMockServer = computed(() => !!existingMockServer.value)
|
||||
|
|
@ -379,7 +382,7 @@ const isExistingMockServer = computed(() => !!existingMockServer.value)
|
|||
// Collection options for the selector (only root collections)
|
||||
const collectionOptions = computed(() => {
|
||||
return availableCollections.value.map((collection) => {
|
||||
const collectionId = collection.id || collection._ref_id
|
||||
const collectionId = collection.id
|
||||
const hasMockServer = mockServers.value.some(
|
||||
(server) => server.collectionID === collectionId
|
||||
)
|
||||
|
|
@ -410,21 +413,6 @@ const selectCollection = (option: any) => {
|
|||
selectedCollectionName.value = option.label
|
||||
}
|
||||
|
||||
// Mock server base URL construction
|
||||
const mockServerBaseUrl = computed(() => {
|
||||
if (!existingMockServer.value) return ""
|
||||
|
||||
// Extract host and port from backend API URL
|
||||
const backendApiUrl =
|
||||
import.meta.env.VITE_BACKEND_API_URL || "http://localhost:3170"
|
||||
const url = new URL(backendApiUrl)
|
||||
const protocol = url.protocol
|
||||
const port = url.port ? `:${url.port}` : ""
|
||||
|
||||
// Create subdomain URL: mock-1234.localhost:3170
|
||||
return `${protocol}//${existingMockServer.value.subdomain}.${url.hostname}${port}`
|
||||
})
|
||||
|
||||
// Copy functionality
|
||||
const copyIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
||||
IconCopy,
|
||||
|
|
@ -483,8 +471,10 @@ const createMockServer = async () => {
|
|||
),
|
||||
TE.match(
|
||||
(error) => {
|
||||
// `error` here is the message string produced by the mutation helper.
|
||||
console.error("Failed to create mock server:", error)
|
||||
toast.error(t("error.something_went_wrong"))
|
||||
// Show the backend-provided error message if available, otherwise fallback to generic
|
||||
toast.error(String(error) || t("error.something_went_wrong"))
|
||||
loading.value = false
|
||||
},
|
||||
(result) => {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<div
|
||||
class="flex-1 px-3 py-2 border border-divider rounded bg-primaryLight text-body font-mono"
|
||||
class="flex-1 px-3 py-2 border border-divider rounded bg-primaryLight"
|
||||
>
|
||||
{{
|
||||
mockServer.serverUrlDomainBased || mockServer.serverUrlPathBased
|
||||
|
|
@ -58,18 +58,28 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Toggle -->
|
||||
<!-- Status Display (Read-only) -->
|
||||
<div class="flex flex-col space-y-2">
|
||||
<label class="text-sm font-semibold text-secondaryDark">
|
||||
{{ t("mock_server.status") }}
|
||||
{{ t("app.status") }}
|
||||
</label>
|
||||
<div class="flex items-center space-x-3">
|
||||
<HoppSmartToggle :on="isActive" @change="isActive = !isActive" />
|
||||
<span class="text-sm text-secondaryLight">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span
|
||||
class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium"
|
||||
:class="
|
||||
isActive
|
||||
? 'bg-green-600/20 text-green-200 border border-green-900/50'
|
||||
: 'bg-gray-600/20 text-gray-200 border border-gray-900/50'
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="w-2 h-2 rounded-full mr-2"
|
||||
:class="isActive ? 'bg-green-400' : 'bg-gray-400'"
|
||||
></span>
|
||||
{{
|
||||
isActive
|
||||
? t("mock_server.server_running")
|
||||
: t("mock_server.server_stopped")
|
||||
? t("mockServer.dashboard.active")
|
||||
: t("mockServer.dashboard.inactive")
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -99,7 +109,7 @@
|
|||
</label>
|
||||
<div class="flex items-center space-x-3">
|
||||
<HoppSmartToggle :on="isPublic" @change="isPublic = !isPublic" />
|
||||
<span class="text-sm text-secondaryLight">
|
||||
<span class="text-secondaryLight">
|
||||
{{
|
||||
isPublic
|
||||
? t("mock_server.public_description")
|
||||
|
|
@ -112,31 +122,52 @@
|
|||
</template>
|
||||
|
||||
<template #footer>
|
||||
<span class="flex space-x-2">
|
||||
<div class="flex justify-end space-x-2">
|
||||
<!-- Start/Stop Server Button (consistent with CreateMockServer) -->
|
||||
<HoppButtonPrimary
|
||||
:label="
|
||||
isActive
|
||||
? t('mock_server.stop_server')
|
||||
: t('mock_server.start_server')
|
||||
"
|
||||
:loading="loading"
|
||||
:icon="isActive ? IconSquare : IconPlay"
|
||||
@click="toggleMockServer"
|
||||
/>
|
||||
|
||||
<!-- Save button for other settings -->
|
||||
<HoppButtonSecondary
|
||||
outline
|
||||
:label="t('action.save')"
|
||||
:loading="loading"
|
||||
@click="updateMockServer"
|
||||
/>
|
||||
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.cancel')"
|
||||
@click="emit('hide-modal')"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</HoppSmartModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { ref, watch } from "vue"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { updateMockServer as updateMockServerMutation } from "~/helpers/backend/mutations/MockServer"
|
||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||
import type { MockServer } from "~/newstore/mockServers"
|
||||
import { updateMockServer as updateMockServerInStore } from "~/newstore/mockServers"
|
||||
|
||||
// Icons
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconCheck from "~icons/lucide/check"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconPlay from "~icons/lucide/play"
|
||||
import IconSquare from "~icons/lucide/square"
|
||||
|
||||
interface Props {
|
||||
show: boolean
|
||||
|
|
@ -176,22 +207,74 @@ watch(
|
|||
const updateMockServer = async () => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
// TODO: Implement mock server update API call
|
||||
// const updatedMockServer = await updateMockServerAPI(props.mockServer.id, {
|
||||
// name: mockServerName.value,
|
||||
// isActive: isActive.value,
|
||||
// delayInMs: delayInMs.value,
|
||||
// isPublic: isPublic.value
|
||||
// })
|
||||
|
||||
toast.success(t("mock_server.mock_server_updated"))
|
||||
emit("hide-modal")
|
||||
} catch (error) {
|
||||
toast.error(t("error.something_went_wrong"))
|
||||
} finally {
|
||||
loading.value = false
|
||||
// Prepare payload
|
||||
const payload = {
|
||||
name: mockServerName.value,
|
||||
isActive: isActive.value,
|
||||
delayInMs: delayInMs.value,
|
||||
isPublic: isPublic.value,
|
||||
}
|
||||
|
||||
await pipe(
|
||||
updateMockServerMutation(props.mockServer.id, payload),
|
||||
TE.match(
|
||||
(error) => {
|
||||
console.error("Failed to update mock server:", error)
|
||||
toast.error(t("error.something_went_wrong"))
|
||||
loading.value = false
|
||||
},
|
||||
() => {
|
||||
// Update the mock server in the store with the changed fields
|
||||
updateMockServerInStore(props.mockServer.id, payload)
|
||||
|
||||
toast.success(t("mock_server.mock_server_updated"))
|
||||
emit("hide-modal")
|
||||
|
||||
// Update local state in case parent doesn't refresh immediately
|
||||
mockServerName.value = payload.name
|
||||
isActive.value = payload.isActive
|
||||
delayInMs.value = payload.delayInMs || 0
|
||||
isPublic.value = payload.isPublic
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
)
|
||||
)()
|
||||
}
|
||||
|
||||
// Toggle mock server active state (consistent with CreateMockServer)
|
||||
const toggleMockServer = async () => {
|
||||
loading.value = true
|
||||
const newActiveState = !isActive.value
|
||||
|
||||
await pipe(
|
||||
updateMockServerMutation(props.mockServer.id, { isActive: newActiveState }),
|
||||
TE.match(
|
||||
(error) => {
|
||||
console.error("Failed to update mock server:", error)
|
||||
toast.error(t("error.something_went_wrong"))
|
||||
loading.value = false
|
||||
},
|
||||
(result) => {
|
||||
console.log("Mock server updated:", result)
|
||||
toast.success(
|
||||
newActiveState
|
||||
? t("mock_server.mock_server_started")
|
||||
: t("mock_server.mock_server_stopped")
|
||||
)
|
||||
|
||||
// Update the mock server in the store
|
||||
updateMockServerInStore(props.mockServer.id, {
|
||||
isActive: newActiveState,
|
||||
})
|
||||
|
||||
// Update local state
|
||||
isActive.value = newActiveState
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
)
|
||||
)()
|
||||
}
|
||||
|
||||
const copyToClipboardHandler = async (text: string) => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
<template>
|
||||
<div class="flex flex-col space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<button
|
||||
v-if="content && content.trim()"
|
||||
class="flex items-center space-x-1 font-medium text-secondary hover:text-secondaryDark transition-colors"
|
||||
@click="toggleExpanded"
|
||||
>
|
||||
<icon-lucide-chevron-right
|
||||
:class="[
|
||||
'h-4 w-4 transition-transform duration-200',
|
||||
isExpanded ? 'rotate-90' : 'rotate-0',
|
||||
]"
|
||||
/>
|
||||
<span>{{ title }}</span>
|
||||
</button>
|
||||
<span v-else class="font-medium text-secondary">{{ title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="content && content.trim() && isExpanded" class="relative group">
|
||||
<div
|
||||
class="absolute top-2 right-3 z-10 p-2 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
>
|
||||
<HoppSmartItem
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.copy')"
|
||||
:icon="copySuccess ? IconCheck : IconCopy"
|
||||
class="p-1 rounded transition-colors"
|
||||
@click="copyContent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<pre
|
||||
class="relative whitespace-pre-wrap cursor-text select-text break-words bg-primaryLight border border-dividerLight rounded-sm p-4 max-h-96 overflow-y-auto"
|
||||
:class="[isValidJSON ? 'text-accent' : 'text-secondaryLight']"
|
||||
>{{ formattedContent }}</pre
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="!content || !content.trim()"
|
||||
class="text-xs text-secondaryLight italic py-2"
|
||||
>
|
||||
{{ t("state.no_content_found") }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||
import { refAutoReset } from "@vueuse/core"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconCheck from "~icons/lucide/check"
|
||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
content: string | null | undefined
|
||||
defaultExpanded?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
defaultExpanded: false,
|
||||
})
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const isExpanded = ref(props.defaultExpanded)
|
||||
const copySuccess = refAutoReset(false, 1000)
|
||||
|
||||
const toggleExpanded = () => {
|
||||
isExpanded.value = !isExpanded.value
|
||||
}
|
||||
|
||||
const isValidJSON = computed(() => {
|
||||
if (!props.content) return false
|
||||
try {
|
||||
JSON.parse(props.content)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
const formattedContent = computed(() => {
|
||||
if (!props.content) return ""
|
||||
|
||||
if (isValidJSON.value) {
|
||||
try {
|
||||
const parsed = JSON.parse(props.content)
|
||||
return JSON.stringify(parsed, null, 2)
|
||||
} catch {
|
||||
return props.content
|
||||
}
|
||||
}
|
||||
|
||||
return props.content
|
||||
})
|
||||
|
||||
const copyContent = () => {
|
||||
if (!props.content) return
|
||||
|
||||
try {
|
||||
copyToClipboard(formattedContent.value)
|
||||
copySuccess.value = true
|
||||
} catch (error) {
|
||||
console.error("Failed to copy content:", error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -58,13 +58,16 @@
|
|||
@click="openCreateModal"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="divide-y divide-dividerLight">
|
||||
<div v-else class="flex flex-1 flex-col space-y-2 py-2">
|
||||
<div
|
||||
v-for="mockServer in mockServers"
|
||||
:key="mockServer.id"
|
||||
class="group flex items-stretch"
|
||||
>
|
||||
<span class="flex cursor-pointer items-center justify-center px-4">
|
||||
<span
|
||||
class="flex cursor-pointer items-center justify-center px-4"
|
||||
@click="openMockServerLogs(mockServer)"
|
||||
>
|
||||
<component
|
||||
:is="IconServer"
|
||||
class="svg-icons"
|
||||
|
|
@ -75,7 +78,7 @@
|
|||
/>
|
||||
</span>
|
||||
<span
|
||||
class="flex min-w-0 flex-1 cursor-pointer py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
class="flex min-w-0 flex-1 cursor-pointer pr-2 transition group-hover:text-secondaryDark"
|
||||
@click="openMockServerLogs(mockServer)"
|
||||
>
|
||||
<div class="flex min-w-0 flex-1 flex-col">
|
||||
|
|
@ -201,53 +204,48 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch } from "vue"
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { computed, ref } from "vue"
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
import { useMockServerStatus } from "~/composables/mockServer"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||
import { platform } from "~/platform"
|
||||
import type { MockServer } from "~/newstore/mockServers"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { platform } from "~/platform"
|
||||
|
||||
import {
|
||||
loadMockServers,
|
||||
deleteMockServer as deleteMockServerInStore,
|
||||
showCreateMockServerModal$,
|
||||
updateMockServer as updateMockServerInStore,
|
||||
deleteMockServer as deleteMockServerInStore,
|
||||
} from "~/newstore/mockServers"
|
||||
import { useService } from "dioc/vue"
|
||||
import { WorkspaceService } from "~/services/workspace.service"
|
||||
|
||||
import {
|
||||
updateMockServer as updateMockServerMutation,
|
||||
deleteMockServer as deleteMockServerMutation,
|
||||
} from "~/helpers/backend/mutations/MockServer"
|
||||
import MockServerCreateMockServer from "~/components/mockServer/CreateMockServer.vue"
|
||||
import MockServerEditMockServer from "~/components/mockServer/EditMockServer.vue"
|
||||
import MockServerLogs from "~/components/mockServer/MockServerLogs.vue"
|
||||
import {
|
||||
deleteMockServer as deleteMockServerMutation,
|
||||
updateMockServer as updateMockServerMutation,
|
||||
} from "~/helpers/backend/mutations/MockServer"
|
||||
|
||||
// Icons
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconServer from "~icons/lucide/server"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconPlay from "~icons/lucide/play"
|
||||
import IconStop from "~icons/lucide/stop-circle"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconCheck from "~icons/lucide/check"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import IconPlay from "~icons/lucide/play"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconServer from "~icons/lucide/server"
|
||||
import IconStop from "~icons/lucide/stop-circle"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
const colorMode = useColorMode()
|
||||
const { mockServers } = useMockServerStatus()
|
||||
const workspaceService = useService(WorkspaceService)
|
||||
|
||||
const loading = ref(false)
|
||||
const showCreateModal = ref(false)
|
||||
const showEditModal = ref(false)
|
||||
|
|
@ -376,28 +374,4 @@ const openCreateModal = () => {
|
|||
collectionName: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
// Load mock servers on component mount
|
||||
// Load mock servers for current workspace
|
||||
const loadCurrentWorkspaceMockServers = async () => {
|
||||
if (!platform.auth.getCurrentUser()) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
await loadMockServers()
|
||||
} catch (error) {
|
||||
console.error("Failed to load mock servers:", error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadCurrentWorkspaceMockServers)
|
||||
|
||||
// Watch for workspace changes and reload mock servers
|
||||
watch(
|
||||
() => workspaceService.currentWorkspace.value,
|
||||
loadCurrentWorkspaceMockServers,
|
||||
{ deep: true }
|
||||
)
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
v-if="show"
|
||||
dialog
|
||||
:title="t('mock_server.logs_title')"
|
||||
styles="sm:max-w-4xl"
|
||||
@close="close"
|
||||
>
|
||||
<template #body>
|
||||
|
|
@ -19,60 +20,78 @@
|
|||
<div
|
||||
v-for="log in logs"
|
||||
:key="log.id"
|
||||
class="mb-4 p-3 border rounded"
|
||||
class="mb-4 border border-dividerDark rounded overflow-hidden"
|
||||
>
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="font-mono text-sm text-secondaryDark">
|
||||
{{ log.requestMethod }} {{ log.requestPath }}
|
||||
</div>
|
||||
<div class="text-sm text-secondaryLight">
|
||||
{{ new Date(log.executedAt).toLocaleString() }}
|
||||
<div
|
||||
class="p-3 cursor-pointer hover:bg-primaryLight/5 transition-colors duration-200"
|
||||
@click="toggleLogExpansion(log.id)"
|
||||
>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center space-x-3">
|
||||
<icon-lucide-chevron-right
|
||||
class="w-4 h-4 transition-transform duration-200"
|
||||
:class="{ 'rotate-90': isLogExpanded(log.id) }"
|
||||
/>
|
||||
<div
|
||||
:style="{
|
||||
color: getMethodLabelColor(log.requestMethod),
|
||||
}"
|
||||
class="flex-1"
|
||||
>
|
||||
{{ log.requestMethod }}
|
||||
</div>
|
||||
<div class="text-secondaryDark truncate">
|
||||
{{ log.requestPath }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="log.responseStatus"
|
||||
class="px-2 py-1 rounded text-xs font-medium"
|
||||
:class="getStatusColor(log.responseStatus)"
|
||||
>
|
||||
{{ log.responseStatus }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-secondaryLight flex flex-1 justify-center">
|
||||
{{ formatExecutedAt(log.executedAt) }}
|
||||
</div>
|
||||
<HoppSmartItem
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.delete')"
|
||||
:icon="IconTrash"
|
||||
class="bg-transparent hover:bg-transparent !text-red-500"
|
||||
@click.stop="confirmRemoveLog(log.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 text-xs text-secondaryLight">
|
||||
<div>
|
||||
<span class="font-medium"
|
||||
>{{ t("mock_server.request_headers") }}:</span
|
||||
>
|
||||
<pre class="whitespace-pre-wrap">{{
|
||||
prettyJSON(log.requestHeaders)
|
||||
}}</pre>
|
||||
|
||||
<div
|
||||
v-if="isLogExpanded(log.id)"
|
||||
class="border-t border-dividerDark"
|
||||
>
|
||||
<div class="py-4 px-3 text-xs flex flex-col space-y-4">
|
||||
<MockServerLogSection
|
||||
:title="t('mock_server.request_headers')"
|
||||
:content="log.requestHeaders"
|
||||
/>
|
||||
|
||||
<MockServerLogSection
|
||||
v-if="log.requestBody"
|
||||
:title="t('mock_server.request_body')"
|
||||
:content="log.requestBody"
|
||||
/>
|
||||
|
||||
<MockServerLogSection
|
||||
:title="t('mock_server.response_headers')"
|
||||
:content="log.responseHeaders"
|
||||
/>
|
||||
|
||||
<MockServerLogSection
|
||||
v-if="log.responseBody"
|
||||
:title="t('mock_server.response_body')"
|
||||
:content="log.responseBody"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="log.requestBody">
|
||||
<span class="font-medium"
|
||||
>{{ t("mock_server.request_body") }}:</span
|
||||
>
|
||||
<pre class="whitespace-pre-wrap">{{
|
||||
prettyJSON(log.requestBody)
|
||||
}}</pre>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<span class="font-medium"
|
||||
>{{ t("mock_server.response_status") }}:</span
|
||||
>
|
||||
{{ log.responseStatus }}
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
<span class="font-medium"
|
||||
>{{ t("mock_server.response_headers") }}:</span
|
||||
>
|
||||
<pre class="whitespace-pre-wrap">{{
|
||||
prettyJSON(log.responseHeaders)
|
||||
}}</pre>
|
||||
</div>
|
||||
<div v-if="log.responseBody" class="mt-1">
|
||||
<span class="font-medium"
|
||||
>{{ t("mock_server.response_body") }}:</span
|
||||
>
|
||||
<pre class="whitespace-pre-wrap">{{
|
||||
prettyJSON(log.responseBody)
|
||||
}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end mt-2">
|
||||
<HoppButtonSecondary outline @click="removeLog(log.id)">{{
|
||||
t("action.delete")
|
||||
}}</HoppButtonSecondary>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -80,12 +99,17 @@
|
|||
</template>
|
||||
<template #footer>
|
||||
<div class="flex justify-end">
|
||||
<HoppButtonSecondary @click="close">{{
|
||||
t("action.close")
|
||||
}}</HoppButtonSecondary>
|
||||
<HoppButtonPrimary :label="t('action.close')" @click="close" />
|
||||
</div>
|
||||
</template>
|
||||
</HoppSmartModal>
|
||||
|
||||
<HoppSmartConfirmModal
|
||||
:show="showDeleteConfirm"
|
||||
:title="t('mock_server.confirm_delete_log')"
|
||||
@hide-modal="showDeleteConfirm = false"
|
||||
@resolve="confirmDelete"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
@ -98,6 +122,9 @@ import {
|
|||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import IconTrash from "~icons/lucide/trash"
|
||||
import { getMethodLabelColor } from "~/helpers/rest/labelColoring"
|
||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||
|
||||
const props = defineProps<{ show: boolean; mockServerID: string }>()
|
||||
const emit = defineEmits<{ (e: "close"): void }>()
|
||||
|
|
@ -107,6 +134,9 @@ const toast = useToast()
|
|||
|
||||
const loading = ref(false)
|
||||
const logs = ref<any[]>([])
|
||||
const expandedLogs = ref<Set<string>>(new Set())
|
||||
const showDeleteConfirm = ref(false)
|
||||
const logToDelete = ref<string | null>(null)
|
||||
|
||||
const fetchLogs = async () => {
|
||||
loading.value = true
|
||||
|
|
@ -132,31 +162,54 @@ onMounted(() => {
|
|||
|
||||
const close = () => emit("close")
|
||||
|
||||
const removeLog = async (id: string) => {
|
||||
await pipe(
|
||||
deleteMockServerLog(id),
|
||||
TE.match(
|
||||
(err) => {
|
||||
console.error("Failed to delete log", err)
|
||||
toast.error(t("error.something_went_wrong"))
|
||||
},
|
||||
(res) => {
|
||||
if (res) {
|
||||
logs.value = logs.value.filter((l) => l.id !== id)
|
||||
toast.success(t("mock_server.log_deleted"))
|
||||
}
|
||||
}
|
||||
)
|
||||
)()
|
||||
const confirmRemoveLog = (id: string) => {
|
||||
logToDelete.value = id
|
||||
showDeleteConfirm.value = true
|
||||
}
|
||||
|
||||
const prettyJSON = (s: string | null | undefined) => {
|
||||
try {
|
||||
if (!s) return ""
|
||||
const obj = typeof s === "string" ? JSON.parse(s) : s
|
||||
return JSON.stringify(obj, null, 2)
|
||||
} catch (e) {
|
||||
return String(s)
|
||||
const confirmDelete = async () => {
|
||||
if (logToDelete.value) {
|
||||
await pipe(
|
||||
deleteMockServerLog(logToDelete.value),
|
||||
TE.match(
|
||||
(err) => {
|
||||
console.error("Failed to delete log", err)
|
||||
toast.error(t("error.something_went_wrong"))
|
||||
},
|
||||
(res) => {
|
||||
if (res) {
|
||||
logs.value = logs.value.filter((l) => l.id !== logToDelete.value)
|
||||
toast.success(t("mock_server.log_deleted"))
|
||||
logToDelete.value = null
|
||||
showDeleteConfirm.value = false
|
||||
}
|
||||
}
|
||||
)
|
||||
)()
|
||||
}
|
||||
}
|
||||
|
||||
const formatExecutedAt = (executedAt: string) => {
|
||||
return new Date(executedAt).toLocaleString()
|
||||
}
|
||||
|
||||
const toggleLogExpansion = (id: string) => {
|
||||
if (expandedLogs.value.has(id)) {
|
||||
expandedLogs.value.delete(id)
|
||||
} else {
|
||||
expandedLogs.value.add(id)
|
||||
}
|
||||
}
|
||||
|
||||
const isLogExpanded = (id: string) => {
|
||||
return expandedLogs.value.has(id)
|
||||
}
|
||||
|
||||
const getStatusColor = (statusCode: number) => {
|
||||
const status = statusCode.toString()
|
||||
if (status.startsWith("2")) return "bg-green-800/20 text-green-600"
|
||||
if (status.startsWith("4")) return "bg-yellow-800/20 text-yellow-400"
|
||||
if (status.startsWith("5")) return "bg-red-800/20 text-red-400"
|
||||
return "bg-gray-600/20 text-secondaryDark"
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
import { onMounted, watch } from "vue"
|
||||
import { useService } from "dioc/vue"
|
||||
import { WorkspaceService } from "~/services/workspace.service"
|
||||
import { setMockServers, loadMockServers } from "~/newstore/mockServers"
|
||||
import { platform } from "~/platform"
|
||||
import { useSetting } from "./settings"
|
||||
|
||||
/**
|
||||
* Composable to handle mock server state when workspace changes
|
||||
* This ensures mock servers are cleared immediately when switching workspaces
|
||||
* to prevent showing stale data from the previous workspace
|
||||
*/
|
||||
export function useMockServerWorkspaceSync() {
|
||||
const workspaceService = useService(WorkspaceService)
|
||||
const ENABLE_EXPERIMENTAL_MOCK_SERVERS = useSetting(
|
||||
"ENABLE_EXPERIMENTAL_MOCK_SERVERS"
|
||||
)
|
||||
const isAuthenticated = !!platform.auth.getCurrentUser()
|
||||
|
||||
// Initial load of mock servers for the current workspace
|
||||
onMounted(() => {
|
||||
if (!isAuthenticated || !ENABLE_EXPERIMENTAL_MOCK_SERVERS.value) return
|
||||
loadMockServers().catch(() => setMockServers([]))
|
||||
})
|
||||
|
||||
// Watch for workspace changes and clear mock servers immediately
|
||||
watch(
|
||||
() => workspaceService.currentWorkspace.value,
|
||||
(newWorkspace, oldWorkspace) => {
|
||||
if (!isAuthenticated || !ENABLE_EXPERIMENTAL_MOCK_SERVERS.value) return
|
||||
|
||||
// Clear mock servers when workspace changes to prevent stale data
|
||||
if (
|
||||
newWorkspace?.type !== oldWorkspace?.type ||
|
||||
(newWorkspace?.type === "team" &&
|
||||
oldWorkspace?.type === "team" &&
|
||||
newWorkspace.teamID !== oldWorkspace.teamID)
|
||||
) {
|
||||
// Clear mock servers immediately to prevent showing stale data
|
||||
setMockServers([])
|
||||
|
||||
// If user is authenticated, reload mock servers for the new workspace
|
||||
if (platform.auth.getCurrentUser()) {
|
||||
// fire-and-forget; loadMockServers handles errors internally
|
||||
loadMockServers().catch(() => setMockServers([]))
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: false }
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
mutation ImportUserCollectionsFromJSON(
|
||||
$jsonString: String!
|
||||
$reqType: ReqType!
|
||||
$parentCollectionID: ID
|
||||
) {
|
||||
importUserCollectionsFromJSON(
|
||||
jsonString: $jsonString
|
||||
reqType: $reqType
|
||||
parentCollectionID: $parentCollectionID
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
query GetGQLRootUserCollections($cursor: ID, $take: Int) {
|
||||
rootGQLUserCollections(cursor: $cursor, take: $take) {
|
||||
id
|
||||
title
|
||||
data
|
||||
type
|
||||
parent {
|
||||
id
|
||||
}
|
||||
requests {
|
||||
id
|
||||
title
|
||||
request
|
||||
type
|
||||
collectionID
|
||||
}
|
||||
childrenGQL {
|
||||
id
|
||||
title
|
||||
data
|
||||
type
|
||||
parent {
|
||||
id
|
||||
}
|
||||
requests {
|
||||
id
|
||||
title
|
||||
request
|
||||
type
|
||||
collectionID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
query GetUserRootCollections($cursor: ID, $take: Int) {
|
||||
rootRESTUserCollections(cursor: $cursor, take: $take) {
|
||||
id
|
||||
title
|
||||
data
|
||||
type
|
||||
parent {
|
||||
id
|
||||
}
|
||||
requests {
|
||||
id
|
||||
title
|
||||
request
|
||||
type
|
||||
collectionID
|
||||
}
|
||||
childrenREST {
|
||||
id
|
||||
title
|
||||
data
|
||||
type
|
||||
parent {
|
||||
id
|
||||
}
|
||||
requests {
|
||||
id
|
||||
title
|
||||
request
|
||||
type
|
||||
collectionID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import * as TE from "fp-ts/TaskEither"
|
||||
import { client } from "../GQLClient"
|
||||
import { GQLError } from "../GQLClient"
|
||||
import { getI18n } from "~/modules/i18n"
|
||||
import {
|
||||
CreateMockServerDocument,
|
||||
UpdateMockServerDocument,
|
||||
|
|
@ -36,6 +38,7 @@ export type MockServer = {
|
|||
}
|
||||
|
||||
type CreateMockServerError =
|
||||
| "mock_server/invalid_collection"
|
||||
| "mock_server/invalid_collection_id"
|
||||
| "mock_server/name_too_short"
|
||||
| "mock_server/limit_exceeded"
|
||||
|
|
@ -73,7 +76,26 @@ export const createMockServer = (
|
|||
.toPromise()
|
||||
|
||||
if (result.error) {
|
||||
throw new Error(result.error.message || "Failed to create mock server")
|
||||
// Try to extract a useful error message from the GraphQL error
|
||||
const err: any = result.error
|
||||
let message = err.message
|
||||
|
||||
// urql exposes GraphQL errors in graphQLErrors array
|
||||
const gqlErr = (err.graphQLErrors && err.graphQLErrors[0]) || null
|
||||
if (gqlErr) {
|
||||
// Prefer originalError.message from backend if present (it may be an array of messages)
|
||||
const orig =
|
||||
gqlErr.extensions &&
|
||||
gqlErr.extensions.originalError &&
|
||||
gqlErr.extensions.originalError.message
|
||||
if (orig) {
|
||||
message = Array.isArray(orig) ? orig.join(", ") : String(orig)
|
||||
} else if (gqlErr.message) {
|
||||
message = gqlErr.message
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
if (!result.data) {
|
||||
|
|
@ -183,3 +205,36 @@ export const getTeamMockServers = (
|
|||
},
|
||||
(error) => (error as Error).message as CreateMockServerError
|
||||
)
|
||||
|
||||
// Centralized mapper for backend GraphQL error tokens to user-facing messages.
|
||||
export const getErrorMessage = (err: GQLError<string> | string | Error) => {
|
||||
const t = getI18n()
|
||||
|
||||
// Normalize to GQLError-like shape
|
||||
let gErr: GQLError<string> | null = null
|
||||
|
||||
if (typeof err === "string") {
|
||||
gErr = { type: "gql_error", error: err }
|
||||
} else if (err instanceof Error) {
|
||||
gErr = { type: "network_error", error: err }
|
||||
} else if ((err as any)?.type) {
|
||||
gErr = err as GQLError<string>
|
||||
}
|
||||
|
||||
if (!gErr) return t("error.something_went_wrong")
|
||||
|
||||
if (gErr.type === "network_error") {
|
||||
console.error(gErr.error)
|
||||
return t("error.network_error")
|
||||
}
|
||||
|
||||
const code = String(gErr.error)
|
||||
|
||||
switch (code) {
|
||||
case "mock_server/invalid_collection":
|
||||
case "mock_server/invalid_collection_id":
|
||||
return t("mock_server.invalid_collection_error")
|
||||
default:
|
||||
return t("error.something_went_wrong")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,203 @@
|
|||
import { runMutation } from "../GQLClient"
|
||||
import { runGQLQuery } from "../GQLClient"
|
||||
import {
|
||||
GetGqlRootUserCollectionsDocument,
|
||||
GetGqlRootUserCollectionsQuery,
|
||||
GetGqlRootUserCollectionsQueryVariables,
|
||||
GetUserRootCollectionsDocument,
|
||||
GetUserRootCollectionsQuery,
|
||||
GetUserRootCollectionsQueryVariables,
|
||||
ImportUserCollectionsFromJsonDocument,
|
||||
ImportUserCollectionsFromJsonMutation,
|
||||
ImportUserCollectionsFromJsonMutationVariables,
|
||||
ReqType,
|
||||
UserCollection,
|
||||
UserRequest,
|
||||
} from "../graphql"
|
||||
import {
|
||||
HoppCollection,
|
||||
makeCollection,
|
||||
HoppRESTRequest,
|
||||
HoppGQLRequest,
|
||||
getDefaultRESTRequest,
|
||||
getDefaultGQLRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import * as E from "fp-ts/Either"
|
||||
|
||||
export const importUserCollectionsFromJSON = (
|
||||
collectionJSON: string,
|
||||
reqType: ReqType,
|
||||
parentCollectionID?: string
|
||||
) =>
|
||||
runMutation<
|
||||
ImportUserCollectionsFromJsonMutation,
|
||||
ImportUserCollectionsFromJsonMutationVariables,
|
||||
""
|
||||
>(ImportUserCollectionsFromJsonDocument, {
|
||||
jsonString: collectionJSON,
|
||||
reqType,
|
||||
parentCollectionID,
|
||||
})
|
||||
|
||||
// Use generated GraphQL documents instead of inline gql tags
|
||||
export const getUserRootCollections = () =>
|
||||
runGQLQuery<
|
||||
GetUserRootCollectionsQuery,
|
||||
GetUserRootCollectionsQueryVariables,
|
||||
""
|
||||
>({
|
||||
query: GetUserRootCollectionsDocument,
|
||||
variables: {},
|
||||
})
|
||||
|
||||
export const getGQLRootUserCollections = () =>
|
||||
runGQLQuery<
|
||||
GetGqlRootUserCollectionsQuery,
|
||||
GetGqlRootUserCollectionsQueryVariables,
|
||||
""
|
||||
>({
|
||||
query: GetGqlRootUserCollectionsDocument,
|
||||
variables: {},
|
||||
})
|
||||
|
||||
/**
|
||||
* Converts a UserRequest from backend format to HoppRequest format
|
||||
*/
|
||||
function convertUserRequestToHoppRequest(
|
||||
userRequest: UserRequest
|
||||
): HoppRESTRequest | HoppGQLRequest {
|
||||
try {
|
||||
const parsedRequest = JSON.parse(userRequest.request)
|
||||
|
||||
// Add the backend ID and title to the request
|
||||
const request = {
|
||||
...parsedRequest,
|
||||
id: userRequest.id,
|
||||
name: userRequest.title,
|
||||
}
|
||||
|
||||
return request
|
||||
} catch (error) {
|
||||
console.warn("Failed to parse user request data:", error)
|
||||
|
||||
// Return a default request if parsing fails
|
||||
if (userRequest.type === ReqType.Rest) {
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
defaultRequest.id = userRequest.id
|
||||
defaultRequest.name = userRequest.title
|
||||
return defaultRequest
|
||||
}
|
||||
const defaultRequest = getDefaultGQLRequest()
|
||||
defaultRequest.id = userRequest.id
|
||||
defaultRequest.name = userRequest.title
|
||||
return defaultRequest
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse collection data similar to the existing parseCollectionData function in helpers.ts
|
||||
*/
|
||||
function parseUserCollectionData(data: string | null | undefined) {
|
||||
const defaultDataProps = {
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
variables: [],
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return defaultDataProps
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedData = JSON.parse(data)
|
||||
return {
|
||||
auth: parsedData?.auth || defaultDataProps.auth,
|
||||
headers: parsedData?.headers || defaultDataProps.headers,
|
||||
variables: parsedData?.variables || defaultDataProps.variables,
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Failed to parse user collection data:", error)
|
||||
return defaultDataProps
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a UserCollection from backend format to HoppCollection format
|
||||
* Following the same pattern as teamCollectionJSONToHoppRESTColl in helpers.ts
|
||||
*/
|
||||
export function convertUserCollectionToHoppCollection(
|
||||
userCollection: UserCollection,
|
||||
reqType: ReqType
|
||||
): HoppCollection {
|
||||
const { auth, headers, variables } = parseUserCollectionData(
|
||||
userCollection.data
|
||||
)
|
||||
|
||||
// Get the appropriate children based on request type
|
||||
const children =
|
||||
reqType === ReqType.Rest
|
||||
? userCollection.childrenREST
|
||||
: userCollection.childrenGQL
|
||||
|
||||
// Convert requests - filter by type and convert
|
||||
const requests = userCollection.requests
|
||||
? userCollection.requests
|
||||
.filter((req) => req.type === reqType)
|
||||
.map(convertUserRequestToHoppRequest)
|
||||
: []
|
||||
|
||||
const collection = makeCollection({
|
||||
name: userCollection.title,
|
||||
folders: children
|
||||
? children.map((child) =>
|
||||
convertUserCollectionToHoppCollection(child, reqType)
|
||||
)
|
||||
: [],
|
||||
requests: requests,
|
||||
auth,
|
||||
headers,
|
||||
variables,
|
||||
})
|
||||
|
||||
// Add the backend ID to the collection
|
||||
collection.id = userCollection.id
|
||||
|
||||
return collection
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches user collections from backend and converts them to HoppCollection format
|
||||
*/
|
||||
export const fetchAndConvertUserCollections = async (reqType: ReqType) => {
|
||||
const fetchFunction =
|
||||
reqType === ReqType.Rest
|
||||
? getUserRootCollections
|
||||
: getGQLRootUserCollections
|
||||
|
||||
const result = await fetchFunction()
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
return E.left(result.left)
|
||||
}
|
||||
|
||||
if (reqType === ReqType.Rest) {
|
||||
const right = result.right as GetUserRootCollectionsQuery
|
||||
const collections = right.rootRESTUserCollections
|
||||
const convertedCollections = collections.map((collection) =>
|
||||
convertUserCollectionToHoppCollection(
|
||||
collection as unknown as UserCollection,
|
||||
reqType
|
||||
)
|
||||
)
|
||||
return E.right(convertedCollections)
|
||||
}
|
||||
const right = result.right as GetGqlRootUserCollectionsQuery
|
||||
const collections = right.rootGQLUserCollections
|
||||
const convertedCollections = collections.map((collection) =>
|
||||
convertUserCollectionToHoppCollection(
|
||||
collection as unknown as UserCollection,
|
||||
reqType
|
||||
)
|
||||
)
|
||||
return E.right(convertedCollections)
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
import { pluck } from "rxjs/operators"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { BehaviorSubject } from "rxjs"
|
||||
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||
import { pluck } from "rxjs/operators"
|
||||
import {
|
||||
getMyMockServers,
|
||||
getTeamMockServers,
|
||||
} from "~/helpers/backend/queries/MockServer"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { getService } from "~/modules/dioc"
|
||||
import { WorkspaceService } from "~/services/workspace.service"
|
||||
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||
|
||||
export type WorkspaceType = "USER" | "TEAM"
|
||||
|
||||
|
|
@ -166,6 +166,8 @@ export function loadMockServers(skip?: number, take?: number) {
|
|||
TE.match(
|
||||
(error) => {
|
||||
console.error("Failed to load mock servers:", error)
|
||||
// Clear mock servers on error to prevent stale data
|
||||
setMockServers([])
|
||||
},
|
||||
(mockServers) => {
|
||||
setMockServers(mockServers)
|
||||
|
|
@ -179,6 +181,8 @@ export function loadMockServers(skip?: number, take?: number) {
|
|||
TE.match(
|
||||
(error) => {
|
||||
console.error("Failed to load mock servers:", error)
|
||||
// Clear mock servers on error to prevent stale data
|
||||
setMockServers([])
|
||||
},
|
||||
(mockServers) => {
|
||||
setMockServers(mockServers)
|
||||
|
|
@ -199,6 +203,8 @@ export function loadTeamMockServers(
|
|||
TE.match(
|
||||
(error) => {
|
||||
console.error("Failed to load team mock servers:", error)
|
||||
// Clear mock servers on error to prevent stale data
|
||||
setMockServers([])
|
||||
},
|
||||
(mockServers) => {
|
||||
setMockServers(mockServers)
|
||||
|
|
@ -214,6 +220,9 @@ export function loadMockServersForWorkspace(
|
|||
skip?: number,
|
||||
take?: number
|
||||
) {
|
||||
// Clear existing mock servers first to prevent stale data
|
||||
setMockServers([])
|
||||
|
||||
if (workspaceType === "team" && teamID) {
|
||||
return loadTeamMockServers(teamID, skip, take)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ export type SettingsDef = {
|
|||
CUSTOM_NAMING_STYLE: string
|
||||
|
||||
EXPERIMENTAL_SCRIPTING_SANDBOX: boolean
|
||||
ENABLE_EXPERIMENTAL_MOCK_SERVERS: boolean
|
||||
}
|
||||
|
||||
let defaultProxyURL = DEFAULT_HOPP_PROXY_URL
|
||||
|
|
@ -146,6 +147,7 @@ export const getDefaultSettings = (): SettingsDef => {
|
|||
CUSTOM_NAMING_STYLE: "",
|
||||
|
||||
EXPERIMENTAL_SCRIPTING_SANDBOX: true,
|
||||
ENABLE_EXPERIMENTAL_MOCK_SERVERS: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center py-4">
|
||||
<HoppSmartToggle
|
||||
:on="EXPERIMENTAL_SCRIPTING_SANDBOX"
|
||||
@change="toggleSetting('EXPERIMENTAL_SCRIPTING_SANDBOX')"
|
||||
|
|
@ -164,6 +164,14 @@
|
|||
{{ t("settings.experimental_scripting_sandbox") }}
|
||||
</HoppSmartToggle>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<HoppSmartToggle
|
||||
:on="ENABLE_EXPERIMENTAL_MOCK_SERVERS"
|
||||
@change="toggleSetting('ENABLE_EXPERIMENTAL_MOCK_SERVERS')"
|
||||
>
|
||||
{{ t("settings.enable_experimental_mock_servers") }}
|
||||
</HoppSmartToggle>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -354,6 +362,9 @@ const CUSTOM_NAMING_STYLE = useSetting("CUSTOM_NAMING_STYLE")
|
|||
const EXPERIMENTAL_SCRIPTING_SANDBOX = useSetting(
|
||||
"EXPERIMENTAL_SCRIPTING_SANDBOX"
|
||||
)
|
||||
const ENABLE_EXPERIMENTAL_MOCK_SERVERS = useSetting(
|
||||
"ENABLE_EXPERIMENTAL_MOCK_SERVERS"
|
||||
)
|
||||
|
||||
const supportedNamingStyles = [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ const SettingsDefSchema = z.object({
|
|||
CUSTOM_NAMING_STYLE: z.string().optional().catch(""),
|
||||
|
||||
EXPERIMENTAL_SCRIPTING_SANDBOX: z.optional(z.boolean()),
|
||||
ENABLE_EXPERIMENTAL_MOCK_SERVERS: z.optional(z.boolean()),
|
||||
})
|
||||
|
||||
const HoppRESTRequestSchema = entityReference(HoppRESTRequest)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ import {
|
|||
updateRESTCollectionOrder,
|
||||
updateRESTRequestOrder,
|
||||
} from "@hoppscotch/common/newstore/collections"
|
||||
import { loadMockServers } from "@hoppscotch/common/newstore/mockServers"
|
||||
import {
|
||||
GQLHeader,
|
||||
HoppCollection,
|
||||
|
|
@ -84,7 +83,6 @@ function initCollectionsSync() {
|
|||
if (user) {
|
||||
loadUserCollections("REST")
|
||||
loadUserCollections("GQL")
|
||||
loadMockServers()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { CollectionsPlatformDef } from "@hoppscotch/common/platform/collections"
|
||||
import { authEvents$, def as platformAuth } from "@platform/auth/desktop"
|
||||
import { runDispatchWithOutSyncing } from "@lib/sync"
|
||||
import { authEvents$, def as platformAuth } from "@platform/auth/desktop"
|
||||
|
||||
import {
|
||||
exportUserCollectionsToJSON,
|
||||
|
|
@ -19,6 +19,11 @@ import {
|
|||
} from "./api"
|
||||
import { collectionsSyncer, getStoreByCollectionType } from "./sync"
|
||||
|
||||
import {
|
||||
ReqType,
|
||||
UserCollectionDuplicatedData,
|
||||
UserRequest,
|
||||
} from "@api/generated/graphql"
|
||||
import { runGQLSubscription } from "@hoppscotch/common/helpers/backend/GQLClient"
|
||||
import {
|
||||
addGraphqlCollection,
|
||||
|
|
@ -51,7 +56,6 @@ import {
|
|||
updateRESTCollectionOrder,
|
||||
updateRESTRequestOrder,
|
||||
} from "@hoppscotch/common/newstore/collections"
|
||||
import { loadMockServers } from "@hoppscotch/common/newstore/mockServers"
|
||||
import {
|
||||
generateUniqueRefId,
|
||||
GQLHeader,
|
||||
|
|
@ -62,11 +66,6 @@ import {
|
|||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import * as E from "fp-ts/Either"
|
||||
import {
|
||||
ReqType,
|
||||
UserCollectionDuplicatedData,
|
||||
UserRequest,
|
||||
} from "@api/generated/graphql"
|
||||
import { gqlCollectionsSyncer } from "./gqlCollections.sync"
|
||||
|
||||
function initCollectionsSync() {
|
||||
|
|
@ -85,7 +84,6 @@ function initCollectionsSync() {
|
|||
if (user) {
|
||||
loadUserCollections("REST")
|
||||
loadUserCollections("GQL")
|
||||
loadMockServers()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { CollectionsPlatformDef } from "@hoppscotch/common/platform/collections"
|
||||
import { authEvents$, def as platformAuth } from "@platform/auth/web"
|
||||
import { runDispatchWithOutSyncing } from "@lib/sync"
|
||||
import { authEvents$, def as platformAuth } from "@platform/auth/web"
|
||||
|
||||
import {
|
||||
exportUserCollectionsToJSON,
|
||||
|
|
@ -19,6 +19,11 @@ import {
|
|||
} from "./api"
|
||||
import { collectionsSyncer, getStoreByCollectionType } from "./sync"
|
||||
|
||||
import {
|
||||
ReqType,
|
||||
UserCollectionDuplicatedData,
|
||||
UserRequest,
|
||||
} from "@api/generated/graphql"
|
||||
import { runGQLSubscription } from "@hoppscotch/common/helpers/backend/GQLClient"
|
||||
import {
|
||||
addGraphqlCollection,
|
||||
|
|
@ -51,7 +56,6 @@ import {
|
|||
updateRESTCollectionOrder,
|
||||
updateRESTRequestOrder,
|
||||
} from "@hoppscotch/common/newstore/collections"
|
||||
import { loadMockServers } from "@hoppscotch/common/newstore/mockServers"
|
||||
import {
|
||||
generateUniqueRefId,
|
||||
GQLHeader,
|
||||
|
|
@ -62,11 +66,6 @@ import {
|
|||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import * as E from "fp-ts/Either"
|
||||
import {
|
||||
ReqType,
|
||||
UserCollectionDuplicatedData,
|
||||
UserRequest,
|
||||
} from "@api/generated/graphql"
|
||||
import { gqlCollectionsSyncer } from "./gqlCollections.sync"
|
||||
|
||||
function initCollectionsSync() {
|
||||
|
|
@ -85,7 +84,6 @@ function initCollectionsSync() {
|
|||
if (user) {
|
||||
loadUserCollections("REST")
|
||||
loadUserCollections("GQL")
|
||||
loadMockServers()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -136,9 +136,8 @@
|
|||
"title": "Mock Server",
|
||||
"description": "Configure mock server settings used to host example responses.",
|
||||
"wildcard_domain": "Wildcard Domain",
|
||||
"wildcard_domain_placeholder": "e.g. *.example.com",
|
||||
"secure_cookies": "Allow secure cookies",
|
||||
"secure_cookies_desc": "Use secure cookies for responses from the mock server"
|
||||
"wildcard_domain_description": "This field requires a full wildcard domain format. The input must start with an asterisk (*) followed by a dot (.) and then the domain name.",
|
||||
"wildcard_domain_example": "Example: *.mock.domain.com"
|
||||
},
|
||||
"update_failure": "Failed to update server configurations"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -17,29 +17,19 @@
|
|||
<label class="block text-sm font-medium text-secondaryDark mb-1">
|
||||
{{ t('configs.mock_server.wildcard_domain') }}
|
||||
</label>
|
||||
<input
|
||||
<HoppSmartInput
|
||||
v-model="mockFields.mock_server_wildcard_domain"
|
||||
type="text"
|
||||
class="w-full rounded border p-2"
|
||||
:placeholder="t('configs.mock_server.wildcard_domain_placeholder')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h5 class="font-medium">
|
||||
{{ t('configs.mock_server.secure_cookies') }}
|
||||
</h5>
|
||||
<p class="text-secondaryLight text-sm">
|
||||
{{ t('configs.mock_server.secure_cookies_desc') }}
|
||||
</p>
|
||||
</div>
|
||||
<HoppSmartToggle
|
||||
:on="mockFields.allow_secure_cookies"
|
||||
@change="
|
||||
mockFields.allow_secure_cookies = !mockFields.allow_secure_cookies
|
||||
"
|
||||
:placeholder="'e.g. *.mock.example.com'"
|
||||
class="!bg-primaryLight border border-divider rounded"
|
||||
input-styles="!border-0"
|
||||
/>
|
||||
<p class="text-secondaryLight text-sm mt-2">
|
||||
{{ t('configs.mock_server.wildcard_domain_description') }}
|
||||
</p>
|
||||
<p class="text-secondaryLight text-sm mt-1 font-mono">
|
||||
{{ t('configs.mock_server.wildcard_domain_example') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -69,7 +59,6 @@ const mockFields = computed({
|
|||
return (
|
||||
workingConfigs.value.mockServerConfigs?.fields ?? {
|
||||
mock_server_wildcard_domain: '',
|
||||
allow_secure_cookies: false,
|
||||
}
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -191,8 +191,6 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) {
|
|||
mock_server_wildcard_domain: getFieldValue(
|
||||
InfraConfigEnum.MockServerWildcardDomain
|
||||
),
|
||||
allow_secure_cookies:
|
||||
getFieldValue(InfraConfigEnum.AllowSecureCookies) === 'true',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@ export type ServerConfigs = {
|
|||
name: string;
|
||||
fields: {
|
||||
mock_server_wildcard_domain: string;
|
||||
allow_secure_cookies: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -289,10 +288,6 @@ export const MOCK_SERVER_CONFIGS: Config[] = [
|
|||
name: InfraConfigEnum.MockServerWildcardDomain,
|
||||
key: 'mock_server_wildcard_domain',
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.AllowSecureCookies,
|
||||
key: 'allow_secure_cookies',
|
||||
},
|
||||
];
|
||||
|
||||
export const ALL_CONFIGS = [
|
||||
|
|
|
|||
Loading…
Reference in a new issue