<script setup>

import "@/lib/array.js"
import {inject, onMounted, ref, watch} from "vue"
import TOption from "@/components/ui/TOption.vue";
import TButton from "@/components/ui/TButton.vue";
import TDivider from "@/components/ui/TDivider.vue";
import TSelect from "@/components/ui/TSelect.vue";
import TCheckboxBinary from "@/components/ui/TCheckboxBinary.vue";
import TInputText from "@/components/ui/TInputText.vue";
import MoreOptionsTbody from "@/components/common/MoreOptionsTbody.vue";
import "@/configs/definitions.js"
import "@/lib/date.js"
import TLabel from "@/components/ui/TLabel.vue";
import CodeLabel from "@/components/common/CodeLabel.vue";
import Sortable from "sortablejs"
import PlusButton from "@/components/common/PlusButton.vue";
import TInputGroup from "@/components/ui/TInputGroup.vue";
import TInputGroupAddon from "@/components/ui/TInputGroupAddon.vue";
import DefinitionTable from "@/components/common/DefinitionTable.vue";

const utils = inject("$utils")

const props = defineProps([
	"v-ssl-policy",
	"v-protocol",
	"v-server-id",
	"v-support-http3"
])

const addingHstsDomainRef = ref()
const cipherSuitesBoxRef = ref()
const {
	policy,
	hsts,
	hstsOptionsVisible,
	hstsDomainAdding,
	hstsMaxAgeString,
	addingHstsDomain,
	hstsDomainEditingIndex,
	allVersions,
	allCipherSuites,
	modernCipherSuites,
	intermediateCipherSuites,
	allClientAuthTypes,
	cipherSuitesVisible,
	moreOptionsVisible
} = (function () {
	let policy = props.vSslPolicy
	if (policy == null) {
		policy = {
			id: 0,
			isOn: true,
			certRefs: [],
			certs: [],
			clientCARefs: [],
			clientCACerts: [],
			clientAuthType: 0,
			minVersion: "TLS 1.1",
			hsts: null,
			cipherSuitesIsOn: false,
			cipherSuites: [],
			http2Enabled: true,
			http3Enabled: false,
			ocspIsOn: false
		}
	} else {
		if (policy.certRefs == null) {
			policy.certRefs = []
		}
		if (policy.certs == null) {
			policy.certs = []
		}
		if (policy.clientCARefs == null) {
			policy.clientCARefs = []
		}
		if (policy.clientCACerts == null) {
			policy.clientCACerts = []
		}
		if (policy.cipherSuites == null) {
			policy.cipherSuites = []
		}
	}

	let hsts = policy.hsts
	let hstsMaxAgeString = "31536000"
	if (hsts == null) {
		hsts = {
			isOn: false,
			maxAge: 31536000,
			includeSubDomains: false,
			preload: false,
			domains: []
		}
	}
	if (hsts.maxAge != null) {
		hstsMaxAgeString = hsts.maxAge.toString()
	}

	return {
		policy: ref(policy),

		// hsts
		hsts: ref(hsts),
		hstsOptionsVisible: ref(false),
		hstsDomainAdding: ref(false),
		hstsMaxAgeString: ref(hstsMaxAgeString),
		addingHstsDomain: ref(""),
		hstsDomainEditingIndex: ref(-1),

		// 相关数据
		allVersions: ref(window.SSL_ALL_VERSIONS),
		allCipherSuites: ref(window.SSL_ALL_CIPHER_SUITES.$copy()),
		modernCipherSuites: ref(window.SSL_MODERN_CIPHER_SUITES),
		intermediateCipherSuites: ref(window.SSL_INTERMEDIATE_CIPHER_SUITES),
		allClientAuthTypes: ref(window.SSL_ALL_CLIENT_AUTH_TYPES),
		cipherSuitesVisible: ref(false),

		// 高级选项
		moreOptionsVisible: ref(false)
	}
})()

// 删除证书
const removeCert = function (index) {
	utils.confirm("确定删除此证书吗？证书数据仍然保留，只是当前网站不再使用此证书。", function () {
		policy.value.certRefs.$remove(index)
		policy.value.certs.$remove(index)
	})
}

