Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,16 @@ LLM_MAX_TOKENS_CLARIFY=2000 # cap for follow-up Q&A
# DEEPSEEK_TIMEOUT_MS=600000 # optional: raise DeepSeek axios timeout (default: 10 min for reasoner, 4 min for chat)
# DEEPSEEK_BASE_URL="https://api.deepseek.com" # optional: override DeepSeek base URL (e.g. for a compatible proxy)

# ─────────────────────────────────────────
# DEMO MODE (public demo instances only)
# When DEMO_MODE=true, the app shows a "you're on the live demo" banner with
# the shared demo credentials and a "deploy your own" link. Leave false for
# real deployments. Only set DEMO_EMAIL/DEMO_PASSWORD on a throwaway demo DB.
# ─────────────────────────────────────────
DEMO_MODE="false"
DEMO_EMAIL=""
DEMO_PASSWORD=""

# ─────────────────────────────────────────
# MISC
# ─────────────────────────────────────────
Expand Down
14 changes: 12 additions & 2 deletions Modules/Admin/common/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,22 @@ exports.makeDefaultBrandSettings = () => {

exports.getBrandSettingsData = (req, res) => {
try {
// Public demo flag (+ shared demo creds) surfaced on this already-public,
// pre-auth endpoint so the demo banner / login can read it without a
// build-time var. Off unless DEMO_MODE=true in the server env.
const demo = process.env.DEMO_MODE === 'true';
const withDemo = (obj) => ({
...obj,
demoMode: demo,
...(demo ? { demoEmail: process.env.DEMO_EMAIL || '', demoPassword: process.env.DEMO_PASSWORD || '' } : {}),
});

const filePath = path.join(__dirname,'/../../../', 'brandSettings.json');

if (!fs.existsSync(filePath)) {
exports.makeDefaultBrandSettings()
.then((data) => {
res.status(200).json(JSON.parse(data));
res.status(200).json(withDemo(JSON.parse(data)));
})
.catch((error) => {
res.status(404).send(error);
Expand All @@ -107,7 +117,7 @@ exports.getBrandSettingsData = (req, res) => {
logger.error('Error writing file getBrandSettingsData:', err);
return res.status(500).send('Internal Server Error');
}
res.status(200).json(JSON.parse(data));
res.status(200).json(withDemo(JSON.parse(data)));
});
}
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<div v-if="!underMaintainance">
<DemoBanner/>
<template v-if="$route.meta.requiresAuth">
<template v-if="logged && (rules && Object.keys(rules).length && companyUserDetail && Object.keys(companyUserDetail).length) && socket">
<HeaderComponent v-if="!$route.meta.hideHeader" @change="changeCompany($event)" @filter="handleFilter"/>
Expand Down
64 changes: 64 additions & 0 deletions frontend/src/components/atom/DemoBanner/DemoBanner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<template>
<div v-if="demoMode" class="demo-banner">
<span class="demo-banner__msg">
🚀 {{ $t('Demo.banner') }}
<span v-if="demoEmail" class="demo-banner__creds">
{{ $t('Demo.login_with') }} <strong>{{ demoEmail }}</strong><template v-if="demoPassword"> / <strong>{{ demoPassword }}</strong></template>
</span>
</span>
<span class="demo-banner__links">
<a :href="githubUrl" target="_blank" rel="noopener noreferrer">⭐ {{ $t('Demo.star') }}</a>
<a :href="deployUrl" target="_blank" rel="noopener noreferrer">{{ $t('Demo.deploy') }}</a>
</span>
</div>
</template>

<script setup>
import { computed } from "vue";
import { useStore } from "vuex";

const store = useStore();

const GITHUB_URL = 'https://github.com/aliansoftwareteam/AlianHub-Project-Management-System';
const DEPLOY_URL = 'https://github.com/aliansoftwareteam/AlianHub-Project-Management-System#quick-start';

const brand = computed(() => store.getters['brandSettingTab/brandSettings'] || {});
const demoMode = computed(() => brand.value.demoMode === true);
const demoEmail = computed(() => brand.value.demoEmail || '');
const demoPassword = computed(() => brand.value.demoPassword || '');
const githubUrl = GITHUB_URL;
const deployUrl = DEPLOY_URL;
</script>

<style scoped>
.demo-banner {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 6px 16px;
background: #2f3990;
color: #fff;
font-size: 12.5px;
line-height: 1.3;
padding: 6px 14px;
text-align: center;
}
.demo-banner__creds {
opacity: 0.92;
margin-left: 6px;
}
.demo-banner__links {
display: inline-flex;
gap: 14px;
white-space: nowrap;
}
.demo-banner__links a {
color: #fff;
text-decoration: underline;
font-weight: 600;
}
.demo-banner__links a:hover {
opacity: 0.85;
}
</style>
6 changes: 6 additions & 0 deletions frontend/src/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -2655,4 +2655,10 @@ export default {
load_failed: "Couldn't load the changelog.",
retry: "Retry",
},
Demo: {
banner: "You're on the AlianHub live demo — data may reset periodically.",
login_with: "Log in with",
star: "Star on GitHub",
deploy: "Deploy your own",
},
};
3 changes: 3 additions & 0 deletions frontend/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { DatePicker } from 'v-calendar';
import 'v-calendar/style.css';
import {i18n } from '@/locales/main';
import VueApexCharts from "vue3-apexcharts";
import DemoBanner from "@/components/atom/DemoBanner/DemoBanner.vue";
// Plugins Path
import registerPlugin from './plugins/register/registerPlugin';
import createcompanyinsidePlugin from './plugins/createcompanyinside/createcompanyinsidePlugin';
Expand Down Expand Up @@ -61,6 +62,8 @@ for (let index = 0; index < pluginArray.length; index++) {
app.use(ToastPlugin,{position: 'top-right'});
app.use(plugin, defaultConfig({ plugins: [pro]}))
app.use(i18n);
// Registered before mount so the root App.vue template can resolve it.
app.component('DemoBanner', DemoBanner);
app.mount('#app');
// Use plugin defaults (optional)
app.component('ApexChart', VueApexCharts); // Register the apexchart component globally
Expand Down
Loading