diff --git a/.gitignore b/.gitignore index 8565e8c..d6ee0f5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,4 @@ node_modules/**/* dist/ -*.self.js -*.self.json \ No newline at end of file +data/**/* \ No newline at end of file diff --git a/compile-template.js b/compile-template.js index 4c76dbe..5b884d3 100644 --- a/compile-template.js +++ b/compile-template.js @@ -32,21 +32,22 @@ if (!fs.existsSync(templateOutDir)) { const templateRootDir = path.resolve(rootDir, 'template') const templateFiles = fs.readdirSync(templateRootDir).filter(file => file.endsWith('.ejs')) for (let fn of templateFiles) { - let content = fs.readFileSync(path.resolve(templateRootDir, fn), 'utf8') - // - content = content.replace(/<([a-zA-Z\-]+)\s+inject="([^"]+)"\s+escape="([^"]+)"\s*><\/([a-zA-Z\-]+)>/g, - function (substring, tag, inject, escape, closeTag) { - let injectFilePath = path.resolve(templateRootDir, inject) - let injectContent = fs.readFileSync(injectFilePath, 'utf8') - escape = escape === 'true' || escape === '1' - if (escape) { - injectContent = escapeHtml(injectContent) - } - return `<${tag}>${injectContent}` - } - ) + // let content = fs.readFileSync(path.resolve(templateRootDir, fn), 'utf8') + // // + // content = content.replace(/<([a-zA-Z\-]+)\s+inject="([^"]+)"\s+escape="([^"]+)"\s*><\/([a-zA-Z\-]+)>/g, + // function (substring, tag, inject, escape, closeTag) { + // let injectFilePath = path.resolve(templateRootDir, inject) + // let injectContent = fs.readFileSync(injectFilePath, 'utf8') + // escape = escape === 'true' || escape === '1' + // if (escape) { + // injectContent = escapeHtml(injectContent) + // } + // return `<${tag}>${injectContent}` + // } + // ) // write to outDir let outFilePath = path.resolve(templateOutDir, fn) - fs.writeFileSync(outFilePath, content, 'utf8') + // fs.writeFileSync(outFilePath, content, 'utf8') + fs.copyFileSync(path.resolve(templateRootDir, fn), outFilePath) } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d37ab69..4b0e90c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ssl-autoupdater", - "version": "1.0.0", + "version": "0.1.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ssl-autoupdater", - "version": "1.0.0", + "version": "0.1.5", "license": "MIT", "dependencies": { "ansi-html": "^0.0.9", @@ -24,6 +24,9 @@ "ts-loader": "^9.5.1", "tsc-alias": "^1.8.8", "typescript": "^5.3.3" + }, + "engines": { + "node": ">=16.13.1" } }, "node_modules/@fastify/busboy": { diff --git a/package.json b/package.json index 5744709..39f77ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ssl-autoupdater", - "version": "0.1.0", + "version": "0.1.5", "description": "Automatically update SSL certificate on different cloud platforms", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/components/output.ts b/src/components/output.ts index d686f44..6072416 100644 --- a/src/components/output.ts +++ b/src/components/output.ts @@ -3,9 +3,16 @@ import "colors" const date_string = () => new Date().toLocaleString().replace(/\/(\d)([^\d])/, "/0$1$2").replace(/\/(\d)([^\d])/, "/0$1$2").replace(/\//g, "-") -export class Output { - private session: Session; +export type OutputConstructor = new (session: Session, identifier?: string) => Output + +export abstract class Output { + protected session: Session; public identifier: string; + + /** + * Output template class + * - Remember to implement session output via `session.appendLine` + */ constructor(session: Session, identifier?: string) { this.session = session; if (typeof identifier === "string") { @@ -15,6 +22,21 @@ export class Output { } } + abstract log(...args: any[]): any + abstract info(...args: any[]): any + abstract warn(...args: any[]): any + abstract error(...args: any[]): any + abstract debug(...args: any[]): any + abstract success(...args: any[]): any + abstract failure(...args: any[]): any +} + +export class ConsoleOutput extends Output { + + constructor(session: Session, identifier?: string) { + super(session, identifier) + } + private _output(consoleFunc: Function, append: string[], args: any[]) { let date_str = date_string().gray let append_str = append.join(" ") diff --git a/src/components/ssl-updater.ts b/src/components/ssl-updater.ts index e5a1f91..7df7e32 100644 --- a/src/components/ssl-updater.ts +++ b/src/components/ssl-updater.ts @@ -2,7 +2,7 @@ import MailSender from "@/utils/mail-sender" import Timer from "@/utils/timer" import Cache from "@/utils/cache" import { Session } from "@/components/session" -import { Output } from "@/components/output" +import { ConsoleOutput, Output, OutputConstructor } from "@/components/output" import fs from "fs" import path from "path" import { sha256 } from "@/utils/utils" @@ -12,6 +12,17 @@ export type CertificateData = { private: string } +export type TriggerReturnTyoe = { + /** + * 生成消息的原料 + */ + msg_material: T, + /** + * 是否发送邮件 + */ + send_mail: boolean +} + /** * 根据域名和算法获取证书内容的函数,返回证书内容和私钥内容 \ * 如果文件不存在,则该文件内容返回 `undefined` @@ -48,17 +59,11 @@ export type SSLUpdaterOptions = { * 邮件发送器(默认不发送邮件) */ mailer?: MailSender -} - -export type StatusRecord = { - new_cert_id: string, - old_cert_id: string, - domain: string, - sans: string[], - uploaded: boolean | null, - updated: boolean | null, - old_deleted: boolean | null, - comment: string + /** + * 输出的类构造器 + * @experimental 该选项可能在未来的版本中移除 + */ + outputCreator?: OutputConstructor } function genTimeBasedHash(identifier: string, random_upper: boolean = false) { @@ -140,6 +145,12 @@ export default abstract class SSLUpdater { } else throw new TypeError("mailer must be an instance of MailSender"); + // opts.outputCreator + let outputCreator = ConsoleOutput as OutputConstructor + if (opts.outputCreator instanceof Function && typeof opts.outputCreator.name === "string") { + outputCreator = opts.outputCreator; + } + // timer let defaultTimerStart = new Date(); defaultTimerStart.setHours(4, 0, 0, 0) @@ -149,7 +160,7 @@ export default abstract class SSLUpdater { this.cache = new Cache(); this.session = new Session() - this.output = new Output(this.session, this.identifier); + this.output = new outputCreator(this.session, this.identifier); // ID of setInterval this.watch_pool = new Set(); @@ -178,41 +189,43 @@ export default abstract class SSLUpdater { if (typeof d !== "string") throw new TypeError("Domains must be string or array of string") }); - let on_running = false, trigger_cnt = 0; - let func: () => any; - const itvid = setInterval(func = async () => { - if (on_running) return; - if (that.timer.is_expired()) { - that.timer.set_future(); + let trigger_cnt = 0; + // let on_running = false; + const itvid = this.timer.watch(async (run_time) => { + // if (on_running) return; - on_running = true; - that.session.start(); + // on_running = true; + that.session.start(); - let trigger_id = genTimeBasedHash(this.identifier, false); - that.output.log(`[#${trigger_cnt}] Timer triggered, ID: ${trigger_id}`); - let status_record_array = await that.triggerUpdate(domains as string[]) - .catch(err => { - that.output.error(err); - return []; - }) - that.output.log(`[#${trigger_cnt}] Trigger complete`); + let trigger_id = genTimeBasedHash(this.identifier, false); + that.output.log(`[#${trigger_cnt}] Timer triggered, ID: ${trigger_id}`); + let { msg_material, send_mail } = await that.triggerUpdate(domains as string[]) + .catch(err => { + that.output.error(err); + return { msg_material: [], send_mail: true } + }) + that.output.log(`[#${trigger_cnt}] Trigger complete`); - that.session.end(); - on_running = false; + that.session.end(); + // on_running = false; - let message = that.genMsg(trigger_id, status_record_array, that.session.lines()); + let message = that.genMsg(trigger_id, msg_material, that.session.lines()); + if (send_mail) { this.output.log("Sending message...") let send_result = await that.sendMsg("[SSL Updater] 证书更新结果通知", message) .catch(err => { that.output.error(err); }) if (send_result === "cancel") this.output.log("Send canceled") else if (send_result === "failure") this.output.error("Send failed") else this.output.log("Send done") - - ++trigger_cnt; } - }, 10 * 1000) - func(); + + ++trigger_cnt; + + }, { + preventWhenRunning: true, + InstantlyRun: true + }) this.watch_pool.add(itvid); return itvid; @@ -222,15 +235,15 @@ export default abstract class SSLUpdater { * 触发更新 * @param domains 检测的域名列表 */ - abstract triggerUpdate(domains: string[]): Promise; + abstract triggerUpdate(domains: string[]): Promise>; /** * 解析状态记录 * @param trigger_id 触发 ID - * @param record_list 状态记录列表 + * @param msg_material 生成消息的原料 * @param terminal_lines 终端输出 */ - abstract genMsg(trigger_id: string, record_list: any[], terminal_lines: string[]): string; + abstract genMsg(trigger_id: string, msg_material: Object, terminal_lines: string[]): string; /** * 发送消息 diff --git a/src/template/css/spectre-exp.min.css b/src/template/css/spectre-exp.min.css deleted file mode 100644 index d893164..0000000 --- a/src/template/css/spectre-exp.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Experimentals v0.4.5 | MIT License | github.com/picturepan2/spectre */.calendar{border:.05rem solid #e7e9ed;border-radius:.1rem;display:block;min-width:280px;text-align:center}.calendar .calendar-nav{align-items:center;background:#f8f9fa;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:flex;display:-ms-flexbox;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f8f9fa;border-bottom:.05rem solid #e7e9ed;color:#acb3c2;font-size:.7rem}.calendar .calendar-body{color:#667189}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#667189;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:all .2s ease;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item:disabled,.calendar .calendar-date.disabled .calendar-event,.calendar .calendar-date.disabled .date-item{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #e7e9ed;border-right:.05rem solid #e7e9ed;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{background:#f1f1fc;border-radius:.1rem;color:#5755d9;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-align:left;text-overflow:ellipsis;white-space:nowrap}.carousel{background:#f8f9fa;display:block;overflow:hidden;position:relative;width:100%}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(231,233,237,.25);border-color:rgba(231,233,237,.5);color:#e7e9ed;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s ease;z-index:200}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4){color:#e7e9ed}.carousel .carousel-nav{bottom:.4rem;display:flex;display:-ms-flexbox;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:200}.carousel .carousel-nav .nav-item{color:rgba(231,233,237,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:none;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(69,77,93,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f8f9fa;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f8f9fa}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:flex;display:-ms-flexbox;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-sidebar{background:#f8f9fa;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s ease;z-index:300}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(69,77,93,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:200}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}@media (min-width:960px){.off-canvas .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas .off-canvas-content{padding-left:.8rem}.off-canvas .off-canvas-toggle{display:none}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(69,77,93,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(69,77,93,.75);top:0;transform:translateZ(50px) scale(.95);transition:all .4s ease;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f0f1f4;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#f0f1f4 linear-gradient(to right,#5755d9 30%,#f0f1f4 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;transition:transform .2s ease;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;transition:transform .2s ease;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;transition:transform .2s ease;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#e7e9ed;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#e7e9ed;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#e7e9ed;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#f0f1f4;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#f0f1f4;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#f0f1f4;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:flex;display:-ms-flexbox;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#e7e9ed;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{border-radius:50%;color:#fff;display:block;height:1.2rem;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none} \ No newline at end of file diff --git a/src/template/css/spectre-icons.min.css b/src/template/css/spectre-icons.min.css deleted file mode 100644 index b66068e..0000000 --- a/src/template/css/spectre-icons.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Icons v0.4.5 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;content:"";height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;content:"";height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;content:"";height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;content:"";height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;content:"";height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;content:"";height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;content:"";height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;content:"";height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;content:"";height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;content:"";height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;content:"";height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;content:"";height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;content:"";height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;content:"";height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;content:"";height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;content:"";height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;content:"";height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;content:"";height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;content:"";height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;content:"";height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;content:"";height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;content:"";height:.5em;width:.6em}.icon-flag::before{background:currentColor;content:"";height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;content:"";height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;content:"";height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;content:"";height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;content:"";height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;content:"";height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;content:"";height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;content:"";height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;content:"";height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;content:"";height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;content:"";height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;content:"";height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;content:"";height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;content:"";height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;content:"";height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;content:"";height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;content:"";height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;content:"";height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;content:"";height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.15em,.17em -.15em;content:"";height:.1em;width:.1em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;content:"";height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/src/template/css/spectre.min.css b/src/template/css/spectre.min.css deleted file mode 100644 index b6c6815..0000000 --- a/src/template/css/spectre.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css v0.4.5 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#50596c;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#4240d4;text-decoration:underline}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#454d5d;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.2;padding:.1rem .15rem}mark{background:#ffe9b3;border-radius:.1rem;color:#50596c;padding:.05rem}blockquote{border-left:.1rem solid #e7e9ed;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}:lang(zh){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.cjk ins,.cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.cjk del+del,.cjk del+s,.cjk ins+ins,.cjk ins+u,.cjk s+del,.cjk s+s,.cjk u+ins,.cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f8f9fa}.table.table-hover tbody tr:hover{background:#f0f1f4}.table tbody tr.active,.table.table-striped tbody tr.active{background:#f0f1f4}.table td,.table th{border-bottom:.05rem solid #e7e9ed;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1rem;outline:0;padding:.35rem .4rem;text-align:center;text-decoration:none;transition:all .2s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#4240d4}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.15rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.45rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:.8rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:0;text-decoration:none;width:.8rem}.btn.btn-clear:hover{opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:inline-flex;display:-ms-inline-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:flex;display:-ms-flexbox}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1rem;padding:.4rem 0}.form-label.label-sm{padding:.2rem 0}.form-label.label-lg{padding:.5rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #caced7;border-radius:.1rem;color:#50596c;display:block;font-size:.8rem;height:1.8rem;line-height:1rem;max-width:100%;outline:0;padding:.35rem .4rem;position:relative;transition:all .2s ease;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input::-webkit-input-placeholder{color:#acb3c2}.form-input:-ms-input-placeholder{color:#acb3c2}.form-input::placeholder{color:#acb3c2}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.15rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.45rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input{height:auto}.form-input-hint{color:#acb3c2;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:.05rem solid #caced7;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1rem;outline:0;padding:.35rem .4rem;vertical-align:middle;width:100%}.form-select[multiple],.form-select[size]{height:auto}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.15rem 1.1rem .15rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.45rem 1.4rem .45rem .6rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .35rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.5rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.5rem}.form-checkbox,.form-radio,.form-switch{display:inline-block;line-height:1rem;padding:.2rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #caced7;cursor:pointer;display:inline-block;position:absolute;transition:all .2s ease}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#f0f1f4}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:12px;left:50%;margin-left:-4px;margin-top:-8px;position:absolute;top:50%;transform:rotate(45deg);width:8px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:4px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:4px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#e7e9ed;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:all .2s ease;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f8f9fa}.input-group{display:flex;display:-ms-flexbox}.input-group .input-group-addon{background:#f8f9fa;border:.05rem solid #caced7;border-radius:.1rem;line-height:1rem;padding:.35rem .4rem}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.15rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.45rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:inline-flex;display:-ms-inline-flexbox}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#f0f1f4;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f8f9fa}input.disabled+.form-icon,input:disabled+.form-icon{background:#f0f1f4;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem}.form-horizontal .form-group{display:flex;display:-ms-flexbox}.form-horizontal .form-checkbox,.form-horizontal .form-radio,.form-horizontal .form-switch{margin:.2rem 0}.label{background:#f0f1f4;border-radius:.1rem;color:#5b657a;display:inline-block;line-height:1.2;padding:.1rem .15rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fdf4f4;border-radius:.1rem;color:#e06870;font-size:85%;line-height:1.2;padding:.1rem .15rem}.code{border-radius:.1rem;color:#50596c;position:relative}.code::before{color:#acb3c2;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f8f9fa;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#667189;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.columns{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.columns.col-gapless{margin-left:0;margin-right:0}.columns.col-gapless>.column{padding-left:0;padding-right:0}.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.hide-xs{display:none!important}.show-xs{display:block!important}}.navbar{align-items:stretch;display:flex;display:-ms-flexbox;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:flex;display:-ms-flexbox;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:flex;display:-ms-flexbox;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;font-weight:500;text-decoration:none}.accordion input:checked~.accordion-header .icon,.accordion[open] .accordion-header .icon{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:all .2s ease}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .2s ease}summary.accordion-header::-webkit-details-marker{display:none}.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:flex;display:-ms-flexbox;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#acb3c2;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.1rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.badge.avatar-xs::after{content:"";height:.4rem;min-width:.4rem;padding:0;width:.4rem}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#667189;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#667189}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#e7e9ed;content:"/";padding-right:.2rem}.bar{background:#f0f1f4;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#f0f1f4;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #e7e9ed;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#f0f1f4;border-radius:5rem;color:#667189;display:inline-flex;display:-ms-inline-flexbox;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:100%;padding:.2rem .4rem;text-decoration:none;vertical-align:middle}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f8f9fa;border-radius:.1rem;color:#667189;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(69,77,93,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:100}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;text-decoration:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{float:right;padding:.2rem 0}.menu .menu-badge .btn{margin-top:-.1rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:flex;display:-ms-flexbox;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(248,249,250,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;max-width:640px;width:100%;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(69,77,93,.3);display:block;padding:0 .8rem;text-align:left}.modal-container .modal-header{padding:.8rem}.modal-container .modal-body{max-height:50vh;overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#667189;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#50596c;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:flex;display:-ms-flexbox;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;color:#667189;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #e7e9ed;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{content:attr(data-tooltip);left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s ease;width:320px;z-index:400}.popover .popover-container:hover,.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right .popover-container:hover,.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom .popover-container:hover,.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left .popover-container:hover,.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(69,77,93,.3)}.step{display:flex;display:-ms-flexbox;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#acb3c2;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#e7e9ed}.step .step-item.active~.step-item a::before{background:#e7e9ed}.tab{align-items:center;border-bottom:.05rem solid #e7e9ed;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:flex;display:-ms-flexbox;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(69,77,93,.9);border:.05rem solid #454d5d;border-color:#454d5d;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.9);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.9);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.9);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.9);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:4px -2px 4px 4px}.tooltip{position:relative}.tooltip::after{background:rgba(69,77,93,.9);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:all .2s ease;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9}a.text-primary:focus,a.text-primary:hover{color:#4240d4}.text-secondary{color:#e5e5f9}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}.text-gray{color:#acb3c2}a.text-gray:focus,a.text-gray:hover{color:#9ea6b7}.text-light{color:#fff}a.text-light:focus,a.text-light:hover{color:#f2f2f2}.text-success{color:#32b643}a.text-success:focus,a.text-success:hover{color:#2da23c}.text-warning{color:#ffb700}a.text-warning:focus,a.text-warning:hover{color:#e6a500}.text-error{color:#e85600}a.text-error:focus,a.text-error:hover{color:#cf4d00}.bg-primary{background:#5755d9}.bg-secondary{background:#f1f1fc}.bg-dark{background:#454d5d}.bg-gray{background:#f8f9fa}.bg-success{background:#32b643}.bg-warning{background:#ffb700}.bg-error{background:#e85600}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:flex;display:-ms-flexbox}.d-inline-flex{display:inline-flex;display:-ms-inline-flexbox}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#acb3c2;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #e7e9ed;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #e7e9ed;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after,.container::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.relative{position:relative}.absolute{position:absolute}.fixed{position:fixed}.centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0}.mb-0{margin-bottom:0}.ml-0{margin-left:0}.mr-0{margin-right:0}.mt-0{margin-top:0}.mx-0{margin-left:0;margin-right:0}.my-0{margin-bottom:0;margin-top:0}.m-1{margin:.2rem}.mb-1{margin-bottom:.2rem}.ml-1{margin-left:.2rem}.mr-1{margin-right:.2rem}.mt-1{margin-top:.2rem}.mx-1{margin-left:.2rem;margin-right:.2rem}.my-1{margin-bottom:.2rem;margin-top:.2rem}.m-2{margin:.4rem}.mb-2{margin-bottom:.4rem}.ml-2{margin-left:.4rem}.mr-2{margin-right:.4rem}.mt-2{margin-top:.4rem}.mx-2{margin-left:.4rem;margin-right:.4rem}.my-2{margin-bottom:.4rem;margin-top:.4rem}.p-0{padding:0}.pb-0{padding-bottom:0}.pl-0{padding-left:0}.pr-0{padding-right:0}.pt-0{padding-top:0}.px-0{padding-left:0;padding-right:0}.py-0{padding-bottom:0;padding-top:0}.p-1{padding:.2rem}.pb-1{padding-bottom:.2rem}.pl-1{padding-left:.2rem}.pr-1{padding-right:.2rem}.pt-1{padding-top:.2rem}.px-1{padding-left:.2rem;padding-right:.2rem}.py-1{padding-bottom:.2rem;padding-top:.2rem}.p-2{padding:.4rem}.pb-2{padding-bottom:.4rem}.pl-2{padding-left:.4rem}.pr-2{padding-right:.4rem}.pt-2{padding-top:.4rem}.px-2{padding-left:.4rem;padding-right:.4rem}.py-2{padding-bottom:.4rem;padding-top:.4rem}.rounded{border-radius:.1rem}.circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/src/template/qcloud.ejs b/src/template/qcloud.ejs index 2ba76a9..25e9b6e 100644 --- a/src/template/qcloud.ejs +++ b/src/template/qcloud.ejs @@ -1,84 +1,74 @@ - - - - - -
-
-
-
-
腾讯云 SSL 证书自动更新结果通知
-
Trigger ID #<%= trigger_id %>
-
-
+ + <% + const use_color = { + 'light': "style='color: #acb3c2 !important;'", + 'gray': "style='color: #79808e !important;'", + 'dark': "style='color: #3b4351 !important;'", + 'success': "style='color: #32b643 !important;'", + 'warning': "style='color: #ffb700 !important;'", + 'error': "style='color: #e85600 !important;'", + } + const use_class = { + 'line': "style='display: block;'", + 'line head': "style='display: block; margin: 0.25rem 0; font-size: .9rem;'", + } + %> + + + +
+ + + + + + + +
+
腾讯云 SSL 证书自动更新结果通知
+
>Trigger ID #<%= trigger_id %>
+

你的腾讯云证书在近期进行了一次自动更新,以下是更新结果

- <% for (const info of data) { %>

- 证书 #<%= info.old_cert_id.text %> - - 新证书 ID: - <%= info.new_cert_id.text %> + >证书 #<%= info.old_cert_id.text %> + > + >新证书 ID: + ><%= info.new_cert_id.text %> - - 原证书 ID: - <%= info.old_cert_id.text %> - (<%= info.old_deleted.text %>) + > + >原证书 ID: + ><%= info.old_cert_id.text %> + >(<%= info.old_deleted.text %>) - - 主域名: - <%= info.domain.text %> + > + >主域名: + ><%= info.domain.text %> - - SANS 域名: - <%= info.sans.text %> + > + >SANS 域名: + ><%= info.sans.text %> - - 证书上传: - <%= info.uploaded.text %> + > + >证书上传: + ><%= info.uploaded.text %> - - 资源迁移: - <%= info.updated.text %> + > + >资源迁移: + ><%= info.updated.text %> - - 备注: - <%= info.comment.text %> + > + >备注: + ><%= info.comment.text %>

<% } %> - 控制台输出 -
<%- output %>
- - - - + >控制台输出 +
+                        OUTPUT
+                        <%- output %>
+                    
+
+
\ No newline at end of file diff --git a/src/template/qiniu.ejs b/src/template/qiniu.ejs index 4a2087a..7a20512 100644 --- a/src/template/qiniu.ejs +++ b/src/template/qiniu.ejs @@ -1,84 +1,74 @@ - - - - - -
-
-
-
-
七牛云 SSL 证书自动更新结果通知
-
Trigger ID #<%= trigger_id %>
-
-
+ + <% + const use_color = { + 'light': "style='color: #acb3c2 !important;'", + 'gray': "style='color: #79808e !important;'", + 'dark': "style='color: #3b4351 !important;'", + 'success': "style='color: #32b643 !important;'", + 'warning': "style='color: #ffb700 !important;'", + 'error': "style='color: #e85600 !important;'", + } + const use_class = { + 'line': "style='display: block;'", + 'line head': "style='display: block; margin: 0.25rem 0; font-size: .9rem;'", + } + %> + + + +
+ + + + + + + +
+
七牛云 SSL 证书自动更新结果通知
+
>Trigger ID #<%= trigger_id %>
+

你的七牛云证书在近期进行了一次自动更新,以下是更新结果

- <% for (const info of data) { %>

- 证书 #<%= info.old_cert_id.text %> - - 新证书 ID: - <%= info.new_cert_id.text %> + >证书 #<%= info.old_cert_id.text %> + > + >新证书 ID: + ><%= info.new_cert_id.text %> - - 原证书 ID: - <%= info.old_cert_id.text %> - (<%= info.old_deleted.text %>) + > + >原证书 ID: + ><%= info.old_cert_id.text %> + >(<%= info.old_deleted.text %>) - - 主域名: - <%= info.domain.text %> + > + >主域名: + ><%= info.domain.text %> - - SANS 域名: - <%= info.sans.text %> + > + >SANS 域名: + ><%= info.sans.text %> - - 证书上传: - <%= info.uploaded.text %> + > + >证书上传: + ><%= info.uploaded.text %> - - CDN 证书更新: - <%= info.cdn_updated.text %> + > + >CDN 证书更新: + ><%= info.cdn_updated.text %> - - 备注: - <%= info.comment.text %> + > + >备注: + ><%= info.comment.text %>

<% } %> - 控制台输出 -
<%- output %>
- - - - + >控制台输出 +
+                        OUTPUT
+                        <%- output %>
+                    
+
+
\ No newline at end of file diff --git a/src/updater/qcloud.ts b/src/updater/qcloud.ts index f0d7e8c..8a6bddf 100644 --- a/src/updater/qcloud.ts +++ b/src/updater/qcloud.ts @@ -1,5 +1,4 @@ -// import tencentcloud from "tencentcloud-sdk-nodejs" // compile error -import { ssl } from "tencentcloud-sdk-nodejs" +import * as tencentcloud from "tencentcloud-sdk-nodejs" import SSLModule from "tencentcloud-sdk-nodejs/tencentcloud/services/ssl/v20191205/ssl_models" import { Client as QCloudSSLClient } from "tencentcloud-sdk-nodejs/tencentcloud/services/ssl/v20191205/ssl_client"; import fs from "fs" @@ -7,7 +6,7 @@ import path from "path" import Timer from "@/utils/timer" import { sha256, ansi2html } from "@/utils/utils" import MailSender from "@/utils/mail-sender" -import SSLUpdater, { SSLUpdaterOptions, sendMsgStatus } from "@/components/ssl-updater" +import SSLUpdater, { SSLUpdaterOptions, TriggerReturnTyoe, sendMsgStatus } from "@/components/ssl-updater" import "colors" type StatusRecord = { @@ -112,7 +111,7 @@ export class QCloudSSLUpdater extends SSLUpdater { this.mailer.Template.HTML.set(fs.readFileSync(path.resolve(__dirname, "../template/qcloud.ejs"), "utf-8")); } - this._CLIENT = new ssl.v20191205.Client({ + this._CLIENT = new tencentcloud.ssl.v20191205.Client({ credential: { secretId: secretId, secretKey: secretKey, @@ -233,7 +232,7 @@ export class QCloudSSLUpdater extends SSLUpdater { for (k in task_resp) { if (!Object.prototype.hasOwnProperty.call(task_resp, k)) continue; if (!Array.isArray(task_resp[k])) continue; - let task_value: any = task_resp[k as keyof BindResource]; + let task_value: any[] | undefined = task_resp[k as keyof BindResource]; if (typeof task_value === "undefined" || task_value.length === 0) continue; else { let val = task_value.filter((item: any) => { @@ -248,7 +247,7 @@ export class QCloudSSLUpdater extends SSLUpdater { res[k] = val; } } - if (typeof task_info.CertId === "string") result[task_info.CertId] = null; + if (typeof task_info.CertId === "string") result[task_info.CertId] = res; return resolve(void 0); } }, 10 * 1000) @@ -304,8 +303,9 @@ export class QCloudSSLUpdater extends SSLUpdater { * 触发更新 * @param domains 检测的域名列表(留空则检测所有) */ - async triggerUpdate(domains: string[]): Promise { + async triggerUpdate(domains: string[]): Promise> { let status_record_json: { [cert_id: string]: StatusRecord } = {}; + let do_send_mail = false; const fmt = (c?: number) => typeof c === "undefined" ? "?" : c.toString(); try { const detectAll = typeof domains === "undefined" || domains.length === 0; @@ -338,13 +338,7 @@ export class QCloudSSLUpdater extends SSLUpdater { continue; // invalid cert info } - let need_continue = this._force_upload_days > 0 && (new Date(cert_info.CertEndTime).getTime() - Timer.now() < this._force_upload_days * 24 * 60 * 60 * 1000) - - if (!need_continue) { - this.output.log("STEP", "UPLOAD", "SKIP", "|", "REASON", "SCHEDULE_NOT_MATCH") - this.output.info(`Domain ${cert_info.Domain} doesn't match schedule, skip`); - continue; - } + let is_force_upload = this._force_upload_days > 0 && (new Date(cert_info.CertEndTime).getTime() - Timer.now() < this._force_upload_days * 24 * 60 * 60 * 1000) const { public: local_pubcer, private: local_ptekey } = this._founder(cert_info.Domain, cert_info.CertSANs || [], cert_info.EncryptAlgorithm) || {}; if (typeof local_pubcer !== "string" || typeof local_ptekey !== "string") { @@ -361,14 +355,18 @@ export class QCloudSSLUpdater extends SSLUpdater { let is_local_changed = this.cache.is_changed([cert_info.Domain, cert_info.CertSANs, cert_info.EncryptAlgorithm], { public: local_pubcer_sha256, private: local_ptekey_sha256 - }) + }, true) + + let need_upload = is_force_upload || is_local_changed; - if (!is_local_changed) { + if (!need_upload && !is_local_changed) { this.output.log("STEP", "UPLOAD", "SKIP", "|", "REASON", "LOCAL_CERT_NO_CHANGE") this.output.info(`Local certificate for domain ${cert_info.Domain} has no change, no need to update`); continue; } + this.output.log("PROCESS", "FORCE_UPLOAD", is_force_upload ? "ON" : "OFF") + // get detail this.output.log("STEP", "UPLOAD[GET_OLD_DETAIL]", "START", "|", "CERT_ID", cert_info.CertificateId); let detail_resp = await this.getCertDetail(cert_info.CertificateId); @@ -406,6 +404,7 @@ export class QCloudSSLUpdater extends SSLUpdater { old_deleted: null, comment: '' } + do_send_mail = true; // upload this.output.log("STEP", "UPLOAD[UPLOAD_NEW_CERT]", "START") @@ -446,7 +445,10 @@ export class QCloudSSLUpdater extends SSLUpdater { if (need_update_certificates.length === 0) { this.output.info("No certificate needs to be updated, nothing to do"); - return Object.values(status_record_json); + return { + msg_material: Object.values(status_record_json), + send_mail: false // nothing to do, no need to send mail + } } // fetch bound resources @@ -494,12 +496,14 @@ export class QCloudSSLUpdater extends SSLUpdater { resources: resource_types as ResourceType[], regions: resource_regions }) - this.output.log("STEP", "UPDATE[UPDATE_CERT_INSTANCE]", "DONE", "|", "STATUS", update_resp.DeployStatus); + this.output.log("STEP", "UPDATE[UPDATE_CERT_INSTANCE]", "DONE", "|", "STATUS", update_resp.DeployStatus, "|", "RECORD_ID", update_resp.DeployRecordId); - if (update_resp.DeployStatus !== 1) { - this.output.log("STEP", "UPDATE", "SKIP", "|", "REASON", "DEPLOY_FAILED".red) - this.output.failure(`Failed to update resources for certificate ${cert_info.CertificateId} (domain: ${cert_info.Domain}) (${cert_info.Alias})`); - continue; + if (update_resp.DeployStatus !== 1) { // 部署失败 + if (typeof update_resp.DeployRecordId !== "number" || update_resp.DeployRecordId < 0) { // 是否是正在进行中 + this.output.log("STEP", "UPDATE", "SKIP", "|", "REASON", "DEPLOY_FAILED".red) + this.output.failure(`Failed to update resources for certificate ${cert_info.CertificateId} (domain: ${cert_info.Domain}) (${cert_info.Alias})`); + continue; + } } this.output.log("STEP", "UPDATE", "DONE"); @@ -520,7 +524,10 @@ export class QCloudSSLUpdater extends SSLUpdater { } } } catch (err) { this.output.error(err); } - return Object.values(status_record_json) + return { + msg_material: Object.values(status_record_json), + send_mail: do_send_mail + } } async sendMsg(title: string, content: string): Promise { diff --git a/src/updater/qiniu.ts b/src/updater/qiniu.ts index c62d937..511da75 100644 --- a/src/updater/qiniu.ts +++ b/src/updater/qiniu.ts @@ -3,7 +3,7 @@ import path from "path" import Timer from "@/utils/timer" import { sha256, ansi2html, hmacSha1 } from "@/utils/utils" import MailSender from "@/utils/mail-sender" -import SSLUpdater, { SSLUpdaterOptions, CertificateData, sendMsgStatus } from "@/components/ssl-updater" +import SSLUpdater, { SSLUpdaterOptions, CertificateData, sendMsgStatus, TriggerReturnTyoe } from "@/components/ssl-updater" import urllib from "urllib" import "colors" @@ -77,11 +77,11 @@ export namespace QiniuAPI { */ create_time: number /** - * 证书生效时间 + * 证书生效时间(单位:秒) */ not_before: number /** - * 证书过期时间 + * 证书过期时间(单位:秒) */ not_after: number /** @@ -156,11 +156,11 @@ export namespace QiniuAPI { */ create_time: number /** - * 证书生效时间 + * 证书生效时间(单位:秒) */ not_before: number /** - * 证书过期时间 + * 证书过期时间(单位:秒) */ not_after: number /** @@ -767,8 +767,9 @@ export class QiniuSSLUpdater extends SSLUpdater { }) } - async triggerUpdate(domains: string[]): Promise { + async triggerUpdate(domains: string[]): Promise> { let status_record_json: { [cert_id: string]: StatusRecord } = {}; + let do_send_mail = false const fmt = (c?: number) => typeof c === "undefined" ? "?" : c.toString(); try { const detectAll = typeof domains === "undefined" || domains.length === 0; @@ -795,13 +796,7 @@ export class QiniuSSLUpdater extends SSLUpdater { "|", "DOMAIN", cert_info.common_name, "|", "ALIAS", cert_info.name); - let need_continue = this._force_upload_days > 0 && (new Date(cert_info.not_after).getTime() - Timer.now() < this._force_upload_days * 24 * 60 * 60 * 1000) - - if (!need_continue) { - this.output.log("STEP", "UPLOAD", "SKIP", "|", "REASON", "SCHEDULE_NOT_MATCH"); - this.output.info(`Domain ${cert_info.common_name} doesn't match schedule, skip`); - continue; - } + let is_force_upload = this._force_upload_days > 0 && (new Date(cert_info.not_after * 1000).getTime() - Timer.now() < this._force_upload_days * 24 * 60 * 60 * 1000) const { public: local_pubcer, private: local_ptekey } = this._founder(cert_info.common_name, cert_info.dnsnames, cert_info.encrypt); if (typeof local_pubcer !== "string" || typeof local_ptekey !== "string") { @@ -818,14 +813,18 @@ export class QiniuSSLUpdater extends SSLUpdater { let is_local_changed = this.cache.is_changed([cert_info.common_name, cert_info.dnsnames, cert_info.encrypt], { public: local_pubcer_sha256, private: local_ptekey_sha256 - }) + }, true) - if (!is_local_changed) { + let need_upload = is_force_upload || is_local_changed + + if (!need_upload && !is_local_changed) { this.output.log("STEP", "UPLOAD", "SKIP", "|", "REASON", "LOCAL_CERT_NO_CHANGE"); this.output.info(`Local certificate for domain ${cert_info.common_name} has no change, no need to update`); continue; } + this.output.log("PROCESS", "FORCE_UPLOAD", is_force_upload ? "ON" : "OFF") + // get detail this.output.log("STEP", "UPLOAD[GET_OLD_DETAIL]", "START", "|", "OLD_CERT_ID", cert_info.certid) let detail_resp = await this.getCertDetail(cert_info.certid); @@ -861,6 +860,7 @@ export class QiniuSSLUpdater extends SSLUpdater { old_deleted: null, comment: '' } + do_send_mail = true // upload this.output.log("STEP", "UPLOAD[UPLOAD_NEW_CERT]", "START"); @@ -1013,7 +1013,10 @@ export class QiniuSSLUpdater extends SSLUpdater { } await Promise.all(delete_promise_pool); } catch (err) { this.output.error(err); } - return Object.values(status_record_json); + return { + msg_material: Object.values(status_record_json), + send_mail: do_send_mail + } } async sendMsg(title: string, content: string): Promise { diff --git a/src/utils/cache.ts b/src/utils/cache.ts index e763d43..cf0faf9 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -12,7 +12,9 @@ export class Cache { return this.cache[JSON.stringify(key)] } - is_changed(key: any, value: any) { + is_changed(key: any, value: any, strict = true) { + const cache_key = JSON.stringify(key) + if (!Object.prototype.hasOwnProperty.call(this.cache, cache_key)) return strict ? true : false return this._different(this.cache[JSON.stringify(key)], value) } diff --git a/src/utils/timer.ts b/src/utils/timer.ts index 93b357f..b5e118f 100644 --- a/src/utils/timer.ts +++ b/src/utils/timer.ts @@ -1,10 +1,24 @@ type DateLike = number | string | Date +type TimerWatchCallBack = (run_time: number) => T +type TimerWatchOptions = { + /** + * Prevent the callback from being called when the former callback is still running + * @default true + */ + preventWhenRunning?: boolean + /** + * Run the callback instantly when the timer is started + * @default false + */ + InstantlyRun?: boolean +} + export class Timer { private _start_time: number; private _interval: number; - public record: number; + public appoint: number; constructor(start_time: DateLike, interval: number) { @@ -13,39 +27,99 @@ export class Timer { this._interval = interval; if (typeof this._interval !== "number") throw new TypeError("interval must be a number"); else this._interval = Math.abs(this._interval); - // record 只会记录 interval 到达的时间,并且不会自动更新 - // 自动更新需要外部使用 setInterval 不断调用 set_future - if (Timer.now() < this._start_time) this.record = this._start_time; - else this.record = Math.floor((Timer.now() - this._start_time) / this._interval) * this._interval + this._start_time; + + this.nearest_start_time(); + this.appoint = this.future(); } static now() { return Date.now ? Date.now() : new Date().getTime() } + /** + * Update start time to what is most close to now + */ + nearest_start_time() { + let nowTime = Timer.now(); + if (nowTime < this._start_time) return this._start_time; // future, waiting for its coming + let possible = Math.floor((nowTime - this._start_time) / this._interval) * this._interval + this._start_time; + if (possible > nowTime) possible -= this._interval; + return this._start_time = possible; + } + + /** + * Watch the timer and call the callback when the time is up + */ + watch(callback: TimerWatchCallBack, opts: TimerWatchOptions) { + if (!Object.prototype.hasOwnProperty.call(opts, "preventWhenRunning")) { + opts.preventWhenRunning = true; + } + if (!Object.prototype.hasOwnProperty.call(opts, "InstantlyRun")) { + opts.InstantlyRun = false; + } + + function isAsync(fn: Function): fn is TimerWatchCallBack> { + return typeof fn === "function" && fn.constructor.name === "AsyncFunction"; + } + + let system_prevent = false; // prevent when one schedule is running + let user_prevent = false; // only prevent calling the callback + + let force_run = Boolean(opts.InstantlyRun); + const itvid = setInterval(() => { + if (system_prevent) return; + system_prevent = true; + + if (opts.preventWhenRunning && user_prevent) return; + + if (this.is_expired() || force_run) { + force_run = false; + let callingTime = this.appoint; + this.set_future(); + this.nearest_start_time(); + system_prevent = false; + + user_prevent = true; + // The parameter `callback` might be delivered by reference + // That means it might be changed in calls at different calling time + // So we need to check it (async or sync) every time before calling it + if (isAsync(callback)) { + callback(callingTime).finally(() => { + user_prevent = false; + }) + } else if (typeof callback === "function") { + callback(callingTime); + user_prevent = false; + } + } else { + system_prevent = false; + } + }, 1000); + return itvid; + } + next() { - return this.record += this._interval; + return this.appoint + this._interval; } future() { - if (Timer.now() < this._start_time) return this._start_time; - let result = (Math.floor((Timer.now() - this._start_time) / this._interval) + 1) * this._interval + this._start_time; - // ensure no bugs - let now = Timer.now(); - while (result < now) result += this._interval; + let nowTime = Timer.now(); + if (nowTime < this._start_time) return this._start_time; // future, waiting for its coming + let result = Math.floor((nowTime - this._start_time) / this._interval) * this._interval + this._start_time; + while (result <= nowTime) result += this._interval; return result; } is_expired() { - return this.record < Timer.now(); + return this.appoint < Timer.now(); } set_next() { - return this.record = this.next(); + return this.appoint = this.next(); } set_future() { - return this.record = this.future(); + return this.appoint = this.future(); } }