// 选择证书
const selectCert = function () {
	let selectedCertIds = []
	if (policy.value != null && policy.value.certs.length > 0) {
		policy.value.certs.forEach(function (cert) {
			selectedCertIds.push(cert.id.toString())
		})
	}
	let serverId = props.vServerId
	if (serverId == null) {
		serverId = 0
	}
	utils.popup("/servers/certs/selectPopup?selectedCertIds=" + selectedCertIds + "&serverId=" + serverId, {
		width: "50em",
		height: "40em",
		callback: function (resp) {
			if (resp.data.cert != null && resp.data.certRef != null) {
				policy.value.certRefs.push(resp.data.certRef)
				policy.value.certs.push(resp.data.cert)
			}
			if (resp.data.certs != null && resp.data.certRefs != null) {
				policy.value.certRefs.$pushAll(resp.data.certRefs)
				policy.value.certs.$pushAll(resp.data.certs)
			}
		}
	})
}
// 上传证书
const uploadCert = function () {
	let serverId = props.vServerId
	if (typeof serverId != "number" && typeof serverId != "string") {
		serverId = 0
	}
	utils.popup("/servers/certs/uploadPopup?serverId=" + serverId, {
		callback: function (resp) {
			utils.success("上传成功", function () {
				policy.value.certRefs.push(resp.data.certRef)
				policy.value.certs.push(resp.data.cert)
			})
		}
	})
}

// 批量上传
const uploadBatch = function () {
	let serverId = props.vServerId
	if (typeof serverId != "number" && typeof serverId != "string") {
		serverId = 0
	}
	utils.popup("/servers/certs/uploadBatchPopup?serverId=" + serverId, {
		callback: function (resp) {
			if (resp.data.cert != null) {
				policy.value.certRefs.push(resp.data.certRef)
				policy.value.certs.push(resp.data.cert)
			}
			if (resp.data.certs != null) {
				policy.value.certRefs.$pushAll(resp.data.certRefs)
				policy.value.certs.$pushAll(resp.data.certs)
			}
		}
	})
}

// 申请证书
const requestCert = function () {
	// 已经在证书中的域名
	let excludeServerNames = []
	if (policy.value != null && policy.value.certs.length > 0) {
		policy.value.certs.forEach(function (cert) {
			excludeServerNames.$pushAll(cert.dnsNames)
		})
	}

	utils.popup("/servers/server/settings/https/requestCertPopup?serverId=" + props.vServerId + "&excludeServerNames=" + excludeServerNames.join(","), {
		callback: function () {
			policy.value.certRefs.push(resp.data.certRef)
			policy.value.certs.push(resp.data.cert)
		}
	})
}

// 更多选项
const changeOptionsVisible = function () {
	moreOptionsVisible.value = !moreOptionsVisible.value
}

// 格式化时间
const formatTime = function (timestamp) {
	return new Date(timestamp * 1000).format("Y-m-d")
}

// 格式化加密套件
const formatCipherSuite = function (cipherSuite) {
	return cipherSuite.replace(/(AES|3DES)/, "<var style=\"font-weight: bold\">$1</var>")
}

// 添加单个套件
const addCipherSuite = function (cipherSuite) {
	if (!policy.value.cipherSuites.$contains(cipherSuite)) {
		policy.value.cipherSuites.push(cipherSuite)
	}
	allCipherSuites.value.$removeValue(cipherSuite)
}

// 删除单个套件
const removeCipherSuite = function (cipherSuite) {
	utils.confirm("确定要删除此套件吗？", function () {
		policy.value.cipherSuites.$removeValue(cipherSuite)
		allCipherSuites.value = window.SSL_ALL_CIPHER_SUITES.$findAll(function (k, v) {
			return !policy.value.cipherSuites.$contains(v)
		})
	})
}

// 清除所选套件
const clearCipherSuites = function () {
	utils.confirm("确定要清除所有已选套件吗？", function () {
		policy.value.cipherSuites = []
		allCipherSuites.value = window.SSL_ALL_CIPHER_SUITES.$copy()
	})
}

