Docker compose setup (#513)

* fix password reset bug

* self hosted mode middleware changes on  pages

* fix lint

* wip: self hosted changes

* wip: self hosted frontend changes

* wip self hosted mode changes

* typo correction

* remove commented logic

* fix env variable names

* fix lint issues

* fix minor updates

* #445 Switched from single monolithic docker image to a docker-compose
 orchestrated network of services

* Automatically configures shared secret

* Working through some issues

* Use local file storage

* Moved the dockerfiles

* Fixed some issues when building from clean

* Corrected workflow

* Hopefully schedules everything correctly now

* Prep storage for worker process as well

* .env files are required

* Pinned dependency versions

* Disable self hosted in the client as well

* Removed double defaulting logic

* Using regexs is more succinct

* Added FRONT_URL environment variable

* Merge 236e4-self-hosted-mode-changes

* Improve inital user setup

* Finalized the new docker-compose setup

* Fix back-end formatting issues

---------

Co-authored-by: Frank <csskfaves@gmail.com>
Co-authored-by: Don Benjamin <don@webhammer.co.uk>
This commit is contained in:
Julien Nahum
2024-08-05 12:06:20 +02:00
committed by GitHub
parent 6b13f95322
commit 3280e38ee1
49 changed files with 6152 additions and 3431 deletions

2
client/.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
/Dockerfile
/.dockerignore

View File

@@ -32,7 +32,7 @@
>
Templates
</NuxtLink>
<template v-if="featureBaseEnabled">
<template v-if="appStore.featureBaseEnabled">
<button
v-if="user"
:class="navLinkClasses"
@@ -54,7 +54,7 @@
</a>
</template>
<NuxtLink
v-if="$route.name !== 'ai-form-builder' && user === null"
v-if="($route.name !== 'ai-form-builder' && user === null) && (!appStore.selfHosted || appStore.aiFeaturesEnabled)"
:to="{ name: 'ai-form-builder' }"
:class="navLinkClasses"
class="hidden lg:inline"
@@ -63,9 +63,9 @@
</NuxtLink>
<NuxtLink
v-if="
paidPlansEnabled &&
(appStore.paidPlansEnabled &&
(user === null || (user && workspace && !workspace.is_pro)) &&
$route.name !== 'pricing'
$route.name !== 'pricing') && !appStore.selfHosted
"
:to="{ name: 'pricing' }"
:class="navLinkClasses"
@@ -248,6 +248,7 @@
</NuxtLink>
<v-button
v-if="!appStore.selfHosted"
v-track.nav_create_form_click
size="small"
class="shrink-0"
@@ -313,12 +314,6 @@ export default {
workspace() {
return this.workspacesStore.getCurrent
},
paidPlansEnabled() {
return this.config.public.paidPlansEnabled
},
featureBaseEnabled() {
return this.config.public.featureBaseOrganization !== null
},
showAuth() {
return this.$route.name && this.$route.name !== "forms-slug"
},
@@ -340,14 +335,8 @@ export default {
userOnboarded() {
return this.user && this.user.has_forms === true
},
hasCrisp() {
return (
this.config.public.crispWebsiteId &&
this.config.public.crispWebsiteId !== ""
)
},
hasNewChanges() {
if (import.meta.server || !window.Featurebase) return false
if (import.meta.server || !window.Featurebase || !this.appStore.featureBaseEnabled) return false
return window.Featurebase("unviewed_changelog_count") > 0
},
},

View File

@@ -22,6 +22,7 @@
<div class="flex justify-center mt-5 md:mt-0">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-x-4 gap-y-2">
<router-link
v-if="!appStore.selfHosted"
:to="{ name: 'privacy-policy' }"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
@@ -29,6 +30,7 @@
</router-link>
<router-link
v-if="!appStore.selfHosted"
:to="{ name: 'terms-conditions' }"
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
>
@@ -70,6 +72,7 @@ export default {
const authStore = useAuthStore()
return {
user: computed(() => authStore.user),
appStore: useAppStore(),
opnformConfig,
}
},

View File

@@ -58,7 +58,10 @@
Log in to continue
</v-button>
<p class="text-gray-500 mt-4">
<p
v-if="!appStore.selfHosted"
class="text-gray-500 mt-4"
>
Don't have an account?
<a
v-if="isQuick"
@@ -99,6 +102,7 @@ export default {
emits: ['afterQuickLogin', 'openRegister'],
setup() {
return {
appStore: useAppStore(),
authStore: useAuthStore(),
formsStore: useFormsStore(),
workspaceStore: useWorkspacesStore(),
@@ -139,7 +143,11 @@ export default {
this.redirect()
})
.catch((error) => {
console.error(error)
if (error.response?._data?.message == "You must change your credentials when in self host mode") {
// this.showForgotModal = true
this.redirect()
}
})
.finally(() => {
this.loading = false

View File

@@ -1,8 +1,6 @@
export default defineNuxtRouteMiddleware(() => {
const authStore = useAuthStore()
if (authStore.check) {
console.log("redirecting to home")
return navigateTo({ name: "home" })
}
})

View File

@@ -0,0 +1,9 @@
export default defineNuxtRouteMiddleware(() => {
const authStore = useAuthStore()
const runtimeConfig = useRuntimeConfig()
if (runtimeConfig.public?.selfHosted) {
if (authStore.check && authStore.user?.email === 'admin@opnform.com') {
return navigateTo({ name: "update-credentials" })
}
}
})

14
client/middleware/self-hosted.js vendored Normal file
View File

@@ -0,0 +1,14 @@
export default defineNuxtRouteMiddleware((from, to, next) => {
const runtimeConfig = useRuntimeConfig()
const route = useRoute()
if (runtimeConfig.public?.selfHosted) {
console.log('in')
if (from.name === 'register' && route.query?.email && route.query?.invite_token) {
return
}
if (from.name === 'ai-form-builder' && runtimeConfig.public?.aiFeaturesEnabled) {
return
}
return navigateTo({ name: "index" })
}
})

View File

@@ -5,8 +5,7 @@ export default {
githubAuth: null,
notion: { worker: "https://notion-forms-worker.notionforms.workers.dev/v1" },
links: {
help_url: "https://github.com/JhumanJ/OpnForm/discussions",
helpdesk_sitemap_url: "https://notionforms.crisp.help/sitemap.xml",
help_url: "https://help.opnform.com",
github_url: "https://github.com/JhumanJ/OpnForm",
github_forum_url: "https://github.com/JhumanJ/OpnForm/discussions",
discord: "https://discord.gg/YTSjU2a9TS",

7185
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@
"scripts": {
"build": "nuxt build",
"dev": "export NODE_TLS_REJECT_UNAUTHORIZED=0; nuxt dev",
"docker-dev": "export NODE_TLS_REJECT_UNAUTHORIZED=0; nuxt dev --host 0.0.0.0",
"generate": "export NODE_TLS_REJECT_UNAUTHORIZED=0; nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
@@ -48,6 +49,7 @@
"crisp-sdk-web": "^1.0.21",
"date-fns": "^2.30.0",
"debounce": "^1.2.1",
"esbuild": "^0.23.0",
"fuse.js": "^6.4.6",
"js-sha256": "^0.10.0",
"libphonenumber-js": "^1.10.44",

View File

@@ -627,11 +627,10 @@
</template>
<script setup>
import { computed } from "vue"
import { useAuthStore } from "../stores/auth"
const authStore = useAuthStore()
definePageMeta({
middleware: ["self-hosted",]
})
useOpnSeoMeta({
title: "Free AI form builder",
description:

View File

@@ -72,7 +72,6 @@
</template>
<script setup>
import {computed} from 'vue'
import OpenCompleteForm from "~/components/open/forms/OpenCompleteForm.vue"
import sha256 from 'js-sha256'
import { onBeforeRouteLeave } from 'vue-router'

View File

@@ -58,7 +58,7 @@ useOpnSeoMeta({
title: "Create a new Form for free",
})
definePageMeta({
middleware: "guest",
middleware: ["guest", "self-hosted"],
})
// Data

View File

@@ -237,7 +237,7 @@ import ExtraMenu from "../components/pages/forms/show/ExtraMenu.vue"
import {refDebounced} from "@vueuse/core"
definePageMeta({
middleware: "auth",
middleware: ["auth", "self-hosted-credentials"],
})
useOpnSeoMeta({

View File

@@ -416,13 +416,7 @@ export default {
definePageMeta({
middleware: [
function () {
// Custom inline middleware
if (!useRuntimeConfig().public.paidPlansEnabled) {
// If no paid plan so no need this page
return navigateTo("/", {redirectCode: 301})
}
},
"self-hosted"
],
})

View File

@@ -22,6 +22,11 @@ import { computed } from "vue"
useOpnSeoMeta({
title: "Privacy Policy",
})
definePageMeta({
middleware: ["self-hosted"]
})
defineRouteRules({
swr: 3600,
})

View File

@@ -11,7 +11,7 @@
Create an account
</h2>
<small>Sign up in less than 2 minutes.</small>
<template v-if="!isSelfHosted || isInvited">
<template v-if="!appStore.selfHosted || isInvited">
<register-form />
</template>
<div
@@ -106,22 +106,21 @@ export default {
})
definePageMeta({
middleware: "guest",
middleware: ["self-hosted", "guest"]
})
defineRouteRules({
swr: 3600,
})
return {
appStore: useAppStore(),
}
},
data: () => ({}),
computed: {
isSelfHosted(){
return useRuntimeConfig().public.selfHosted
},
isInvited(){
isInvited() {
return this.$route.query?.email && this.$route.query?.invite_token
}
},

View File

@@ -22,6 +22,11 @@ import { computed } from "vue"
useOpnSeoMeta({
title: "Terms & Conditions",
})
definePageMeta({
middleware: ["self-hosted"]
})
defineRouteRules({
swr: 3600,
})

View File

@@ -0,0 +1,96 @@
<template>
<modal :show="showModal" @close="logout" max-width="lg">
<div class="">
<h2 class="font-medium text-3xl mb-3">Welcome to OpnForm!</h2>
<p class="text-sm text-gray-600">
You're using the self-hosted version of OpnForm and need to set up your account.
Please enter your email and create a password to continue.
</p>
</div>
<form
class="mt-4"
@submit.prevent="updateCredentials"
@keydown="form.onKeydown($event)"
>
<!-- Email -->
<text-input
name="email"
:form="form"
label="Email"
:required="true"
placeholder="Your email address"
/>
<!-- Password -->
<text-input
native-type="password"
placeholder="Your password"
name="password"
:form="form"
label="Password"
:required="true"
/>
<!-- Password Confirmation-->
<text-input
native-type="password"
:form="form"
:required="true"
placeholder="Enter confirm password"
name="password_confirmation"
label="Confirm Password"
/>
<!-- Submit Button -->
<v-button class="mx-auto" :loading="form.busy || loading">
Update Credentials
</v-button>
</form>
</modal>
</template>
<script setup>
import { onMounted } from "vue";
const authStore = useAuthStore();
const workspacesStore = useWorkspacesStore();
const formsStore = useFormsStore();
const user = computed(() => authStore.user);
const router = useRouter();
const showModal = ref(true);
const form = useForm({
name: "",
email: "",
password: "",
password_confirmation: "",
agree_terms: false,
appsumo_license: null,
});
onMounted(() => {
form.email = user?.value?.email;
});
const updateCredentials = () => {
form
.post("update-credentials")
.then(async (data) => {
authStore.setUser(data.user);
const workspaces = await fetchAllWorkspaces();
workspacesStore.set(workspaces.data.value);
formsStore.loadAll(workspacesStore.currentId);
router.push({ name: "home" });
})
.catch((error) => {
console.error(error);
useAlert().error(error.response._data.message);
});
};
const logout = () => {
authStore.logout();
showModal.value = false;
router.push({ name: "login" });
};
</script>

View File

@@ -20,6 +20,13 @@ export const useAppStore = defineStore("app", {
_cut: null,
},
}),
getters: {
paidPlansEnabled: () => useRuntimeConfig().public.paidPlansEnabled,
featureBaseEnabled: () => useRuntimeConfig().public.featureBaseOrganization !== null,
selfHosted: () => useRuntimeConfig().public.selfHosted,
aiFeaturesEnabled: () => useRuntimeConfig().public.aiFeaturesEnabled,
crispEnabled: () => useRuntimeConfig().public.crispWebsiteId !== null && useRuntimeConfig().public.crispWebsiteId !== '',
},
actions: {
hideNavbar() {
this.navbarHidden = true