Files
cloudron-box/dashboard/src/components/app/Security.vue
T
2026-01-13 17:05:54 +01:00

87 lines
4.0 KiB
Vue

<script setup>
import { useI18n } from 'vue-i18n';
const i18n = useI18n();
const t = i18n.t;
import { ref, onMounted } from 'vue';
import { Button, FormGroup, Checkbox } from '@cloudron/pankow';
import AppsModel from '../../models/AppsModel.js';
const props = defineProps([ 'app' ]);
const appsModel = AppsModel.create();
function addRobotsTxtPreset(pattern) {
if (robotsTxt.value) robotsTxt.value += '\n';
robotsTxt.value += pattern;
}
const busy = ref(false);
const robotsTxt = ref('');
const csp = ref('');
const hstsPreload = ref(false);
const commonRobotsTxtMenu = [
{ label: t('app.security.robots.commonPattern.allowAll'), action: () => addRobotsTxtPreset('# Allow all\nUser-agent: *\nDisallow:') },
{ label: t('app.security.robots.commonPattern.disallowAll'), action: () => addRobotsTxtPreset('# Disable search engine indexing\n\nUser-agent: *\nDisallow: /') },
{ label: t('app.security.robots.commonPattern.disallowCommonBots'), action: () => addRobotsTxtPreset('# Disallow common bots\nUser-agent: Googlebot\nDisallow: /\n\nUser-agent: Bingbot\nDisallow: /\n\nUser-agent: Slurp\nDisallow: /\n\nUser-agent: DuckDuckBot\nDisallow: /\n\nUser-agent: Baiduspider\nDisallow: /\n\nUser-agent: YandexBot\nDisallow: /\n\nUser-agent: facebot\nDisallow: /\n\nUser-agent: ia_archiver\nDisallow: /') },
{ label: t('app.security.robots.commonPattern.disallowAdminPaths'), action: () => addRobotsTxtPreset('# Disallow admin paths\nUser-agent: *\nDisallow: /admin/\nDisallow: /internal/\nDisallow: /private/') },
{ label: t('app.security.robots.commonPattern.disallowApiPaths'), action: () => addRobotsTxtPreset('# Disallow API paths\nUser-agent: *\nDisallow: /api/\nDisallow: /v1/\nDisallow: /v2/') },
];
async function onSubmit() {
busy.value = true;
const data = {
robotsTxt: robotsTxt.value || null, // empty string resets
csp: csp.value || null, // empty string resets
hstsPreload: hstsPreload.value,
};
const [error] = await appsModel.configure(props.app.id, 'reverse_proxy', data);
if (error) return console.error(error);
busy.value = false;
}
onMounted(() => {
robotsTxt.value = props.app.reverseProxyConfig.robotsTxt || '';
csp.value = props.app.reverseProxyConfig.csp || '';
hstsPreload.value = props.app.reverseProxyConfig.hstsPreload || false;
});
</script>
<template>
<div>
<form @submit.prevent="onSubmit()" autocomplete="off">
<fieldset :disabled="busy || app.error">
<input style="display: none;" type="submit" />
<FormGroup>
<label for="robotsTxtInput" style="display: flex; justify-content: space-between;">
<span>{{ $t('app.security.robots.title') }} <sup><a href="https://docs.cloudron.io/apps/#robotstxt" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></span>
<Button small outline :menu="commonRobotsTxtMenu">{{ $t('app.security.robots.insertCommonRobotsTxt') }}</Button>
</label>
<div description>{{ $t('app.security.robots.description') }}</div>
<textarea id="robotsTxtInput" style="white-space: pre-wrap; font-family: monospace;" v-model="robotsTxt" rows="10"></textarea>
</FormGroup>
<FormGroup>
<label for="cspInput">{{ $t('app.security.csp.title') }} <sup><a href="https://docs.cloudron.io/apps/#custom-csp" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> </label>
<div description>{{ $t('app.security.csp.description') }}</div>
<textarea id="cspInput" style="white-space: pre-wrap; font-family: monospace;" v-model="csp" placeholder="default-src 'self'; frame-ancestors 'none';" rows="2"></textarea>
</FormGroup>
<FormGroup>
<Checkbox v-model="hstsPreload" style="display: inline-flex;" :label="$t('app.security.hstsPreload')" help-url="https://docs.cloudron.io/apps/#hsts-preload"/>
</FormGroup>
<br/>
<Button @click="onSubmit()" :loading="busy" :disabled="busy">{{ $t('app.security.csp.saveAction') }}</Button>
</fieldset>
</form>
</div>
</template>