// 批量添加套件
const addBatchCipherSuites = function (suites) {
	utils.confirm("确定要批量添加套件？", function () {
		suites.$each(function (k, v) {
			if (policy.value.cipherSuites.$contains(v)) {
				return
			}
			policy.value.cipherSuites.push(v)
		})
	})
}

/**
 * 套件拖动排序
 */
const sortableCipherSuites = function () {
	Sortable.create(cipherSuitesBoxRef.value, {
		draggable: ".sort-label",
		handle: ".sort-handle",
		onUpdate: function (event) {
			utils.moveElement(policy.value.cipherSuites, event.oldDraggableIndex, event.newDraggableIndex)
		}
	})
}

// 显示所有套件
const showAllCipherSuites = function () {
	cipherSuitesVisible.value = !cipherSuitesVisible.value
}

// 显示HSTS更多选项
const showMoreHSTS = function () {
	hstsOptionsVisible.value = !hstsOptionsVisible.value;
	if (hstsOptionsVisible.value) {
		changeHSTSMaxAge()
	}
}

// 监控HSTS有效期修改
const changeHSTSMaxAge = function () {
	var v = parseInt(hstsMaxAgeString.value)
	if (isNaN(v) || v < 0) {
		hsts.value.maxAge = 0
		hsts.value.days = "-"
		return
	}
	hsts.value.maxAge = v
	hsts.value.days = v / 86400
	if (hsts.value.days == 0) {
		hsts.value.days = "-"
	}
}

// 设置HSTS有效期
const setHSTSMaxAge = function (maxAge) {
	hstsMaxAgeString.value = maxAge.toString()
	changeHSTSMaxAge()
}

// 添加HSTS域名
const addHstsDomain = function () {
	hstsDomainAdding.value = true
	hstsDomainEditingIndex.value = -1
	setTimeout(function () {
		addingHstsDomainRef.value.focus()
	}, 100)
}

// 修改HSTS域名
const editHstsDomain = function (index) {
	hstsDomainEditingIndex.value = index
	addingHstsDomain.value = hsts.value.domains[index]
	hstsDomainAdding.value = true
	setTimeout(function () {
		addingHstsDomainRef.value.focus()
	}, 100)
}

// 确认HSTS域名添加
const confirmAddHstsDomain = function () {
	addingHstsDomain.value = addingHstsDomain.value.trim()
	if (addingHstsDomain.value.length == 0) {
		return;
	}
	if (hstsDomainEditingIndex.value > -1) {
		hsts.value.domains[hstsDomainEditingIndex.value] = addingHstsDomain.value
	} else {
		hsts.value.domains.push(addingHstsDomain.value)
	}
	cancelHstsDomainAdding()
}
// 取消HSTS域名添加
const cancelHstsDomainAdding = function () {
	hstsDomainAdding.value = false
	addingHstsDomain.value = ""
	hstsDomainEditingIndex.value = -1
}
// 删除HSTS域名
const removeHstsDomain = function (index) {
	cancelHstsDomainAdding()
	hsts.value.domains.$remove(index)
}
// 选择客户端CA证书
const selectClientCACert = function () {
	utils.popup("/servers/certs/selectPopup?isCA=1", {
		width: "50em",
		height: "40em",
		callback: function (resp) {
			if (resp.data.cert != null && resp.data.certRef != null) {
				policy.value.clientCARefs.push(resp.data.certRef)
				policy.value.clientCACerts.push(resp.data.cert)
			}
			if (resp.data.certs != null && resp.data.certRefs != null) {
				policy.value.clientCARefs.$pushAll(resp.data.certRefs)
				policy.value.clientCACerts.$pushAll(resp.data.certs)
			}
		}
	})
}
// 上传CA证书
const uploadClientCACert = function () {
	utils.popup("/servers/certs/uploadPopup?isCA=1", {
		callback: function (resp) {
			utils.success("上传成功", function () {
				policy.value.clientCARefs.push(resp.data.certRef)
				policy.value.clientCACerts.push(resp.data.cert)
			})
		}
	})
}

