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:
2
client/.dockerignore
Normal file
2
client/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
/Dockerfile
|
||||
/.dockerignore
|
||||
@@ -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
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
2
client/middleware/guest.js
vendored
2
client/middleware/guest.js
vendored
@@ -1,8 +1,6 @@
|
||||
export default defineNuxtRouteMiddleware(() => {
|
||||
const authStore = useAuthStore()
|
||||
|
||||
if (authStore.check) {
|
||||
console.log("redirecting to home")
|
||||
return navigateTo({ name: "home" })
|
||||
}
|
||||
})
|
||||
|
||||
9
client/middleware/self-hosted-credentials.js
vendored
Normal file
9
client/middleware/self-hosted-credentials.js
vendored
Normal 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
14
client/middleware/self-hosted.js
vendored
Normal 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" })
|
||||
}
|
||||
})
|
||||
3
client/opnform.config.js
vendored
3
client/opnform.config.js
vendored
@@ -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
7185
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -58,7 +58,7 @@ useOpnSeoMeta({
|
||||
title: "Create a new Form for free",
|
||||
})
|
||||
definePageMeta({
|
||||
middleware: "guest",
|
||||
middleware: ["guest", "self-hosted"],
|
||||
})
|
||||
|
||||
// Data
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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"
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@ import { computed } from "vue"
|
||||
useOpnSeoMeta({
|
||||
title: "Privacy Policy",
|
||||
})
|
||||
|
||||
definePageMeta({
|
||||
middleware: ["self-hosted"]
|
||||
})
|
||||
|
||||
defineRouteRules({
|
||||
swr: 3600,
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
},
|
||||
|
||||
@@ -22,6 +22,11 @@ import { computed } from "vue"
|
||||
useOpnSeoMeta({
|
||||
title: "Terms & Conditions",
|
||||
})
|
||||
|
||||
definePageMeta({
|
||||
middleware: ["self-hosted"]
|
||||
})
|
||||
|
||||
defineRouteRules({
|
||||
swr: 3600,
|
||||
})
|
||||
|
||||
96
client/pages/update-credentials.vue
Normal file
96
client/pages/update-credentials.vue
Normal 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>
|
||||
7
client/stores/app.js
vendored
7
client/stores/app.js
vendored
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user