@geode/opengeodeweb-front 10.38.0-rc.3 → 10.38.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/components/Launcher.vue +10 -5
- package/app/stores/api.ts +12 -11
- package/app/stores/cloud.ts +14 -18
- package/app/stores/infra.ts +2 -2
- package/package.json +1 -1
- package/app/components/Recaptcha.vue +0 -89
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import Loading from "@ogw_front/components/Loading.vue";
|
|
3
|
-
import Recaptcha from "@ogw_front/components/Recaptcha.vue";
|
|
4
3
|
import { Status } from "@ogw_front/utils/status";
|
|
5
4
|
import { appMode } from "@ogw_shared/app_mode";
|
|
6
5
|
import { getCloudUrlParam } from "@ogw_front/utils/cloud";
|
|
@@ -8,22 +7,28 @@ import { useInfraStore } from "@ogw_front/stores/infra";
|
|
|
8
7
|
|
|
9
8
|
interface Props {
|
|
10
9
|
appName: string;
|
|
11
|
-
|
|
10
|
+
// Firebase ID token for the cloud launch; required in cloud mode.
|
|
11
|
+
authToken?: string;
|
|
12
12
|
isUserAuthenticated?: boolean;
|
|
13
13
|
logo?: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
appName,
|
|
18
|
+
authToken = undefined,
|
|
19
|
+
isUserAuthenticated = false,
|
|
20
|
+
logo = "",
|
|
21
|
+
} = defineProps<Props>();
|
|
17
22
|
|
|
18
23
|
const infraStore = useInfraStore();
|
|
19
24
|
// In cloud mode, wait for the user to authenticate and load the app, unless a `?cloud_url=` link
|
|
20
25
|
// Points at an already running service: that one is connected straight away, without authentication.
|
|
21
26
|
if (infraStore.app_mode !== appMode.CLOUD || getCloudUrlParam() !== undefined) {
|
|
22
|
-
infraStore.create_backend(
|
|
27
|
+
infraStore.create_backend(authToken);
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
function cloudCreateBackend(): Promise<void> {
|
|
26
|
-
return infraStore.create_backend(
|
|
31
|
+
return infraStore.create_backend(authToken);
|
|
27
32
|
}
|
|
28
33
|
</script>
|
|
29
34
|
|
package/app/stores/api.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RequestHandlers } from "@ogw_shared/utils/types";
|
|
1
|
+
import type { ParamsOf, RequestHandlers, ResponseOf } from "@ogw_shared/utils/types";
|
|
2
2
|
import { api_fetch } from "@ogw_internal/utils/api_fetch";
|
|
3
3
|
import { consola } from "consola";
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ interface ApiSchema {
|
|
|
8
8
|
[key: string]: unknown;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
type ApiCallbacks = RequestHandlers & {
|
|
11
|
+
type ApiCallbacks<Response = unknown> = RequestHandlers<Response> & {
|
|
12
12
|
skip_feedback_error?: boolean;
|
|
13
13
|
};
|
|
14
14
|
|
|
@@ -26,21 +26,21 @@ export const useAPIStore = defineStore("api", () => {
|
|
|
26
26
|
request_counter.value -= 1;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
//
|
|
30
|
-
async function request<
|
|
29
|
+
// The response type comes from the schema's generated `response` type (see the Cloud API typed schemas), asserted at the API boundary below, not verified
|
|
30
|
+
async function request<Schema extends ApiSchema>(
|
|
31
31
|
{
|
|
32
32
|
schema,
|
|
33
|
-
params
|
|
33
|
+
params,
|
|
34
34
|
headers = {},
|
|
35
|
-
}: { schema:
|
|
36
|
-
callbacks: ApiCallbacks = {},
|
|
37
|
-
): Promise<
|
|
35
|
+
}: { schema: Schema; params?: ParamsOf<Schema>; headers?: Record<string, string> },
|
|
36
|
+
callbacks: ApiCallbacks<ResponseOf<Schema>> = {},
|
|
37
|
+
): Promise<ResponseOf<Schema>> {
|
|
38
38
|
consola.info("[API] Request:", schema.$id);
|
|
39
39
|
const start = Date.now();
|
|
40
40
|
|
|
41
41
|
const result = await api_fetch(
|
|
42
42
|
{ $id: schema.$id, base_url: base_url.value, start_request, stop_request },
|
|
43
|
-
{ schema, params, headers },
|
|
43
|
+
{ schema, params: params ?? {}, headers },
|
|
44
44
|
{
|
|
45
45
|
...callbacks,
|
|
46
46
|
response_function: async (response: unknown) => {
|
|
@@ -52,13 +52,14 @@ export const useAPIStore = defineStore("api", () => {
|
|
|
52
52
|
"s",
|
|
53
53
|
);
|
|
54
54
|
if (callbacks.response_function) {
|
|
55
|
-
|
|
55
|
+
// oxlint-disable-next-line no-unsafe-type-assertion -- trusted API boundary; see comment above.
|
|
56
|
+
await callbacks.response_function(response as ResponseOf<Schema>);
|
|
56
57
|
}
|
|
57
58
|
},
|
|
58
59
|
},
|
|
59
60
|
);
|
|
60
61
|
// oxlint-disable-next-line no-unsafe-type-assertion -- trusted API boundary; see comment above.
|
|
61
|
-
return result as
|
|
62
|
+
return result as ResponseOf<Schema>;
|
|
62
63
|
}
|
|
63
64
|
return {
|
|
64
65
|
base_url,
|
package/app/stores/cloud.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { clearCloudUrlParam, getCloudUrlParam } from "@ogw_front/utils/cloud";
|
|
2
|
-
import type { RunCloudResponse } from "@geode/cloud-api/types";
|
|
3
2
|
import { Status } from "@ogw_front/utils/status";
|
|
4
3
|
import { api_fetch } from "@ogw_internal/utils/api_fetch";
|
|
5
4
|
import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_typed_schemas.js";
|
|
6
|
-
import cloud_api_schemas from "@geode/cloud-api/
|
|
5
|
+
import cloud_api_schemas from "@geode/cloud-api/cloud_api_typed_schemas.js";
|
|
7
6
|
import { setAppBaseUrl } from "@ogw_shared/scripts";
|
|
8
7
|
import { useAPIStore } from "@ogw_front/stores/api";
|
|
9
8
|
import { useFeedbackStore } from "@ogw_front/stores/feedback";
|
|
@@ -17,10 +16,13 @@ export const useCloudStore = defineStore("cloud", {
|
|
|
17
16
|
actions: {
|
|
18
17
|
// Reuses the running service given by `?cloud_url=` when present, otherwise launches a new one.
|
|
19
18
|
// The param is single use: it is cleared whatever the outcome, so a retry launches a new service.
|
|
20
|
-
async start(
|
|
19
|
+
async start(authToken?: string) {
|
|
21
20
|
const existing_host = getCloudUrlParam();
|
|
22
21
|
if (existing_host === undefined) {
|
|
23
|
-
|
|
22
|
+
if (authToken === undefined) {
|
|
23
|
+
throw new Error("Launching a cloud service requires an authenticated user");
|
|
24
|
+
}
|
|
25
|
+
await this.launch(authToken);
|
|
24
26
|
return;
|
|
25
27
|
}
|
|
26
28
|
try {
|
|
@@ -29,29 +31,23 @@ export const useCloudStore = defineStore("cloud", {
|
|
|
29
31
|
clearCloudUrlParam();
|
|
30
32
|
}
|
|
31
33
|
},
|
|
32
|
-
|
|
34
|
+
// `authToken` is the user's Firebase ID token, checked by the Cloud API
|
|
35
|
+
async launch(authToken: string) {
|
|
33
36
|
this.status = Status.CONNECTING;
|
|
34
37
|
const { PROJECT, BRANCH } = useRuntimeConfig().public;
|
|
35
|
-
const params = {
|
|
38
|
+
const params = { project: PROJECT, branch: BRANCH };
|
|
39
|
+
const headers = { Authorization: `Bearer ${authToken}` };
|
|
36
40
|
const feedbackStore = useFeedbackStore();
|
|
37
41
|
const APIStore = useAPIStore();
|
|
38
|
-
const result = await APIStore.request
|
|
39
|
-
{ schema: run_cloud_schema, params },
|
|
42
|
+
const result = await APIStore.request(
|
|
43
|
+
{ schema: run_cloud_schema, params, headers },
|
|
40
44
|
{
|
|
41
45
|
request_error_function: () => {
|
|
42
46
|
feedbackStore.$patch({ server_error: true });
|
|
43
47
|
this.status = Status.NOT_CONNECTED;
|
|
44
48
|
},
|
|
45
|
-
response_function: async (
|
|
46
|
-
|
|
47
|
-
typeof response !== "object" ||
|
|
48
|
-
response === null ||
|
|
49
|
-
!("url" in response) ||
|
|
50
|
-
typeof response.url !== "string"
|
|
51
|
-
) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
await this.on_connected(response.url);
|
|
49
|
+
response_function: async ({ url }) => {
|
|
50
|
+
await this.on_connected(url);
|
|
55
51
|
},
|
|
56
52
|
response_error_function: () => {
|
|
57
53
|
feedbackStore.$patch({ server_error: true });
|
package/app/stores/infra.ts
CHANGED
|
@@ -44,7 +44,7 @@ export const useInfraStore = defineStore("infra", {
|
|
|
44
44
|
(microservice: Microservice) => microservice.$id !== microserviceId,
|
|
45
45
|
);
|
|
46
46
|
},
|
|
47
|
-
async create_backend(
|
|
47
|
+
async create_backend(authToken?: string) {
|
|
48
48
|
if (this.status === Status.CREATED) {
|
|
49
49
|
return undefined;
|
|
50
50
|
}
|
|
@@ -56,7 +56,7 @@ export const useInfraStore = defineStore("infra", {
|
|
|
56
56
|
if (this.app_mode === appMode.CLOUD) {
|
|
57
57
|
const cloudStore = useCloudStore();
|
|
58
58
|
try {
|
|
59
|
-
await cloudStore.start(
|
|
59
|
+
await cloudStore.start(authToken);
|
|
60
60
|
} catch (error) {
|
|
61
61
|
// Back to the "Load the app" button so the user is not stuck on the loading screen.
|
|
62
62
|
this.status = Status.NOT_CREATED;
|
package/package.json
CHANGED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { useInfraStore } from "@ogw_front/stores/infra";
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
buttonLabel?: string;
|
|
6
|
-
buttonColor?: string;
|
|
7
|
-
color?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const {
|
|
11
|
-
buttonLabel = "Load the app",
|
|
12
|
-
buttonColor = "white",
|
|
13
|
-
color = undefined,
|
|
14
|
-
} = defineProps<Props>();
|
|
15
|
-
|
|
16
|
-
const name = ref("");
|
|
17
|
-
const email = ref("");
|
|
18
|
-
const launch = ref(false);
|
|
19
|
-
const valid = ref(false);
|
|
20
|
-
const emailRules = [
|
|
21
|
-
(value: string): boolean | string => {
|
|
22
|
-
if (value) {
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
return "E-mail is required.";
|
|
26
|
-
},
|
|
27
|
-
(value: string): boolean | string => {
|
|
28
|
-
if (/.+@.+\..+/u.test(value)) {
|
|
29
|
-
return true;
|
|
30
|
-
}
|
|
31
|
-
return "E-mail must be valid.";
|
|
32
|
-
},
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
function submit(): Promise<void> {
|
|
36
|
-
const infraStore = useInfraStore();
|
|
37
|
-
return infraStore.create_backend(email.value);
|
|
38
|
-
}
|
|
39
|
-
</script>
|
|
40
|
-
|
|
41
|
-
<template>
|
|
42
|
-
<VRow align="center" justify="center" style="display: none">
|
|
43
|
-
<VCol cols="4">
|
|
44
|
-
<VForm v-model="valid">
|
|
45
|
-
<VContainer>
|
|
46
|
-
<VRow>
|
|
47
|
-
<VCol>
|
|
48
|
-
<VTextField v-model="name" label="Name" required />
|
|
49
|
-
</VCol>
|
|
50
|
-
</VRow>
|
|
51
|
-
<VRow>
|
|
52
|
-
<VCol>
|
|
53
|
-
<VTextField v-model="email" :rules="emailRules" label="E-mail" required />
|
|
54
|
-
</VCol>
|
|
55
|
-
</VRow>
|
|
56
|
-
<VRow>
|
|
57
|
-
<VCol>
|
|
58
|
-
<VCheckbox label="Load the app" v-model="launch" />
|
|
59
|
-
</VCol>
|
|
60
|
-
</VRow>
|
|
61
|
-
</VContainer>
|
|
62
|
-
</VForm>
|
|
63
|
-
</VCol>
|
|
64
|
-
</VRow>
|
|
65
|
-
<VRow align="center" justify="center">
|
|
66
|
-
<VCol cols="auto" class="d-flex justify-center align-center">
|
|
67
|
-
<VBtn class="load-btn" :text="buttonLabel" :color="color || buttonColor" @click="submit" />
|
|
68
|
-
</VCol>
|
|
69
|
-
</VRow>
|
|
70
|
-
</template>
|
|
71
|
-
|
|
72
|
-
<style scoped>
|
|
73
|
-
.load-btn {
|
|
74
|
-
padding: 0 40px !important;
|
|
75
|
-
height: 50px !important;
|
|
76
|
-
border-radius: 8px;
|
|
77
|
-
text-transform: none !important;
|
|
78
|
-
font-weight: 600;
|
|
79
|
-
letter-spacing: 0.5px;
|
|
80
|
-
transition:
|
|
81
|
-
transform 0.2s ease,
|
|
82
|
-
box-shadow 0.2s ease !important;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.load-btn:hover {
|
|
86
|
-
transform: translateY(-2px);
|
|
87
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
|
|
88
|
-
}
|
|
89
|
-
</style>
|