// 删除客户端CA证书
const removeClientCACert = function (index) {
	utils.confirm("确定删除此证书吗？证书数据仍然保留，只是当前网站不再使用此证书。", function () {
		policy.value.clientCARefs.$remove(index)
		policy.value.clientCACerts.$remove(index)
	})
}

watch(hsts.value, function () {
	policy.value.hsts = hsts.value
})

onMounted(function () {
		setTimeout(function () {
			sortableCipherSuites()
		}, 200)
	}
)

</script>

<template>
	<div>
		<h5 class="!mt-8 !mb-4">SSL/TLS相关配置</h5>
		<input type="hidden" name="sslPolicyJSON" :value="JSON.stringify(policy)"/>
		<DefinitionTable>
			<tbody>
			<tr v-show="vProtocol == 'https'">
				<td class="title">启用HTTP/2</td>
				<td>
					<TCheckboxBinary value="1" v-model="policy.http2Enabled"/>
				</td>
			</tr>
			<tr v-show="vProtocol == 'https' && vSupportHttp3">
				<td class="title">启用HTTP/3</td>
				<td>
					<TCheckboxBinary value="1" v-model="policy.http3Enabled"/>
				</td>
			</tr>
			<tr>
				<td class="title">设置证书</td>
				<td>
					<div v-if="policy.certs != null && policy.certs.length > 0" class="label-group">
						<TLabel outlined v-for="(cert, index) in policy.certs">
							{{ cert.name }} / {{ cert.dnsNames }} / 有效至{{ formatTime(cert.timeEndAt) }} &nbsp;
							<a href="" title="删除" @click.prevent="removeCert(index)"><i class="pi pi-remove"></i></a>
						</TLabel>
						<TDivider></TDivider>
					</div>
					<div v-else>
						<span class="red">选择或上传证书后<span v-if="vProtocol == 'https'">HTTPS</span><span v-if="vProtocol == 'tls'">TLS</span>服务才能生效。</span>
						<TDivider></TDivider>
					</div>
					<div class="flex flex-wrap items-center gap-2">
						<TButton secondary="" size="small" type="button" @click.prevent="selectCert()">选择已有证书</TButton> &nbsp;
						<span class="disabled">|</span> &nbsp;
						<TButton secondary="" size="small" type="button" @click.prevent="uploadCert()">上传新证书</TButton> &nbsp;
						<TButton secondary="" size="small" type="button" @click.prevent="uploadBatch()">批量上传证书</TButton> &nbsp;
						<span class="disabled" v-if="vServerId != null && vServerId > 0">|</span> &nbsp;
						<TButton secondary="" size="small" type="button" @click.prevent="requestCert()" v-if="vServerId != null && vServerId > 0">申请免费证书</TButton>
					</div>
				</td>
			</tr>
			<tr>
				<td>TLS最低版本</td>
				<td>
					<TSelect v-model="policy.minVersion">
						<TOption v-for="version in allVersions" :value="version">{{ version }}</TOption>
					</TSelect>
				</td>
			</tr>
			</tbody>
			<MoreOptionsTbody @change="changeOptionsVisible"></MoreOptionsTbody>
			<tbody v-show="moreOptionsVisible">
			<!-- 加密套件 -->
			<tr>
				<td>加密算法套件<em>（CipherSuites）</em></td>
				<td>
					<TCheckboxBinary value="1" v-model="policy.cipherSuitesIsOn">使用自定义</TCheckboxBinary>
					<div v-show="policy.cipherSuitesIsOn">
						<TDivider></TDivider>
						<div class="cipher-suites-box">
							已添加套件({{ policy.cipherSuites.length }})：
							<div class="label-group mt-4" ref="cipherSuitesBoxRef">
								<TLabel outlined="" class="sort-label" v-for="cipherSuite in policy.cipherSuites" :key="cipherSuite">
									<input type="hidden" name="cipherSuites" :value="cipherSuite"/>
									<span v-html="formatCipherSuite(cipherSuite)"></span> &nbsp;
									<a href="" title="删除套件" @click.prevent="removeCipherSuite(cipherSuite)"><i class="pi pi-remove"></i></a>&nbsp;
									<a href="" title="拖动改变顺序" class="sort-handle"><i class="pi pi-bars"></i></a>
								</TLabel>
							</div>
						</div>
						<div>
							<TDivider></TDivider>
							<span v-if="policy.cipherSuites.length > 0"><a href="" @click.prevent="clearCipherSuites()">[清除所有已选套件]</a> &nbsp; </span>
							<a href="" @click.prevent="addBatchCipherSuites(modernCipherSuites)">[添加推荐套件]</a> &nbsp;
							<a href="" @click.prevent="addBatchCipherSuites(intermediateCipherSuites)">[添加兼容套件]</a>
							<TDivider></TDivider>
						</div>

						<div class="cipher-all-suites-box">
							<a href="" @click.prevent="showAllCipherSuites()"><span v-if="policy.cipherSuites.length == 0">所有</span>可选套件({{ allCipherSuites.length }})
								<i class="pi" :class="{'pi-angle-down':!cipherSuitesVisible, 'pi-angle-up':cipherSuitesVisible}"></i></a>

							<div v-if="cipherSuitesVisible" class="label-group mt-4">
								<a href="" v-for="cipherSuite in allCipherSuites" title="点击添加到自定义套件中" @click.prevent="addCipherSuite(cipherSuite)">
									<TLabel outlined=""><span v-html="formatCipherSuite(cipherSuite)"></span></TLabel>
								</a>
							</div>
						</div>
						<p class="comment" v-if="cipherSuitesVisible">点击可选套件添加。</p>
					</div>
				</td>
			</tr>

			<!-- HSTS -->
			<tr v-show="vProtocol == 'https'">
				<td :class="{'color-border':hsts.isOn}">开启HSTS</td>
				<td>
					<TCheckboxBinary name="hstsOn" v-model="hsts.isOn" value="1"/>
					<p class="comment">
						开启后，会自动在响应报头中加入
						<CodeLabel>Strict-Transport-Security:
							<var v-if="!hsts.isOn">...</var>
							<var v-if="hsts.isOn"><span>max-age=</span>{{ hsts.maxAge }}</var>
							<var v-if="hsts.isOn && hsts.includeSubDomains">; includeSubDomains</var>
							<var v-if="hsts.isOn && hsts.preload">; preload</var>
						</CodeLabel>
						<span v-if="hsts.isOn">
							<a href="" @click.prevent="showMoreHSTS()">修改<i class="pi" :class="{'pi-angle-down':!hstsOptionsVisible, 'pi-angle-up':hstsOptionsVisible}"></i> </a>
						 </span>
					</p>
				</td>
			</tr>
			<tr v-show="hsts.isOn && hstsOptionsVisible">
				<td class="color-border">HSTS有效时间<em>（max-age）</em></td>
				<td>
					<div class="ui fields inline">
						<div class="ui field">
							<TInputGroup>
								<TInputText type="text" name="hstsMaxAge" v-model="hstsMaxAgeString" maxlength="10" size="10" @input="changeHSTSMaxAge()" style="width: 8em"/>
								<TInputGroupAddon>秒</TInputGroupAddon>
							</TInputGroup>
						</div>
						<div class="ui field text-gray-400">相当于 {{ hsts.days }}天</div>
					</div>
					<p class="comment">
						<a href="" @click.prevent="setHSTSMaxAge(31536000)" :class="{active:hsts.maxAge == 31536000}">[1年/365天]</a> &nbsp; &nbsp;
						<a href="" @click.prevent="setHSTSMaxAge(15768000)" :class="{active:hsts.maxAge == 15768000}">[6个月/182.5天]</a> &nbsp; &nbsp;
						<a href="" @click.prevent="setHSTSMaxAge(2592000)" :class="{active:hsts.maxAge == 2592000}">[1个月/30天]</a>
					</p>
				</td>
			</tr>
			<tr v-show="hsts.isOn && hstsOptionsVisible">
				<td class="color-border">HSTS包含子域名<em>（includeSubDomains）</em></td>
				<td>
					<TCheckboxBinary name="hstsIncludeSubDomains" value="1" v-model="hsts.includeSubDomains"/>
				</td>
			</tr>
			<tr v-show="hsts.isOn && hstsOptionsVisible">
				<td class="color-border">HSTS预加载<em>（preload）</em></td>
				<td>
					<TCheckboxBinary name="hstsPreload" value="1" v-model="hsts.preload"/>
				</td>
			</tr>
			<tr v-show="hsts.isOn && hstsOptionsVisible">
				<td class="color-border">HSTS生效的域名</td>
				<td colspan="2">
					<div class="names-box label-group">
						<TLabel outlined="" v-for="(domain, arrayIndex) in hsts.domains" :class="{blue:hstsDomainEditingIndex == arrayIndex}">{{ domain }}
							<input type="hidden" name="hstsDomains" :value="domain"/> &nbsp;
							<a href="" @click.prevent="editHstsDomain(arrayIndex)" title="修改"><i class="pi pi-pencil"></i></a>&nbsp;
							<a href="" @click.prevent="removeHstsDomain(arrayIndex)" title="删除"><i class="pi pi-remove"></i></a>
						</TLabel>
					</div>
					<div class="ui fields inline mt-2" v-if="hstsDomainAdding">
						<div class="ui field">
							<TInputText type="text" name="addingHstsDomain" ref="addingHstsDomainRef" style="width:16em" maxlength="100" placeholder="域名，比如example.com" @keyup.enter="confirmAddHstsDomain()" @keypress.enter.prevent="1" v-model="addingHstsDomain" class="w-full"/>
						</div>
						<div class="ui field">
							<TButton secondary="" size="small" type="button" @click="confirmAddHstsDomain()">确定</TButton>
							&nbsp; <a href="" @click.prevent="cancelHstsDomainAdding()"><i class="icon remove"></i></a>
						</div>
					</div>
					<div class="ui field mt-2">
						<PlusButton size="small" type="button" @click="addHstsDomain()"></PlusButton>
					</div>
					<p class="comment">如果没有设置域名的话，则默认支持所有的域名。</p>
				</td>
			</tr>

			<!-- OCSP -->
			<tr>
				<td>OCSP Stapling</td>
				<td>
					<TCheckboxBinary name="ocspIsOn" v-model="policy.ocspIsOn"></TCheckboxBinary>
					<p class="comment">选中表示启用OCSP Stapling。</p>
				</td>
			</tr>

			<!-- 客户端认证 -->
			<tr>
				<td>客户端认证方式</td>
				<td>
					<TSelect name="clientAuthType" v-model="policy.clientAuthType">
						<TOption v-for="authType in allClientAuthTypes" :value="authType.type">{{ authType.name }}</TOption>
					</TSelect>
				</td>
			</tr>
			<tr>
				<td>客户端认证CA证书</td>
				<td>
					<div v-if="policy.clientCACerts != null && policy.clientCACerts.length > 0" class="label-group">
						<TLabel outlined=" " v-for="(cert, index) in policy.clientCACerts">
							{{ cert.name }} / {{ cert.dnsNames }} / 有效至{{ formatTime(cert.timeEndAt) }} &nbsp;
							<a href="" title="删除" @click.prevent="removeClientCACert()"><i class="pi pi-remove"></i></a>
						</TLabel>
						<TDivider></TDivider>
					</div>
					<TButton secondary="" size="small" type="button" @click.prevent="selectClientCACert()">选择已有证书</TButton> &nbsp;
					<TButton secondary="" size="small" type="button" @click.prevent="uploadClientCACert()">上传新证书</TButton>
					<p class="comment">用来校验客户端证书以增强安全性，通常不需要设置。</p>
				</td>
			</tr>
			</tbody>
		</DefinitionTable>
		<div class="ui margin"></div>
	</div>
</template>

<style scoped>
</style>
