Commit b2ffb25e authored by 王曜嵚 Wang Yaoqin's avatar 王曜嵚 Wang Yaoqin

dev: v2和 v1 成功启动

parent 5d12e416
<template> <template>
<div class='container'> <div class='container'>
<NuxtPage /> <NuxtPage />
<BottomLog></BottomLog>
</div> </div>
</template> </template>
......
...@@ -89,6 +89,9 @@ button{ ...@@ -89,6 +89,9 @@ button{
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
} }
button + button{
margin-left: 10px;
}
button{ button{
background-color: #f6f8fa; background-color: #f6f8fa;
color: #24292f; color: #24292f;
......
<template>
<div class="bottom-log" v-show="userStore.username">
<div class="log__icon">
<svg t="1713863250860" fill="gray" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5129" width="16" height="16"><path d="M96 652.8c-19.2 0-38.4-19.2-38.4-38.4V409.6c0-19.2 19.2-38.4 38.4-38.4s38.4 19.2 38.4 38.4v204.8c0 25.6-12.8 38.4-38.4 38.4z" p-id="5130"></path><path d="M1017.6 512c0-51.2-32-89.6-76.8-102.4V160c0-83.2-64-147.2-147.2-147.2H204.8C121.6 12.8 57.6 76.8 57.6 160v19.2c0 19.2 19.2 38.4 38.4 38.4s38.4-19.2 38.4-38.4v-19.2c0-38.4 32-70.4 70.4-70.4h595.2c38.4 0 70.4 32 70.4 70.4V409.6c-38.4 12.8-76.8 57.6-76.8 102.4 0 51.2 32 89.6 76.8 102.4v249.6c0 38.4-32 70.4-70.4 70.4H204.8c-38.4 0-70.4-32-70.4-70.4v-19.2c0-19.2-19.2-38.4-38.4-38.4s-38.4 19.2-38.4 38.4v19.2c0 83.2 64 147.2 147.2 147.2h595.2c83.2 0 147.2-64 147.2-147.2V620.8c44.8-19.2 70.4-57.6 70.4-108.8z m-108.8 51.2c-25.6 0-51.2-19.2-51.2-51.2 0-25.6 19.2-51.2 51.2-51.2s51.2 19.2 51.2 51.2c-6.4 25.6-25.6 51.2-51.2 51.2z" p-id="5131"></path><path d="M659.2 332.8H345.6c-19.2 0-38.4-12.8-38.4-38.4 0-19.2 19.2-38.4 38.4-38.4h313.6c19.2 0 38.4 19.2 38.4 38.4 0 25.6-19.2 38.4-38.4 38.4zM659.2 550.4H345.6c-19.2 0-38.4-12.8-38.4-38.4 0-19.2 19.2-38.4 38.4-38.4h313.6c19.2 0 38.4 19.2 38.4 38.4 0 25.6-19.2 38.4-38.4 38.4zM659.2 768H345.6c-19.2 0-38.4-19.2-38.4-38.4s19.2-38.4 38.4-38.4h313.6c19.2 0 38.4 19.2 38.4 38.4 0 25.6-19.2 38.4-38.4 38.4z" p-id="5132"></path><path d="M153.6 768H44.8c-19.2 0-38.4-19.2-38.4-38.4s19.2-38.4 38.4-38.4h108.8c19.2 0 38.4 19.2 38.4 38.4 0 25.6-19.2 38.4-38.4 38.4z" p-id="5133"></path><path d="M153.6 332.8H44.8c-25.6 0-38.4-12.8-38.4-38.4 0-19.2 12.8-38.4 38.4-38.4h108.8c19.2 0 38.4 19.2 38.4 38.4 0 25.6-19.2 38.4-38.4 38.4z" p-id="5134"></path></svg>
</div>
<div class="log__info" @click="handleChangePanel">
<span v-html="lastLog"></span>
</div>
<div class="log__panel" v-if="visible">
<div class='header'>
<h3>日志</h3>
<div class="icon-close" @click="handleClosePanel">
<svg t="1713929532584" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4248" width="16" height="16"><path d="M572.16 512l183.466667-183.04a42.666667 42.666667 0 1 0-60.586667-60.586667L512 451.84l-183.04-183.466667a42.666667 42.666667 0 0 0-60.586667 60.586667l183.466667 183.04-183.466667 183.04a42.666667 42.666667 0 0 0 0 60.586667 42.666667 42.666667 0 0 0 60.586667 0l183.04-183.466667 183.04 183.466667a42.666667 42.666667 0 0 0 60.586667 0 42.666667 42.666667 0 0 0 0-60.586667z" p-id="4249"></path></svg>
</div>
</div>
<div class='logs-content'>
<div v-for='item in renderLogs' :key='item'>
<p v-html='item'></p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { io } from 'socket.io-client';
import { useUserStore } from "@/store/user"
const userStore = useUserStore()
const logLevel = ref('progress') // info | progress
const logs = ref([] as string[])
const visible = ref(false)
const lastLog = computed(() => {
return renderLogs.value.slice().pop()
})
const renderLogs = computed(() => {
let str = logs.value.join('')
return str.split('\n').filter(o => !!o.trim()).map((o) => addColorfulTag(o))
})
userStore.$subscribe(function (mutation, state) {
if (state.username) {
initWebsocket(state.username)
}
})
function addColorfulTag(log: string) {
let value = log
.replace(/\[1;34m(.*?)\[m/g, '<span style="color: #5a74f5;">$1</span>')
.replace(/\[1;31m(.*?)\[m/g, '<span style="color: red">$1</span>')
.replace(/\[1m(.*?)\[m/g, '<span style="color: gray">$1</span>')
return value
}
function initWebsocket (username: string) {
const socket = io({
path: '/socket.io',
auth: {
username
}
});
// eslint-disable-next-line no-control-regex
const removeLogPrefix = (log: string) => log.replace(/^\[.*?\]\s/, '').replace(/[\u0000-\u0009]/g, '').replace(/[\u000B-\u001F]/g, '')
const replaceLineFlag = (log: string) => log.replaceAll('\n', '<br />')
socket.on('Log', (log) => {
if (logLevel.value === 'info') {
logs.value.push(removeLogPrefix(log))
} else {
if (!log.startsWith('[info]')) {
logs.value.push(removeLogPrefix(log))
}
}
})
}
function handleChangePanel () {
visible.value = !visible.value
}
function handleClosePanel () {
visible.value = false
}
</script>
<style scoped>
.bottom-log{
position: fixed;
inset: auto 0 0 0;
height: 20px;
background-color: #000;
display: flex;
color: #fff;
}
.log__icon{
width: 20px;
height: 20px;
text-align: center;
padding: 2px 0;
}
.log__info{
flex: 1;
color: #ddd;
font-size: 12px;
line-height: 20px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.log__info:hover{
background-color: #333;
cursor: pointer;
}
.log__panel{
position: fixed;
bottom: 20px;
top: auto;
left: 0;
right: 0;
height: 200px;
border-top: 1px solid #999;
background-color: #fff;
color: #333;
padding: 10px;
display: flex;
flex-direction: column;
}
.log__panel .header{
display: flex;
justify-content: space-between;
}
.log__panel .header .icon-close{
cursor: pointer;
}
.log__panel .header .icon-close:hover{
background-color: #eee;
}
.logs-content{
font-size: 12px;
margin-bottom: 5px;
overflow: auto;
flex: 1;
}
</style>
<template>
<div class="project-card project-v2">
<div class="project-card__info">
<div class="project-card__desc">
<div class="row">
<span title="项目" class="primary">logwire-v1</span>
<span title="状态" class="secondary">
状态:{{ getStatusLabel(info?.status) }}
</span>
<span title="分支" class="secondary branch" @click="handleChangeBranch">分支:{{ info?.branch }}</span>
<span title="目录" class="secondary">目录:/var/logwire-platform</span>
</div>
<div>
<button v-if="info?.status === 'null'" class="primary" @click="handleInstall">初始化</button>
<button v-if="info?.status === 'created' || info?.status === 'compiled'" @click="handleCompile">编译</button>
<button v-if="info?.status === 'compiled'" @click="handleExecute">运行</button>
<button v-if="info?.status === 'running'" @click="handleStop">停止</button>
<div v-else-if="info?.status?.endsWith('ing')" class="loading-icon">
<svg t="1714012359955" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4291" width="16" height="16"><path d="M876.864 782.592c3.264 0 6.272-3.2 6.272-6.656 0-3.456-3.008-6.592-6.272-6.592-3.264 0-6.272 3.2-6.272 6.592 0 3.456 3.008 6.656 6.272 6.656z m-140.544 153.344c2.304 2.432 5.568 3.84 8.768 3.84a12.16 12.16 0 0 0 8.832-3.84 13.76 13.76 0 0 0 0-18.56 12.224 12.224 0 0 0-8.832-3.84 12.16 12.16 0 0 0-8.768 3.84 13.696 13.696 0 0 0 0 18.56zM552.32 1018.24c3.456 3.648 8.32 5.76 13.184 5.76a18.368 18.368 0 0 0 13.184-5.76 20.608 20.608 0 0 0 0-27.968 18.368 18.368 0 0 0-13.184-5.824 18.368 18.368 0 0 0-13.184 5.76 20.608 20.608 0 0 0 0 28.032z m-198.336-5.76c4.608 4.8 11.072 7.68 17.6 7.68a24.448 24.448 0 0 0 17.536-7.68 27.456 27.456 0 0 0 0-37.248 24.448 24.448 0 0 0-17.536-7.68 24.448 24.448 0 0 0-17.6 7.68 27.52 27.52 0 0 0 0 37.184z m-175.68-91.84c5.76 6.08 13.824 9.6 21.952 9.6a30.592 30.592 0 0 0 22.016-9.6 34.368 34.368 0 0 0 0-46.592 30.592 30.592 0 0 0-22.016-9.6 30.592 30.592 0 0 0-21.952 9.6 34.368 34.368 0 0 0 0 46.592z m-121.152-159.36c6.912 7.36 16.64 11.648 26.368 11.648a36.736 36.736 0 0 0 26.432-11.584 41.28 41.28 0 0 0 0-55.936 36.736 36.736 0 0 0-26.432-11.584 36.8 36.8 0 0 0-26.368 11.52 41.28 41.28 0 0 0 0 56zM12.736 564.672a42.88 42.88 0 0 0 30.784 13.44 42.88 42.88 0 0 0 30.784-13.44 48.128 48.128 0 0 0 0-65.216 42.88 42.88 0 0 0-30.72-13.44 42.88 42.88 0 0 0-30.848 13.44 48.128 48.128 0 0 0 0 65.216z m39.808-195.392a48.96 48.96 0 0 0 35.2 15.36 48.96 48.96 0 0 0 35.2-15.36 54.976 54.976 0 0 0 0-74.56 48.96 48.96 0 0 0-35.2-15.424 48.96 48.96 0 0 0-35.2 15.424 54.976 54.976 0 0 0 0 74.56zM168.32 212.48c10.368 11.008 24.96 17.408 39.68 17.408 14.592 0 29.184-6.4 39.552-17.408a61.888 61.888 0 0 0 0-83.84 55.104 55.104 0 0 0-39.616-17.408c-14.656 0-29.248 6.4-39.616 17.408a61.888 61.888 0 0 0 0 83.84zM337.344 124.8c11.52 12.16 27.712 19.264 43.968 19.264 16.256 0 32.448-7.04 43.968-19.264a68.672 68.672 0 0 0 0-93.184 61.248 61.248 0 0 0-43.968-19.264 61.248 61.248 0 0 0-43.968 19.264 68.736 68.736 0 0 0 0 93.184z m189.632-1.088c12.672 13.44 30.528 21.248 48.448 21.248s35.712-7.808 48.384-21.248a75.584 75.584 0 0 0 0-102.464A67.392 67.392 0 0 0 575.36 0c-17.92 0-35.776 7.808-48.448 21.248a75.584 75.584 0 0 0 0 102.464z m173.824 86.592c13.824 14.592 33.28 23.104 52.736 23.104 19.584 0 39.04-8.512 52.8-23.104a82.432 82.432 0 0 0 0-111.744 73.472 73.472 0 0 0-52.8-23.168c-19.52 0-38.912 8.512-52.736 23.168a82.432 82.432 0 0 0 0 111.744z m124.032 158.528c14.976 15.872 36.032 25.088 57.216 25.088 21.12 0 42.24-9.216 57.152-25.088a89.344 89.344 0 0 0 0-121.088 79.616 79.616 0 0 0-57.152-25.088c-21.184 0-42.24 9.216-57.216 25.088a89.344 89.344 0 0 0 0 121.088z m50.432 204.032c16.128 17.088 38.784 27.008 61.632 27.008 22.784 0 45.44-9.92 61.568-27.008a96.256 96.256 0 0 0 0-130.432 85.76 85.76 0 0 0-61.568-27.072c-22.848 0-45.44 9.984-61.632 27.072a96.192 96.192 0 0 0 0 130.432z" fill="#262626" p-id="4292"></path></svg>
</div>
</div>
</div>
</div>
<div class="project-card__settings">
<div class="setting-item" @click="handleOpenVscode">
<svg width="32" height="32" t="1713861440945" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5245"><path d="M746.222933 102.239573l-359.799466 330.820267L185.347413 281.4976 102.2464 329.864533l198.20544 182.132054-198.20544 182.132053 83.101013 48.510293 201.076054-151.558826 359.799466 330.676906 175.527254-85.251413V187.4944z m0 217.57952v384.341334l-255.040853-192.177494z" fill="#2196F3" p-id="5246"></path></svg>
</div>
<div class="setting-item">
<svg width="32" height="32" t="1713861304749" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4253"><path d="M439.264 208a16 16 0 0 0-16 16v67.968a239.744 239.744 0 0 0-46.496 26.896l-58.912-34a16 16 0 0 0-21.856 5.856l-80 138.56a16 16 0 0 0 5.856 21.856l58.896 34a242.624 242.624 0 0 0 0 53.728l-58.88 34a16 16 0 0 0-6.72 20.176l0.848 1.68 80 138.56a16 16 0 0 0 21.856 5.856l58.912-34a239.744 239.744 0 0 0 46.496 26.88V800a16 16 0 0 0 16 16h160a16 16 0 0 0 16-16v-67.968a239.744 239.744 0 0 0 46.512-26.896l58.912 34a16 16 0 0 0 21.856-5.856l80-138.56a16 16 0 0 0-4.288-20.832l-1.568-1.024-58.896-34a242.624 242.624 0 0 0 0-53.728l58.88-34a16 16 0 0 0 6.72-20.176l-0.848-1.68-80-138.56a16 16 0 0 0-21.856-5.856l-58.912 34a239.744 239.744 0 0 0-46.496-26.88V224a16 16 0 0 0-16-16h-160z m32 48h96v67.376l28.8 12.576c13.152 5.76 25.632 12.976 37.184 21.52l25.28 18.688 58.448-33.728 48 83.136-58.368 33.68 3.472 31.2a194.624 194.624 0 0 1 0 43.104l-3.472 31.2 58.368 33.68-48 83.136-58.432-33.728-25.296 18.688c-11.552 8.544-24.032 15.76-37.184 21.52l-28.8 12.576V768h-96v-67.376l-28.784-12.576c-13.152-5.76-25.632-12.976-37.184-21.52l-25.28-18.688-58.448 33.728-48-83.136 58.368-33.68-3.472-31.2a194.624 194.624 0 0 1 0-43.104l3.472-31.2-58.368-33.68 48-83.136 58.432 33.728 25.296-18.688a191.744 191.744 0 0 1 37.184-21.52l28.8-12.576V256z m47.28 144a112 112 0 1 0 0 224 112 112 0 0 0 0-224z m0 48a64 64 0 1 1 0 128 64 64 0 0 1 0-128z" fill="#5A626A" p-id="4254"></path></svg>
</div>
<div style="display: none;">
<button>切换分支</button>
<button>修改配置</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useUserStore } from '~/store/user';
let { pending, data: info } = useFetch('/api/devops/v1/getProjectInfo')
function handleChangeBranch () {
}
function handleOpenVscode() {
window.open(window.location.protocol + '//' + window.location.hostname + ':' + useUserStore().config.port + '/vscode?folder=/var/logwire-platform', '_blank')
}
function getStatusLabel(status?: string) {
const map = new Map([
['null', "未初始化"],
['creating', '创建中'],
['created', "已创建"],
['running', '运行中'],
['stopped', '已停止'],
['compiling', '编译中'],
['compiled', '已编译']
])
return status ? map.get(status) : ''
}
async function handleStop() {
await $fetch('/api/devops/v1/stop', { method: 'post' })
if (info.value) info.value.status = 'compiled'
}
async function handleExecute() {
await $fetch('/api/devops/v1/execute', { method: 'post' })
if (info.value) info.value.status = 'running'
}
async function handleCompile() {
if (info.value) info.value.status = 'compiling'
await $fetch('/api/devops/v1/compile', { method: 'post'})
if (info.value) info.value.status = 'compiled'
}
async function handleInstall() {
if (info.value) info.value.status = 'creating'
try {
await $fetch('/api/devops/v1/install', { method: 'post' })
if (info.value) info.value.status = 'created'
} catch (err) {
info.value!.status = 'null'
}
}
</script>
<style scoped>
.project-card{
border: 1px solid #fff;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 20px;
height: 120px;
display: flex;
overflow: hidden
}
.project-card__info{
flex: 1;
overflow: hidden;
padding: 20px;
}
.project-card__settings{
width: 60px;
border-left: 1px solid #ddd;
display: flex;
flex-direction: column;
}
.project-card__settings > div{
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.project-card__settings > div:nth-child(n+1) {
border-top: 1px solid #ddd;
}
.project-card__desc > .row{
margin-bottom: 10px;
font-size: 14px;
}
.project-card__desc > .row > .primary {
font-size: 14px;
color: #000;
margin-right: 15px;
}
.project-card__desc > .row > .secondary {
font-size: 12px;
color: #999;
margin-right: 15px;
}
.branch:hover{
color: #2e75e6 !important;
cursor: pointer;
}
.setting-item:hover{
background-color: #eee;
cursor: pointer;
}
@keyframes rotate {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
.loading-icon svg{
animation: rotate infinite 3s linear;
}
</style>
\ No newline at end of file
<template>
<div class="project-card project-v2">
<div class="project-card__info">
<div class="project-card__desc">
<div class="row">
<span title="项目" class="primary">logwire-v2</span>
<span title="状态" class="secondary">
状态:{{ getStatusLabel(info?.status) }}
</span>
<span title="分支" class="secondary branch" @click="handleChangeBranch">分支:{{ info?.branch }}</span>
<span title="目录" class="secondary">目录:/var/logwire-backend</span>
</div>
<div>
<button v-if="info?.status === 'null'" class="primary" @click="handleInstall">初始化</button>
<button v-if="info?.status === 'created' || info?.status === 'compiled'" @click="handleCompile">编译</button>
<button v-if="info?.status === 'compiled'" @click="handleExecute">运行</button>
<button v-if="info?.status === 'running'" @click="handleStop">停止</button>
<div v-else-if="info?.status?.endsWith('ing')" class="loading-icon">
<svg t="1714012359955" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4291" width="16" height="16"><path d="M876.864 782.592c3.264 0 6.272-3.2 6.272-6.656 0-3.456-3.008-6.592-6.272-6.592-3.264 0-6.272 3.2-6.272 6.592 0 3.456 3.008 6.656 6.272 6.656z m-140.544 153.344c2.304 2.432 5.568 3.84 8.768 3.84a12.16 12.16 0 0 0 8.832-3.84 13.76 13.76 0 0 0 0-18.56 12.224 12.224 0 0 0-8.832-3.84 12.16 12.16 0 0 0-8.768 3.84 13.696 13.696 0 0 0 0 18.56zM552.32 1018.24c3.456 3.648 8.32 5.76 13.184 5.76a18.368 18.368 0 0 0 13.184-5.76 20.608 20.608 0 0 0 0-27.968 18.368 18.368 0 0 0-13.184-5.824 18.368 18.368 0 0 0-13.184 5.76 20.608 20.608 0 0 0 0 28.032z m-198.336-5.76c4.608 4.8 11.072 7.68 17.6 7.68a24.448 24.448 0 0 0 17.536-7.68 27.456 27.456 0 0 0 0-37.248 24.448 24.448 0 0 0-17.536-7.68 24.448 24.448 0 0 0-17.6 7.68 27.52 27.52 0 0 0 0 37.184z m-175.68-91.84c5.76 6.08 13.824 9.6 21.952 9.6a30.592 30.592 0 0 0 22.016-9.6 34.368 34.368 0 0 0 0-46.592 30.592 30.592 0 0 0-22.016-9.6 30.592 30.592 0 0 0-21.952 9.6 34.368 34.368 0 0 0 0 46.592z m-121.152-159.36c6.912 7.36 16.64 11.648 26.368 11.648a36.736 36.736 0 0 0 26.432-11.584 41.28 41.28 0 0 0 0-55.936 36.736 36.736 0 0 0-26.432-11.584 36.8 36.8 0 0 0-26.368 11.52 41.28 41.28 0 0 0 0 56zM12.736 564.672a42.88 42.88 0 0 0 30.784 13.44 42.88 42.88 0 0 0 30.784-13.44 48.128 48.128 0 0 0 0-65.216 42.88 42.88 0 0 0-30.72-13.44 42.88 42.88 0 0 0-30.848 13.44 48.128 48.128 0 0 0 0 65.216z m39.808-195.392a48.96 48.96 0 0 0 35.2 15.36 48.96 48.96 0 0 0 35.2-15.36 54.976 54.976 0 0 0 0-74.56 48.96 48.96 0 0 0-35.2-15.424 48.96 48.96 0 0 0-35.2 15.424 54.976 54.976 0 0 0 0 74.56zM168.32 212.48c10.368 11.008 24.96 17.408 39.68 17.408 14.592 0 29.184-6.4 39.552-17.408a61.888 61.888 0 0 0 0-83.84 55.104 55.104 0 0 0-39.616-17.408c-14.656 0-29.248 6.4-39.616 17.408a61.888 61.888 0 0 0 0 83.84zM337.344 124.8c11.52 12.16 27.712 19.264 43.968 19.264 16.256 0 32.448-7.04 43.968-19.264a68.672 68.672 0 0 0 0-93.184 61.248 61.248 0 0 0-43.968-19.264 61.248 61.248 0 0 0-43.968 19.264 68.736 68.736 0 0 0 0 93.184z m189.632-1.088c12.672 13.44 30.528 21.248 48.448 21.248s35.712-7.808 48.384-21.248a75.584 75.584 0 0 0 0-102.464A67.392 67.392 0 0 0 575.36 0c-17.92 0-35.776 7.808-48.448 21.248a75.584 75.584 0 0 0 0 102.464z m173.824 86.592c13.824 14.592 33.28 23.104 52.736 23.104 19.584 0 39.04-8.512 52.8-23.104a82.432 82.432 0 0 0 0-111.744 73.472 73.472 0 0 0-52.8-23.168c-19.52 0-38.912 8.512-52.736 23.168a82.432 82.432 0 0 0 0 111.744z m124.032 158.528c14.976 15.872 36.032 25.088 57.216 25.088 21.12 0 42.24-9.216 57.152-25.088a89.344 89.344 0 0 0 0-121.088 79.616 79.616 0 0 0-57.152-25.088c-21.184 0-42.24 9.216-57.216 25.088a89.344 89.344 0 0 0 0 121.088z m50.432 204.032c16.128 17.088 38.784 27.008 61.632 27.008 22.784 0 45.44-9.92 61.568-27.008a96.256 96.256 0 0 0 0-130.432 85.76 85.76 0 0 0-61.568-27.072c-22.848 0-45.44 9.984-61.632 27.072a96.192 96.192 0 0 0 0 130.432z" fill="#262626" p-id="4292"></path></svg>
</div>
</div>
</div>
</div>
<div class="project-card__settings">
<div class="setting-item" @click="handleOpenVscode">
<svg width="32" height="32" t="1713861440945" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5245"><path d="M746.222933 102.239573l-359.799466 330.820267L185.347413 281.4976 102.2464 329.864533l198.20544 182.132054-198.20544 182.132053 83.101013 48.510293 201.076054-151.558826 359.799466 330.676906 175.527254-85.251413V187.4944z m0 217.57952v384.341334l-255.040853-192.177494z" fill="#2196F3" p-id="5246"></path></svg>
</div>
<div class="setting-item">
<svg width="32" height="32" t="1713861304749" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4253"><path d="M439.264 208a16 16 0 0 0-16 16v67.968a239.744 239.744 0 0 0-46.496 26.896l-58.912-34a16 16 0 0 0-21.856 5.856l-80 138.56a16 16 0 0 0 5.856 21.856l58.896 34a242.624 242.624 0 0 0 0 53.728l-58.88 34a16 16 0 0 0-6.72 20.176l0.848 1.68 80 138.56a16 16 0 0 0 21.856 5.856l58.912-34a239.744 239.744 0 0 0 46.496 26.88V800a16 16 0 0 0 16 16h160a16 16 0 0 0 16-16v-67.968a239.744 239.744 0 0 0 46.512-26.896l58.912 34a16 16 0 0 0 21.856-5.856l80-138.56a16 16 0 0 0-4.288-20.832l-1.568-1.024-58.896-34a242.624 242.624 0 0 0 0-53.728l58.88-34a16 16 0 0 0 6.72-20.176l-0.848-1.68-80-138.56a16 16 0 0 0-21.856-5.856l-58.912 34a239.744 239.744 0 0 0-46.496-26.88V224a16 16 0 0 0-16-16h-160z m32 48h96v67.376l28.8 12.576c13.152 5.76 25.632 12.976 37.184 21.52l25.28 18.688 58.448-33.728 48 83.136-58.368 33.68 3.472 31.2a194.624 194.624 0 0 1 0 43.104l-3.472 31.2 58.368 33.68-48 83.136-58.432-33.728-25.296 18.688c-11.552 8.544-24.032 15.76-37.184 21.52l-28.8 12.576V768h-96v-67.376l-28.784-12.576c-13.152-5.76-25.632-12.976-37.184-21.52l-25.28-18.688-58.448 33.728-48-83.136 58.368-33.68-3.472-31.2a194.624 194.624 0 0 1 0-43.104l3.472-31.2-58.368-33.68 48-83.136 58.432 33.728 25.296-18.688a191.744 191.744 0 0 1 37.184-21.52l28.8-12.576V256z m47.28 144a112 112 0 1 0 0 224 112 112 0 0 0 0-224z m0 48a64 64 0 1 1 0 128 64 64 0 0 1 0-128z" fill="#5A626A" p-id="4254"></path></svg>
</div>
<div style="display: none;">
<button>切换分支</button>
<button>修改配置</button>
<button>打包微信压缩文件</button>
<button>调试</button>
<button>打开 Vscode</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useUserStore } from '~/store/user';
let { pending, data: info } = useFetch('/api/devops/v2/getProjectInfo')
function handleChangeBranch () {
}
function handleOpenVscode() {
window.open(window.location.protocol + '//' + window.location.hostname + ':' + useUserStore().config.port + '/vscode?folder=/var/logwire-backend', '_blank')
}
function getStatusLabel(status?: string) {
const map = new Map([
['null', "未初始化"],
['creating', '创建中'],
['created', "已创建"],
['running', '运行中'],
['stopped', '已停止'],
['compiling', '编译中'],
['compiled', '已编译']
])
return status ? map.get(status) : ''
}
async function handleDebug() {
await $fetch('/api/devops/v2/debug', { method: 'post'})
if (info.value) info.value.status = 'running'
}
async function handleZip() {
await $fetch('/api/backend/downloadWeApp', { method: 'post' })
if (info.value) info.value.status = 'running'
}
async function handleStop() {
await $fetch('/api/devops/v2/stop', { method: 'post' })
if (info.value) info.value.status = 'compiled'
}
async function handleExecute() {
await $fetch('/api/devops/v2/execute', { method: 'post' })
if (info.value) info.value.status = 'running'
}
async function handleCompile() {
if (info.value) info.value.status = 'compiling'
await $fetch('/api/devops/v2/compile', { method: 'post'})
if (info.value) info.value.status = 'compiled'
}
async function handleInstall() {
if (info.value) info.value.status = 'creating'
try {
await $fetch('/api/devops/v2/install', { method: 'post' })
if (info.value) info.value.status = 'created'
} catch (err) {
info.value!.status = 'null'
}
}
</script>
<style scoped>
.project-card{
border: 1px solid #fff;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 20px;
height: 120px;
display: flex;
overflow: hidden
}
.project-card__info{
flex: 1;
overflow: hidden;
padding: 20px;
}
.project-card__settings{
width: 60px;
border-left: 1px solid #ddd;
display: flex;
flex-direction: column;
}
.project-card__settings > div{
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.project-card__settings > div:nth-child(n+1) {
border-top: 1px solid #ddd;
}
.project-card__desc > .row{
margin-bottom: 10px;
font-size: 14px;
}
.project-card__desc > .row > .primary {
font-size: 14px;
color: #000;
margin-right: 15px;
}
.project-card__desc > .row > .secondary {
font-size: 12px;
color: #999;
margin-right: 15px;
}
.branch:hover{
color: #2e75e6 !important;
cursor: pointer;
}
.setting-item:hover{
background-color: #eee;
cursor: pointer;
}
@keyframes rotate {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
.loading-icon svg{
animation: rotate infinite 3s linear;
}
</style>
/**
* useWaitingFetch
* 等待一定时间后再返回,避免接口时间太短引起页面渲染时的一闪而过的情况
*/
import nitropack from 'nitropack'
export default async function<R extends nitropack.NitroFetchRequest> (
url: R,
options: nitropack.NitroFetchOptions<R> & { wait: number }
): ReturnType<nitropack.$Fetch> {
const startTimestamp = +new Date()
const result = await $fetch(url, options)
const endTimestamp = +new Date()
const gap = endTimestamp - startTimestamp
if (gap < options.wait) {
await sleep(options.wait - gap)
}
return result
}
\ No newline at end of file
...@@ -127,7 +127,15 @@ function getCurrentBranchV1() { ...@@ -127,7 +127,15 @@ function getCurrentBranchV1() {
} }
function handleGenerate() { function handleGenerate() {
$fetch('/api/git/generateSsh', { method: 'post', body: { email: form.value.email } }).then(res => { $fetch('/api/git/generateSsh', { method: 'post', body: { email: form.value.email } }).then(res => {
alert('生成 ssh key: [' + res + '], 请放入 gitlab 的 SSH Key 配置中') if (res) {
const input = document.createElement('input')
input.value = res
document.body.appendChild(input)
input.select()
document.execCommand('copy')
alert('已拷贝 SSH Key, 请前往 Gitlab 更新个人 SSH Key 配置')
document.body.removeChild(input)
}
}) })
} }
async function handleFetch(platform: 'v1' | 'v2') { async function handleFetch(platform: 'v1' | 'v2') {
......
<template> <template>
<NuxtLayout> <NuxtLayout>
<div class='content'> <div class='content'>
<div class='header container'> <ProjectV2></ProjectV2>
<div class='row'> <ProjectV1></ProjectV1>
<div class='avatar'>
{{ username.substring(0, 1) }}
</div>
<p>
{{ username }}
<span v-if='port !== 0'>[容器端口: <b>{{ port }}</b>]</span>
</p>
</div>
<div class='row'>
<span style="display: inline-block; width: 150px;">V2: {{ getStatusLabel(status) }}</span>
<button v-if='status === "null" || status === "creating"' :disabled='loading'
@click='handleInstall'>初始化</button>
<button v-if='status === "created" || status === "stopped" || status === "compiling"' :disabled='loading'
@click='handleCompile'>编译</button>
<button v-if='status === "created" || status === "stopped" || status === "compiling"' :disabled='loading'
@click='handleExecute'>运行</button>
<button v-if='status === "created" || status === "stopped" || status === "compiling"' :disabled='loading'
@click='handleDebug'>调试</button>
<button v-else-if='status === "running"' :disabled='loading' @click='handleStop'>停止</button>
<button v-if="status === 'running'" :disabled="loading" @click="handleZip">打包微信压缩文件</button>
</div>
<div class='row'>
<span style="display: inline-block; width: 150px;">V1: {{ getStatusLabel(statusV1) }}</span>
<button v-if='statusV1 === "null" || statusV1 === "creating"' :disabled='loading'
@click='handleInstallV1'>初始化</button>
<button v-if='statusV1 === "created" || statusV1 === "stopped" || statusV1 === "compiling"' :disabled='loading'
@click='handleCompileV1'>编译</button>
<button v-if='statusV1 === "created" || statusV1 === "stopped" || statusV1 === "compiling"' :disabled='loading'
@click='handleExecuteV1'>运行</button>
<button v-else-if='statusV1 === "running"' :disabled='loading' @click='handleStopV1'>停止</button>
</div>
</div>
<div class='main'>
<div class='tools container'>
<div class='row'>
常用工具
</div>
<div class='row'>
<div class='tool' @click='handleOpenVscode'>
<svg t="1676878506818" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="3085" width="16" height="16">
<path
d="M0 0m184.32 0l655.36 0q184.32 0 184.32 184.32l0 655.36q0 184.32-184.32 184.32l-655.36 0q-184.32 0-184.32-184.32l0-655.36q0-184.32 184.32-184.32Z"
fill="#FFFFFF" p-id="3086"></path>
<path
d="M708.82304 161.9968l137.46176 67.24608c20.81792 10.62912 23.7056 18.28864 23.7056 29.29664l0.60416 506.23488c0 13.50656-7.30112 18.82112-27.42272 30.1568l-133.8368 67.08224c-5.5808 2.60096-10.04544 5.76512-43.47904 5.76512 0 0 28.93824 0.24576 28.93824-22.87616v-662.87616a25.15968 25.15968 0 0 0-24.13568-25.76384c29.6448 0 38.16448 5.7344 38.16448 5.7344z"
fill="#4B9AE9" p-id="3087"></path>
<path
d="M163.34848 613.02784a29.21472 29.21472 0 0 0 0 38.98368s35.11296 32.62464 45.4656 41.984 25.7024 5.12 35.64544-2.37568 450.29376-343.64416 450.29376-343.64416v-165.94944a25.06752 25.06752 0 0 0-24.13568-25.76384 18.11456 18.11456 0 0 0-12.3392 5.376z"
fill="#2A63A4" p-id="3088"></path>
<path
d="M208.92672 331.74528a23.17312 23.17312 0 0 1 32.45056 0l453.4272 343.61344v169.55392c0 23.25504-28.9792 22.8352-28.9792 22.8352a17.00864 17.00864 0 0 1-10.11712-5.53984l-495.77984-454.13376a22.31296 22.31296 0 0 1 0-31.54944z"
fill="#3478C6" p-id="3089"></path>
</svg>
<p>Vscode Web V2</p>
</div>
<div class='tool' @click='handleOpenVscodeV1'>
<svg t="1676878506818" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="3085" width="16" height="16">
<path
d="M0 0m184.32 0l655.36 0q184.32 0 184.32 184.32l0 655.36q0 184.32-184.32 184.32l-655.36 0q-184.32 0-184.32-184.32l0-655.36q0-184.32 184.32-184.32Z"
fill="#FFFFFF" p-id="3086"></path>
<path
d="M708.82304 161.9968l137.46176 67.24608c20.81792 10.62912 23.7056 18.28864 23.7056 29.29664l0.60416 506.23488c0 13.50656-7.30112 18.82112-27.42272 30.1568l-133.8368 67.08224c-5.5808 2.60096-10.04544 5.76512-43.47904 5.76512 0 0 28.93824 0.24576 28.93824-22.87616v-662.87616a25.15968 25.15968 0 0 0-24.13568-25.76384c29.6448 0 38.16448 5.7344 38.16448 5.7344z"
fill="#4B9AE9" p-id="3087"></path>
<path
d="M163.34848 613.02784a29.21472 29.21472 0 0 0 0 38.98368s35.11296 32.62464 45.4656 41.984 25.7024 5.12 35.64544-2.37568 450.29376-343.64416 450.29376-343.64416v-165.94944a25.06752 25.06752 0 0 0-24.13568-25.76384 18.11456 18.11456 0 0 0-12.3392 5.376z"
fill="#2A63A4" p-id="3088"></path>
<path
d="M208.92672 331.74528a23.17312 23.17312 0 0 1 32.45056 0l453.4272 343.61344v169.55392c0 23.25504-28.9792 22.8352-28.9792 22.8352a17.00864 17.00864 0 0 1-10.11712-5.53984l-495.77984-454.13376a22.31296 22.31296 0 0 1 0-31.54944z"
fill="#3478C6" p-id="3089"></path>
</svg>
<p>Vscode Web V1</p>
</div>
</div>
</div>
<div class='logs container'>
<div class='row'>
日志
</div>
<div class='row logs-content' style='overflow: auto;flex: 1;'>
<div v-for='item in renderLogs' :key='item'>
<p v-html='item'></p>
</div>
</div>
</div>
</div>
</div> </div>
</NuxtLayout> </NuxtLayout>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { io } from 'socket.io-client'; import { useUserStore } from '~/store/user';
const username = ref('') const username = ref('')
...@@ -107,80 +19,15 @@ const logLevel = ref('progress') // info | progress ...@@ -107,80 +19,15 @@ const logLevel = ref('progress') // info | progress
const logs = ref([] as string[]) const logs = ref([] as string[])
const port = ref(0) const port = ref(0)
const renderLogs = computed(() => {
let str = logs.value.join('')
return str.split('\n').map((o: string) => addColorfulTag(o))
})
onMounted(async () => { onMounted(async () => {
// await checkAndRestartDocker()
await getUserInfomation() await getUserInfomation()
initWebsocket()
}) })
function getStatusLabel(status: string) {
return status === 'null' ? "未初始化"
: status === 'creating' ? '创建中'
: status === 'created' ? "已创建"
: status === 'running' ? '运行中'
: status === 'stopped' ? '已停止'
: status === 'compiling' ? '编译中'
: ''
}
function checkAndRestartDocker() {
return $fetch('/api/devops/common/checkAndRestartDocker', { method: 'post' })
}
function addColorfulTag(log: string) {
let value = log
.replace(/\[1;34m(.*?)\[m/g, '<span style="color: #5a74f5;">$1</span>')
.replace(/\[1;31m(.*?)\[m/g, '<span style="color: red">$1</span>')
.replace(/\[1m(.*?)\[m/g, '<span style="color: gray">$1</span>')
return value
}
function getUserInfomation() {
return $fetch('/api/user/getProjectInfo').then((res: any) => {
status.value = res.status || 'null'
statusV1.value = res['v1-status'] || 'null'
port.value = res['node-port']
username.value = res.username
if (status.value === 'creating' || status.value === 'compiling') {
loading.value = true
}
if (statusV1.value === 'creating' || statusV1.value === 'compiling') {
loading.value = true
}
})
}
function initWebsocket () {
const socket = io({
path: '/socket.io',
auth: {
username: username.value
}
});
// eslint-disable-next-line no-control-regex
const removeLogPrefix = (log: string) => log.replace(/^\[.*?\]\s/, '').replace(/[\u0000-\u0009]/g, '').replace(/[\u000B-\u001F]/g, '')
const replaceLineFlag = (log: string) => log.replaceAll('\n', '<br />') async function getUserInfomation() {
socket.on('Log', (log) => { const res = await $fetch('/api/user/getProjectInfo')
if (logLevel.value === 'info') { if (res) {
logs.value.push(replaceLineFlag(removeLogPrefix(log))) useUserStore().setUserConfig(res.username, { port: res["port"] })
} else { }
if (!log.startsWith('[info]')) {
logs.value.push(replaceLineFlag(removeLogPrefix(log)))
}
}
})
}
function handleInstall() {
if (loading.value) return
logs.value = []
loading.value = true
status.value = 'creating'
$fetch('/api/devops/v2/install', { method: 'post' })
.then(getUserInfomation, getUserInfomation)
.finally(() => {
loading.value = false
})
} }
function handleInstallV1() { function handleInstallV1() {
if (loading.value) return if (loading.value) return
...@@ -193,17 +40,6 @@ function handleInstallV1() { ...@@ -193,17 +40,6 @@ function handleInstallV1() {
loading.value = false loading.value = false
}) })
} }
function handleCompile() {
if (loading.value) return
loading.value = true
logs.value = []
resetV1()
$fetch('/api/devops/v2/compile', { method: 'post'}).then(() => {
return getUserInfomation()
}).finally(() => {
loading.value = false
})
}
function handleCompileV1() { function handleCompileV1() {
if (loading.value) return if (loading.value) return
loading.value = true loading.value = true
...@@ -215,17 +51,6 @@ function handleCompileV1() { ...@@ -215,17 +51,6 @@ function handleCompileV1() {
loading.value = false loading.value = false
}) })
} }
function handleExecute() {
if (loading.value) return
loading.value = true
logs.value = []
resetV1()
$fetch('/api/devops/v2/execute', { method: 'post' }).then(() => {
return getUserInfomation()
}).finally(() => {
loading.value = false
})
}
function handleExecuteV1() { function handleExecuteV1() {
if (loading.value) return if (loading.value) return
loading.value = true loading.value = true
...@@ -237,16 +62,6 @@ function handleExecuteV1() { ...@@ -237,16 +62,6 @@ function handleExecuteV1() {
loading.value = false loading.value = false
}) })
} }
function handleStop() {
if (loading.value) return
logs.value = []
loading.value = true
$fetch('/api/devops/v2/stop', { method: 'post' }).then(() => {
return getUserInfomation()
}).finally(() => {
loading.value = false
})
}
function handleStopV1() { function handleStopV1() {
if (loading.value) return if (loading.value) return
logs.value = [] logs.value = []
...@@ -257,30 +72,9 @@ function handleStopV1() { ...@@ -257,30 +72,9 @@ function handleStopV1() {
loading.value = false loading.value = false
}) })
} }
function handleZip() {
if (loading.value) return
loading.value = true
logs.value = []
$fetch('/api/backend/downloadWeApp', { method: 'post' }).finally(() => {
loading.value = false
})
}
function handleOpenVscode() {
window.open(window.location.protocol + '//' + window.location.hostname + ':' + port + '/vscode?folder=/var/logwire-backend', '_blank')
}
function handleOpenVscodeV1() { function handleOpenVscodeV1() {
window.open(window.location.protocol + '//' + window.location.hostname + ':' + port + '/vscode?folder=/var/logwire-platform', '_blank') window.open(window.location.protocol + '//' + window.location.hostname + ':' + port + '/vscode?folder=/var/logwire-platform', '_blank')
} }
function handleDebug() {
if (loading.value) return
loading.value = true
logs.value = []
$fetch('/api/devops/v2/debug', { method: 'post'}).then(() => {
return getUserInfomation()
}).finally(() => {
loading.value = false
})
}
function resetV1() { function resetV1() {
statusV1.value = 'created' statusV1.value = 'created'
} }
...@@ -290,60 +84,8 @@ function resetV2() { ...@@ -290,60 +84,8 @@ function resetV2() {
</script> </script>
<style scoped> <style scoped>
.tool {
display: inline-block;
margin-right: 50px;
margin-top: 10px;
cursor: pointer;
}
.tool svg {
width: 100px;
height: 100px;
}
.content { .content {
display: flex; padding: 20px;
flex-direction: column; box-sizing: border-box;
}
.content .container .row {
border-bottom: 1px solid #BBB;
overflow: hidden;
}
.content .container .row:nth-last-of-type(1) {
border-bottom: none;
}
.content .header.container .avatar {
float: left;
width: 56px;
height: 56px;
line-height: 50px;
text-align: center;
font-size: 56px;
background-color: rgb(158, 199, 143);
color: #eee;
border-radius: 50%;
margin-top: 15px;
margin-right: 10px;
}
.content>.main {
display: flex;
overflow: hidden;
flex: 1;
}
.content>.main>.tools.container {
width: 372px;
}
.content>.main>.logs {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
} }
</style> </style>
\ No newline at end of file
...@@ -33,8 +33,12 @@ function handleLogin () { ...@@ -33,8 +33,12 @@ function handleLogin () {
method: 'post', method: 'post',
body: { username: username.value } body: { username: username.value }
}).then((value) => { }).then((value) => {
userStore.setUserName(username.value) if (value) {
navigateTo('/home') userStore.setUserConfig(username.value, { port: value["port"] })
navigateTo('/home')
} else {
navigateTo('/prepare')
}
}) })
} }
</script> </script>
......
<template>
<div class="prepare">
<span>准备数据库服务中......</span>
</div>
</template>
<script setup lang="ts">
import { useUserStore } from '~/store/user';
const { data } = useLazyFetch('/api/user/prepare', { method: 'post' })
watch(data, function (value) {
if (value) {
useUserStore().setUserConfig(value.username, { port: value['port'] })
navigateTo('/home')
}
})
</script>
<style scoped>
.prepare{
height: 100vh;
width: 100vw;
text-align: center;
line-height: 100vh;
}
span{
font-weight: bold;
}
</style>
\ No newline at end of file
...@@ -3,48 +3,13 @@ ...@@ -3,48 +3,13 @@
<div class='content'> <div class='content'>
<div class='container'> <div class='container'>
<form class='form' ref='form'> <form class='form' ref='form'>
<div class='form-group'>
<div class='form-group__label'>
<label for="none">
<span>postgresV1</span>
<button class='primary' @click='handleSaveConfig("postgresV1")'>保存</button>
</label>
<p>postgresV1 数据库的配置信息</p>
</div>
<div class='form-group__body'>
<div class='form-group__item'><span>数据库</span><span><input v-model='form.postgresV1.database' /></span>
</div>
<div class='form-group__item'><span>用户名</span><span><input v-model='form.postgresV1.username' /></span>
</div>
<div class='form-group__item'><span>密码</span><span><input v-model='form.postgresV1.password' /></span>
</div>
<div class='form-group__item'><span>服务 IP</span><span><input v-model='form.postgresV1.ip' /></span></div>
<div class='form-group__item'><span>服务 PORT</span><span><input v-model='form.postgresV1.port' /></span>
</div>
</div>
</div>
<div class='form-group'>
<div class='form-group__label'>
<label for="none">
<span>redisV1</span>
<button class='primary' @click='handleSaveConfig("redisV1")'>保存</button>
</label>
<p>redisV1 数据库的配置信息</p>
</div>
<div class='form-group__body'>
<div class='form-group__item'><span>服务 IP</span><span><input v-model='form.redisV1.ip' /></span></div>
<div class='form-group__item'><span>服务 PORT</span><span><input v-model='form.redisV1.port' /></span></div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div class="form-group__label"> <div class="form-group__label">
<label for="none"> <label for="none">
<span>其他配置</span> <span>其他配置</span>
<div> <div>
<button type="button" style="margin-right: 10px;" @click.stop="handleAddCustomConfig">新增</button> <button type="button" style="margin-right: 10px;" @click.stop="handleAddCustomConfig">新增</button>
<button type="button" class="primary" @click="handleSaveConfig('serverPropertiesV1')">保存</button> <button type="button" class="primary" @click="handleSaveConfig('serverProperties')">保存</button>
</div> </div>
</label> </label>
</div> </div>
...@@ -58,7 +23,7 @@ ...@@ -58,7 +23,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="item in form.serverPropertiesV1" :key="item.id"> <tr v-for="item in form.serverProperties" :key="item.id">
<td><input type="text" v-model="item.key"></td> <td><input type="text" v-model="item.key"></td>
<td><input type="text" v-model="item.value"></td> <td><input type="text" v-model="item.value"></td>
<td><button @click="handleDeleteServerProperty(item)">删除</button></td> <td><button @click="handleDeleteServerProperty(item)">删除</button></td>
...@@ -75,29 +40,7 @@ ...@@ -75,29 +40,7 @@
<script lang="ts" setup> <script lang="ts" setup>
const form = ref<Record<string, any>>({ const form = ref<Record<string, any>>({
postgresV1: { serverProperties: []
visible: false,
username: 'postgres',
password: 'postgres',
ip: '192.168.0.4',
port: '25556',
database: 'logwirev1',
schema: 'library'
},
redisV1: {
visible: false,
ip: '192.168.0.4',
port: '25557'
},
tenantsV1: {
visible: false,
id: 'wongyaqi',
port: 8080
},
debug: {
host: '192.168.0.190'
},
serverPropertiesV1: []
}) })
onMounted(() => { onMounted(() => {
...@@ -114,8 +57,8 @@ function getData() { ...@@ -114,8 +57,8 @@ function getData() {
}) })
} }
function handleSaveConfig(key: string) { function handleSaveConfig(key: string) {
let value = form.value[key] const value = JSON.stringify(form.value[key])
$fetch('/api/config/setUserSetting', { method: 'post', body: { key, value }}).then(() => { $fetch('/api/config/setUserSetting', { method: 'post', body: { table: 'v1', key, value }}).then(() => {
alert('保存成功') alert('保存成功')
}).catch(() => { }).catch(() => {
alert('保存失败!!!') alert('保存失败!!!')
...@@ -123,15 +66,15 @@ function handleSaveConfig(key: string) { ...@@ -123,15 +66,15 @@ function handleSaveConfig(key: string) {
} }
function handleAddCustomConfig() { function handleAddCustomConfig() {
form.value.serverPropertiesV1.push({ form.value.serverProperties.push({
id: Math.random(), id: Math.random(),
key: '', key: '',
value: '' value: ''
}) })
} }
function handleDeleteServerProperty(item: any) { function handleDeleteServerProperty(item: any) {
const index = form.value.serverPropertiesV1.indexOf(item) const index = form.value.serverProperties.indexOf(item)
form.value.serverPropertiesV1.splice(index, 1) form.value.serverProperties.splice(index, 1)
} }
</script> </script>
......
...@@ -3,64 +3,6 @@ ...@@ -3,64 +3,6 @@
<div class='content'> <div class='content'>
<div class='container'> <div class='container'>
<form class='form' @submit.prevent> <form class='form' @submit.prevent>
<div class='form-group'>
<div class='form-group__label'>
<label for="none">
<span>Postgres</span>
<button class='primary' @click='handleSaveConfig("postgres")'>保存</button>
</label>
<p>Postgres 数据库的配置信息</p>
</div>
<div class='form-group__body'>
<div class='form-group__item'><span>用户名</span><span><input v-model='form.postgres.username' /></span></div>
<div class='form-group__item'><span>密码</span><span><input v-model='form.postgres.password' /></span></div>
<div class='form-group__item'><span>服务 IP</span><span><input v-model='form.postgres.ip' /></span></div>
<div class='form-group__item'><span>服务 PORT</span><span><input v-model='form.postgres.port' /></span></div>
</div>
</div>
<div class='form-group'>
<div class='form-group__label'>
<label for="none">
<span>Redis</span>
<button class='primary' @click='handleSaveConfig("redis")'>保存</button>
</label>
<p>Redis 数据库的配置信息</p>
</div>
<div class='form-group__body'>
<div class='form-group__item'><span>服务 IP</span><span><input v-model='form.redis.ip' /></span></div>
<div class='form-group__item'><span>服务 PORT</span><span><input v-model='form.redis.port' /></span></div>
</div>
</div>
<div class='form-group'>
<div class='form-group__label'>
<label for="none">
<span>Zookeeper</span>
<button class='primary' @click='handleSaveConfig("zookeeper")'>保存</button>
</label>
<p>Zookeeper 的配置信息</p>
</div>
<div class='form-group__body'>
<div class='form-group__item'><span>服务 IP</span><span><input v-model='form.zookeeper.ip' /></span></div>
<div class='form-group__item'><span>服务 PORT</span><span><input v-model='form.zookeeper.port' /></span></div>
</div>
</div>
<div class='form-group'>
<div class='form-group__label'>
<label for="none">
<span>Rocket MQ</span>
<button class='primary' @click='handleSaveConfig("rocketmq")'>保存</button>
</label>
<p>Rocket MQ 的配置信息</p>
</div>
<div class='form-group__body'>
<div class='form-group__item'><span>服务 IP</span><span><input v-model='form.rocketmq.ip' /></span></div>
<div class='form-group__item'><span>服务 PORT</span><span><input v-model='form.rocketmq.port' /></span></div>
</div>
</div>
<div class='form-group'> <div class='form-group'>
<div class='form-group__label'> <div class='form-group__label'>
<label for="none"> <label for="none">
...@@ -171,7 +113,7 @@ onMounted(() => { ...@@ -171,7 +113,7 @@ onMounted(() => {
}) })
function getData () { function getData () {
$fetch('/api/user/getProjectInfo').then((res: any) => { $fetch('/api/devops/v2/getProjectInfo').then((res: any) => {
Object.keys(form.value).forEach(key => { Object.keys(form.value).forEach(key => {
if (res[key]) { if (res[key]) {
form.value[key] = res[key] form.value[key] = res[key]
...@@ -180,9 +122,8 @@ function getData () { ...@@ -180,9 +122,8 @@ function getData () {
}) })
} }
function handleSaveConfig (key: string) { function handleSaveConfig (key: string) {
const value = form.value[key] const value = JSON.stringify(form.value[key])
console.log(key, value) $fetch('/api/user/setUserSetting', { method: 'post', body: { table: 'v2', key, value } }).then(() => {
$fetch('/api/user/setUserSetting', { method: 'post', body: { key, value } }).then(() => {
alert('保存成功') alert('保存成功')
}).catch(() => { }).catch(() => {
alert('保存失败!!!') alert('保存失败!!!')
......
{
"postgres": {
"visible": false,
"username": "postgres",
"password": "postgres",
"ip": "192.168.0.4",
"port": "25556",
"database": "login",
"schema": "library"
},
"redis": {
"visible": false,
"ip": "192.168.0.4",
"port": "25557"
},
"zookeeper": {
"visible": false,
"ip": "192.168.0.4",
"port": "2182"
},
"rocketmq": {
"visible": false,
"ip": "192.168.0.4",
"port": "9876"
},
"tenants": {
"visible": false,
"id": "login",
"host": "a.test.com:23339,a.test.com:23340",
"database-schema": "library",
"primary-namespace": "library"
},
"postgresV1": {
"visible": false,
"username": "postgres",
"password": "postgres",
"ip": "192.168.0.4",
"port": "25556",
"database": "logwirev2",
"schema": "library"
},
"redisV1": {
"visible": false,
"ip": "192.168.0.4",
"port": "25557"
},
"tenantsV1": {
"visible": false,
"id": "wongyaqi",
"port": 8080
},
"debug": {
"host": "192.168.1.94"
},
"node-port": 30003,
"username": "login"
}
\ No newline at end of file
{
"postgres": {
"visible": false,
"username": "postgres",
"password": "postgres",
"ip": "localhost",
"port": "25556",
"database": "wyq",
"schema": "library"
},
"redis": {
"visible": false,
"ip": "localhost",
"port": "25557"
},
"zookeeper": {
"visible": false,
"ip": "localhost",
"port": "2182"
},
"rocketmq": {
"visible": false,
"ip": "localhost",
"port": "9876"
},
"tenants": {
"visible": false,
"id": "wyq",
"host": "a.test.com:23333,a.test.com:23334",
"database-schema": "library",
"primary-namespace": "library"
},
"postgresV1": {
"visible": false,
"username": "postgres",
"password": "postgres",
"ip": "192.168.0.4",
"port": "25556",
"database": "logwirev2",
"schema": "library"
},
"redisV1": {
"visible": false,
"ip": "192.168.0.4",
"port": "25557"
},
"tenantsV1": {
"visible": false,
"id": "wongyaqi",
"port": 8080
},
"debug": {
"host": "192.168.1.94"
},
"node-port": 30000,
"username": "wyq",
"status": "created",
"InstallSteps": [
"创建 node 容器",
"检查 SSH Key",
"创建 postgres 容器",
"创建 redis 容器",
"创建 zookeeper 容器",
"创建 rocketmq serv 容器",
"创建 rocketmq broker 容器",
"创建 node 容器",
"检查 SSH Key",
"创建 postgres 容器",
"创建 redis 容器",
"创建 zookeeper 容器",
"创建 rocketmq serv 容器",
"创建 rocketmq broker 容器",
"克隆仓库"
]
}
\ No newline at end of file
-- Database: backend_helper
-- DROP DATABASE IF EXISTS backend_helper;
CREATE DATABASE backend_helper
WITH
OWNER = postgres
ENCODING = 'UTF8'
LC_COLLATE = 'en_US.utf8'
LC_CTYPE = 'en_US.utf8'
TABLESPACE = pg_default
CONNECTION LIMIT = -1
IS_TEMPLATE = False;
\ No newline at end of file
-- Table: public.user
-- DROP TABLE IF EXISTS public."user";
CREATE TABLE IF NOT EXISTS public."user"
(
username character varying(20) COLLATE pg_catalog."default" NOT NULL,
port character varying(10) COLLATE pg_catalog."default",
CONSTRAINT user_pkey PRIMARY KEY (username)
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public."user"
OWNER to postgres;
\ No newline at end of file
-- Table: public.v1
-- DROP TABLE IF EXISTS public.v1;
CREATE TABLE IF NOT EXISTS public.v1
(
username character varying(50) COLLATE pg_catalog."default" NOT NULL,
status character varying(50) COLLATE pg_catalog."default",
"serverProperties" json,
CONSTRAINT v1_pkey PRIMARY KEY (username)
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.v1
OWNER to postgres;
\ No newline at end of file
-- Table: public.v2
-- DROP TABLE IF EXISTS public.v2;
CREATE TABLE IF NOT EXISTS public.v2
(
username character varying(50) COLLATE pg_catalog."default" NOT NULL,
tenants json,
debug json,
has_debugged boolean,
status character varying(50) COLLATE pg_catalog."default",
"serverProperties" json,
CONSTRAINT v2_pkey PRIMARY KEY (username)
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.v2
OWNER to postgres;
\ No newline at end of file
...@@ -10,7 +10,7 @@ spring.redis.database=0 ...@@ -10,7 +10,7 @@ spring.redis.database=0
# redis ?? # redis ??
#spring.redis.password= #spring.redis.password=
# redis ?? # redis ??
spring.redis.port=25557 spring.redis.port=6379
# redis host # redis host
spring.redis.host=192.168.0.4 spring.redis.host=192.168.0.4
...@@ -50,11 +50,11 @@ logwire.tenants[0].mail.password=flzx.3qc ...@@ -50,11 +50,11 @@ logwire.tenants[0].mail.password=flzx.3qc
logwire.tenants[0].mail.protocol=smtp logwire.tenants[0].mail.protocol=smtp
logwire.tenants[0].mail.encoding=utf-8 logwire.tenants[0].mail.encoding=utf-8
#obs #obs
logwire.document-storage-type=obs #logwire.document-storage-type=obs
logwire.obs.bucket-name=platform-dev #logwire.obs.bucket-name=platform-dev
logwire.obs.end-point=obs.cn-east-3.myhuaweicloud.com #logwire.obs.end-point=obs.cn-east-3.myhuaweicloud.com
logwire.obs.access-key-id=BLVYTFIPDZDN57ZTCQ5K #logwire.obs.access-key-id=BLVYTFIPDZDN57ZTCQ5K
logwire.obs.access-key-secret=kPaWoZdyaLQVGYQo0PPTIpwHQi0SaDIE1pmE0itZ #logwire.obs.access-key-secret=kPaWoZdyaLQVGYQo0PPTIpwHQi0SaDIE1pmE0itZ
#azure-blob #azure-blob
#logwire.document-storage-type=blob #logwire.document-storage-type=blob
#logwire.blob.account-name=logwireblob #logwire.blob.account-name=logwireblob
......
...@@ -20,7 +20,7 @@ spring.redis.database=0 ...@@ -20,7 +20,7 @@ spring.redis.database=0
# redis 密码 # redis 密码
spring.redis.password= spring.redis.password=
# redis 端口 # redis 端口
spring.redis.port=30002 spring.redis.port=6379
# redis host # redis host
spring.redis.host=192.168.0.190 spring.redis.host=192.168.0.190
# 连接超时时间(单位:毫秒) # 连接超时时间(单位:毫秒)
...@@ -316,3 +316,9 @@ logwire.tenants[0].primary-namespace=library ...@@ -316,3 +316,9 @@ logwire.tenants[0].primary-namespace=library
#logwire.tenants[0].security.csp.frame-ancestors='self' #logwire.tenants[0].security.csp.frame-ancestors='self'
# 设计器密码 # 设计器密码
logwire.security.encryption.logwire-kms.master-key-passwords[0]=logwire
logwire.tenants[0].designer.trust-ip=127.0.0.1
logwire.security.encryption.key-management-system=logwire-kms
\ No newline at end of file
import LogUtil from "~/server/utils/log"
// 检测容器运行状态。如果服务器突然被关停,会导致个人服务一直显示“运行中”,但是实际停掉了
export default defineEventHandler(async (event) => {
try {
// 根据传入的端口号,检测
let username = event.context.username
let docker = createDockerFactory(username)
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node')
if (!container) {
throw new Error('没有创建容器,请先初始化容器')
}
let info = await container.inspect()
if (info.State.Running) {
// 正在运行,则认为正常
return
} else {
LogUtil.print(username, `[progress] [[1;34mInfo[m] 服务器异常关闭,正在重启...... \n`)
await docker.startContainer({ container })
// 依次打开 nginx, vscode-web
try {
await docker.execContainerCommand({ container, cmd: 'lsof -i:80' })
} catch (err) {
try {
await docker.execContainerCommand({ container, cmd: 'nginx' })
LogUtil.print(username, `[progress] [[1;34mInfo[m] Nginx 重启成功...... \n`)
} catch (err) {
throw err
}
}
try {
await docker.execContainerCommand({ container, cmd: 'lsof -i:8000' })
} catch (err) {
try {
docker.execContainerCommand({ container, cmd: 'code-server --bind-addr 127.0.0.1:8000 --auth none' })
LogUtil.print(username, `[progress] [[1;34mInfo[m] Vscode 重启成功...... \n`)
} catch (err) {
throw err
}
}
try { await docker.execContainerCommand({ container, cmd: 'pm2 delete backend' }) } catch (err) {}
try { await docker.execContainerCommand({ container, cmd: 'pm2 delete gateway' }) } catch (err) {}
try { await docker.execContainerCommand({ container, cmd: 'pm2 delete platform' }) } catch (err) {}
const status = getUserConfig(username, 'status')
if (status === 'running') {
setUserConfig(username, 'status', 'stopped')
}
const statusV1 = getUserConfig(username, 'v1-status')
if (statusV1 === 'running') {
setUserConfig(username, 'v1-status', 'stopped')
}
LogUtil.printSuccess(username, '所有服务重启成功')
}
} catch (err) {
setResponseStatus(event, 500)
return err
}
})
\ No newline at end of file
...@@ -2,15 +2,14 @@ import LogUtil from "~/server/utils/log" ...@@ -2,15 +2,14 @@ import LogUtil from "~/server/utils/log"
import path from 'path' import path from 'path'
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
let username = event.context.username
try { try {
let username = event.context.username
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
let port = getUserConfig(username, 'node-port')
setUserConfig(username, 'v1-status', 'compiling') setPgTableData( 'v1', username, 'status', 'compiling')
// 每次编译前把已有的 tenants_config 文件夹拷贝到一个地方,编译完成后,再拷贝回来 // 每次编译前把已有的 tenants_config 文件夹拷贝到一个地方,编译完成后,再拷贝回来
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') let container = await docker.checkContainer( username + '.node')
if (!container) { if (!container) {
throw new Error('没有创建容器,请先初始化容器') throw new Error('没有创建容器,请先初始化容器')
} }
...@@ -19,14 +18,13 @@ export default defineEventHandler(async (event) => { ...@@ -19,14 +18,13 @@ export default defineEventHandler(async (event) => {
await docker.preStartSystem({ container, username, platform: 'v1' }) await docker.preStartSystem({ container, username, platform: 'v1' })
LogUtil.printInfo(username, '编译中') LogUtil.printInfo(username, '编译中')
const pomDir = '/var/logwire-platform/logwire-build/logwire-server/logwire-libs/logwire-web' const pomDir = '/var/logwire-platform/logwire-build/logwire-server/logwire-libs/logwire-web'
await docker.putArchive({ container, tarPath: path.resolve(__dirname, './files/v1/pomWithoutWeb.tar'), targetPath: pomDir }) await docker.putArchive({ container, tarPath: path.resolve('./public/files/v1/pomWithoutWeb.tar'), targetPath: pomDir })
await docker.execContainerCommand({ container, cmd: ['/bin/bash', '-c', 'export LANG=zh_CN.UTF-8;mvn clean package -Dmaven.test.skip=true'], dir: '/var/logwire-platform/logwire-build'}) await docker.execContainerCommand({ container, cmd: ['/bin/bash', '-c', 'export LANG=zh_CN.UTF-8;mvn clean package -Dmaven.test.skip=true'], dir: '/var/logwire-platform/logwire-build'})
await docker.putArchive({ container, tarPath: path.resolve(__dirname, './files/v1/pomWithWeb.tar'), targetPath: pomDir }) await docker.putArchive({ container, tarPath: path.resolve('./public/files/v1/pomWithWeb.tar'), targetPath: pomDir })
const pomVersionStr = await docker.getFile({ container, path: '/var/logwire-platform/logwire-build/logwire-version/pom.xml' }) const pomVersionStr = await docker.getFile({ container, path: '/var/logwire-platform/logwire-build/logwire-version/pom.xml' })
const version = /<logwire-starter\.version>(.*?)<\/logwire-starter\.version>/.exec(pomVersionStr)?.[1] const version = /<logwire-starter\.version>(.*?)<\/logwire-starter\.version>/.exec(pomVersionStr)?.[1]
const versionWeb = /<logwire-libs\.version>(.*?)<\/logwire-libs\.version>/.exec(pomVersionStr)?.[1] const versionWeb = /<logwire-libs\.version>(.*?)<\/logwire-libs\.version>/.exec(pomVersionStr)?.[1]
const projectDir = getUserConfig(username, 'serverPropertiesV1')?.find((o: { key: string, value: string }) => o.key === 'logwire.tenants[0].dir')?.value?.trim() || './projects/demo'
await docker.execContainerCommand({ container, cmd: 'tar -zvcf ../tenants.tar.gz ./projects/demo', dir: '/var/logwire-platform/dist'}) await docker.execContainerCommand({ container, cmd: 'tar -zvcf ../tenants.tar.gz ./projects/demo', dir: '/var/logwire-platform/dist'})
await docker.execContainerCommand({ container, cmd: 'rm -rf dist', dir: '/var/logwire-platform'}) await docker.execContainerCommand({ container, cmd: 'rm -rf dist', dir: '/var/logwire-platform'})
...@@ -39,11 +37,9 @@ export default defineEventHandler(async (event) => { ...@@ -39,11 +37,9 @@ export default defineEventHandler(async (event) => {
await docker.execContainerCommand({ container, cmd: 'rm ../tenants.tar.gz', dir: '/var/logwire-platform/dist'}) await docker.execContainerCommand({ container, cmd: 'rm ../tenants.tar.gz', dir: '/var/logwire-platform/dist'})
await copyAndCreateServerPropertiesV1InDocker(username) await copyAndCreateServerPropertiesV1InDocker(username)
LogUtil.printInfo(username, '编译完成') LogUtil.printInfo(username, '编译完成')
setUserConfig(username, 'v1-status', 'created') await setPgTableData( 'v1', username, 'status', 'compiled')
} catch (err) { } catch (err) {
console.log(err) await setPgTableData( 'v1', username, 'status', 'created')
let username = event.context.username
setUserConfig(username, 'v1-status', 'created')
LogUtil.printError(username, err instanceof Error ? err.message : JSON.stringify(err)) LogUtil.printError(username, err instanceof Error ? err.message : JSON.stringify(err))
setResponseStatus(event, 500) setResponseStatus(event, 500)
return err return err
......
import LogUtil from "~/server/utils/log" import LogUtil from "~/server/utils/log"
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
try {
let username = event.context.username let username = event.context.username
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') let container = await docker.checkContainer( username + '.node')
let port = getUserConfig(username, 'node-port') const row = await getPgTableData( 'user', username)
const port = row?.["port"]
LogUtil.printInfo(username, '程序启动中') LogUtil.printInfo(username, '程序启动中')
if (!container) { if (!container) {
...@@ -21,13 +21,7 @@ export default defineEventHandler(async (event) => { ...@@ -21,13 +21,7 @@ export default defineEventHandler(async (event) => {
await docker.execContainerCommand({ container, cmd: `pm2 start --name platform --no-autorestart java -- -Xms512m -Xmx512m -XX:MaxMetaspaceSize=200m -Dfile.encoding=UTF-8 -Dloader.path=lib -jar logwire-starter-${version}.jar > logwire.log`, dir: '/var/logwire-platform/dist', quiet: true }) await docker.execContainerCommand({ container, cmd: `pm2 start --name platform --no-autorestart java -- -Xms512m -Xmx512m -XX:MaxMetaspaceSize=200m -Dfile.encoding=UTF-8 -Dloader.path=lib -jar logwire-starter-${version}.jar > logwire.log`, dir: '/var/logwire-platform/dist', quiet: true })
LogUtil.print(username, `[progress] [[1;34mInfo[m] 程序运行中...... \n`) LogUtil.print(username, `[progress] [[1;34mInfo[m] 程序运行中...... \n`)
LogUtil.print(username, `[progress] [[1;34mInfo[m] 请代理后端请求到 <strong>192.168.0.4:${port}</strong> 上 \n`) LogUtil.print(username, `[progress] [[1;34mInfo[m] 请代理后端请求到 <strong>${HOST}:${port}</strong> 上 \n`)
setUserConfig(username, 'v1-status', 'running') await setPgTableData( 'v1', username, 'status', 'running')
} catch (err) {
let username = event.context.username
LogUtil.print(username, '[error] [[1;31mError[m]] ' + (err instanceof Error ? err.message : JSON.stringify(err)))
setResponseStatus(event, 500)
return err
}
}) })
\ No newline at end of file
import { TableV1, TableV2 } from "~/server/utils/postgres"
export default defineEventHandler(async (event) => {
let username = event.context.username
let docker = createDockerFactory(username)
const row = await getPgTableData( 'v1', username)
const info: Partial<TableV1> & { branch: string } = { ...row, branch: '' }
info.status = row?.status || 'null'
try {
let container = await docker.checkContainer(username + '.node')
if (container) {
await docker.startContainer({ container })
let result = await docker.execContainerCommand({ container, cmd: 'git branch', dir: getPlatformRootFolder('v1') })
const arrays = result.split('\n')
let currentBranch = arrays.find(o => o.includes('*'))
currentBranch = currentBranch?.replace(/^.*?\*/, '').trim() || ''
info.branch = removeUnreadCharacter(currentBranch)
} else {
info.branch = '无'
}
} catch (err) {
// 出现任何错误都认为没有未初始化分支
info.branch = '无'
}
return info
})
\ No newline at end of file
...@@ -2,58 +2,32 @@ import Dockerode from "dockerode" ...@@ -2,58 +2,32 @@ import Dockerode from "dockerode"
import LogUtil from "~/server/utils/log" import LogUtil from "~/server/utils/log"
import path from 'path' import path from 'path'
function sleep (ms: number) {
return new Promise<void>((resolve, reject) => {
setTimeout(resolve, ms)
})
}
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
let username = event.context.username
let pgClient = await createPgClientFactory(username)
try { try {
let username = event.context.username
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
let host = process.env['DOCKER_ENV']?.trim() === 'prod' ? '192.168.0.4' : 'localhost' const rowV2 = await getPgTableData( 'v2', username)
let port = getUserConfig(username, 'node-port').toString()
const statusV2 = getUserConfig(username, 'status') if (!rowV2 || rowV2?.status === 'null' || !rowV2?.status) {
if (statusV2 === 'null' || statusV2 === undefined) {
throw new Error('请先初始化 V2') throw new Error('请先初始化 V2')
} }
setUserConfig(username, 'v1-status', 'creating') let rowV1 = await getPgTableData('v1', username)
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') as Dockerode.Container if (!rowV1) await insertPgTableData('v1', username)
await setPgTableData( 'v1', username, 'status', 'creating')
let container = await docker.checkContainer( username + '.node') as Dockerode.Container
if (!container) { if (!container) {
throw new Error('没有创建容器,请先初始化容器') throw new Error('没有创建容器,请先初始化容器')
} }
// 创建 postgres 不一定要在本机,即使是 dev 环境也可以联远程的容器 // 创建 postgres 不一定要在本机,即使是 dev 环境也可以联远程的容器
await LogUtil.run(username, 'v1-创建 postgres 容器', async () => { await LogUtil.run(username, 'v1-创建 postgres 容器', async () => {
if (getUserConfig(username, 'postgresV1.ip') !== '192.168.0.4') { const database = username + '_v1'
let postgres = await docker.checkAndCreateContainer({ name: 'postgres', img: 'postgres:12', env: ["POSTGRES_PASSWORD=postgres"], portBindings: { '5432/tcp': [{ HostPort: '30001' }] } }) let result = await executePgQuery({ client: pgClient, query: "SELECT u.datname FROM pg_catalog.pg_database u where u.datname='" + database + "';" })
let info = await postgres.inspect() if (result.rows.length === 0) {
await docker.startContainer({ container: postgres }) await executePgQuery({ client: pgClient, query: 'CREATE DATABASE ' + database })
if (info?.State.Status === 'created') { const client = await createPgClient({ host: HOST, port: PgPort, database: database })
info = await postgres.inspect() await executePgQuery({ client, query: 'CREATE SCHEMA library' })
await sleep(3000) await client.end()
let client = await createPgClient({ host: host, port: 30001 })
await executePgQuery({ client, query: 'CREATE DATABASE logwirev2'})
await executePgQuery({ client, query: 'CREATE SCHEMA library' })
await client.end()
}
} else {
// 如果是服务器环境,检查是否存在对应的数据库表,根据不同用户创建不同的数据库
let postgres = await docker.checkContainer('postgres_12')
if (!postgres) {
throw new Error('没有创建服务器上的 Postgres 容器, 请先创建')
}
const datname = username + '_v1'
let client = await createPgClient({ host: host, port: 25556 })
let result = await executePgQuery({ client, query: "SELECT u.datname FROM pg_catalog.pg_database u where u.datname='" + datname + "';" })
if (result.rows.length === 0) {
await executePgQuery({ client, query: 'CREATE DATABASE ' + datname })
client = await createPgClient({ host: host, port: 25556, database: datname })
await executePgQuery({ client, query: 'CREATE SCHEMA library' })
await client.end()
setUserConfig(username, 'postgresV1.database', datname)
}
} }
}) })
...@@ -71,7 +45,7 @@ export default defineEventHandler(async (event) => { ...@@ -71,7 +45,7 @@ export default defineEventHandler(async (event) => {
}) })
await LogUtil.run(username, 'v1-下载字符集', async () => { await LogUtil.run(username, 'v1-下载字符集', async () => {
await docker.execContainerCommand({ container, cmd: 'rm /etc/locale.gen'}) await docker.execContainerCommand({ container, cmd: 'rm -f /etc/locale.gen'})
await docker.execContainerCommand({ container, cmd: 'touch /etc/locale.gen' }) await docker.execContainerCommand({ container, cmd: 'touch /etc/locale.gen' })
await docker.writeFile({ container, path: '/etc/locale.gen', text: '\'\nzh_CN.UTF-8 UTF-8\''}) await docker.writeFile({ container, path: '/etc/locale.gen', text: '\'\nzh_CN.UTF-8 UTF-8\''})
await docker.execContainerCommand({ container, cmd: 'apt-get install -y locales' }) await docker.execContainerCommand({ container, cmd: 'apt-get install -y locales' })
...@@ -82,17 +56,20 @@ export default defineEventHandler(async (event) => { ...@@ -82,17 +56,20 @@ export default defineEventHandler(async (event) => {
await docker.execContainerCommand({ container, cmd: 'rm -rf /var/logwire-demo' }) await docker.execContainerCommand({ container, cmd: 'rm -rf /var/logwire-demo' })
await docker.execContainerCommand({ container, cmd: 'mkdir logwire-demo', dir: '/var'}) await docker.execContainerCommand({ container, cmd: 'mkdir logwire-demo', dir: '/var'})
await docker.execContainerCommand({ container, cmd: 'mkdir -p dist/projects/demo', dir: '/var/logwire-platform'}) await docker.execContainerCommand({ container, cmd: 'mkdir -p dist/projects/demo', dir: '/var/logwire-platform'})
// 避免上传太大的压缩文件,需要先在 node 服务器的 /var 目录下,上传该文件
await docker.putArchive({ container, tarPath: path.resolve('/var/book.tar'), targetPath: '/var/logwire-demo' }) await docker.putArchive({ container, tarPath: path.resolve('/var/book.tar'), targetPath: '/var/logwire-demo' })
await docker.execContainerCommand({ container, cmd: 'tar -zvcf ../book.tar .', dir: '/var/logwire-demo/book'}) await docker.execContainerCommand({ container, cmd: 'tar -zvcf ../book.tar .', dir: '/var/logwire-demo/book'})
await docker.execContainerCommand({ container, cmd: 'tar -zvxf book.tar -C /var/logwire-platform/dist/projects/demo', dir: '/var/logwire-demo'}) await docker.execContainerCommand({ container, cmd: 'tar -zvxf book.tar -C /var/logwire-platform/dist/projects/demo', dir: '/var/logwire-demo'})
}) })
setUserConfig(username, 'v1-status', 'created') await setPgTableData( 'v1', username, 'status', 'created')
await stopPgClient(pgClient)
LogUtil.printSuccess(username, 'installed') LogUtil.printSuccess(username, 'installed')
} catch (err: any) { } catch (err: any) {
let username = event.context.username await setPgTableData( 'v1', username, 'status', 'null')
setUserConfig(username, 'v1-status', 'null') await stopPgClient(pgClient)
LogUtil.printError(username, (err instanceof Error ? err.message : JSON.stringify(err))) LogUtil.printError(username, (err instanceof Error ? err.message : JSON.stringify(err)))
setResponseStatus(event, 500) setResponseStatus(event, 500)
return err
} }
}) })
\ No newline at end of file
import LogUtil from "~/server/utils/log" import LogUtil from "~/server/utils/log"
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
try { let username = event.context.username
let username = event.context.username let docker = createDockerFactory(username)
let docker = createDockerFactory(username) let container = await docker.checkContainer( username + '.node')
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') if (!container) {
if (!container) { throw new Error('没有创建容器,请先初始化容器')
throw new Error('没有创建容器,请先初始化容器')
}
LogUtil.printInfo(username, `程序停止中......`)
await docker.execContainerCommand({ container, cmd: 'pm2 delete platform', quiet: true })
LogUtil.printInfo(username, `程序已停止`)
setUserConfig(username, 'v1-status', 'stopped')
} catch (err) {
setResponseStatus(event, 500)
return err
} }
LogUtil.printInfo(username, `程序停止中......`)
await docker.execContainerCommand({ container, cmd: 'pm2 delete platform', quiet: true })
LogUtil.printInfo(username, `程序已停止`)
await setPgTableData( 'v1', username, 'status', 'compiled')
}) })
\ No newline at end of file
...@@ -2,15 +2,16 @@ import LogUtil from "~/server/utils/log" ...@@ -2,15 +2,16 @@ import LogUtil from "~/server/utils/log"
import { copyAndCreateGatewayPropertiesV2InDocker, copyAndCreateServerPropertiesV2InDocker } from "~/server/utils/server" import { copyAndCreateGatewayPropertiesV2InDocker, copyAndCreateServerPropertiesV2InDocker } from "~/server/utils/server"
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
let username = event.context.username
try { try {
let username = event.context.username let username = event.context.username
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
let port = getUserConfig(username, 'node-port')
setUserConfig(username, 'status', 'compiling') await setPgTableData( 'v2', username, 'status', 'compiling')
// TODO: 在后端发送 websocket 通知前端状态变更
// 每次编译前把已有的 tenants_config 文件夹拷贝到一个地方,编译完成后,再拷贝回来 // 每次编译前把已有的 tenants_config 文件夹拷贝到一个地方,编译完成后,再拷贝回来
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') let container = await docker.checkContainer( username + '.node')
if (!container) { if (!container) {
throw new Error('没有创建容器,请先初始化容器') throw new Error('没有创建容器,请先初始化容器')
} }
...@@ -35,10 +36,9 @@ export default defineEventHandler(async (event) => { ...@@ -35,10 +36,9 @@ export default defineEventHandler(async (event) => {
copyAndCreateGatewayPropertiesV2InDocker(username) copyAndCreateGatewayPropertiesV2InDocker(username)
LogUtil.print(username, `[progress] [[1;34mInfo[m] 编译完成 \n`) LogUtil.print(username, `[progress] [[1;34mInfo[m] 编译完成 \n`)
setUserConfig(username, 'status', 'created') await setPgTableData( 'v2', username, 'status', 'compiled')
} catch (err) { } catch (err) {
let username = event.context.username await setPgTableData( 'v2', username, 'status', 'created')
LogUtil.print(username, '[error] [[1;31mError[m]] ' + (err instanceof Error ? err.message : JSON.stringify(err))) LogUtil.print(username, '[error] [[1;31mError[m]] ' + (err instanceof Error ? err.message : JSON.stringify(err)))
setResponseStatus(event, 500) setResponseStatus(event, 500)
} }
......
...@@ -2,17 +2,18 @@ import LogUtil from "~/server/utils/log" ...@@ -2,17 +2,18 @@ import LogUtil from "~/server/utils/log"
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
// 先判断服务是否启动,启动则关闭,然后调用新的命令启动 java 端 // 先判断服务是否启动,启动则关闭,然后调用新的命令启动 java 端
try {
let username = event.context.username let username = event.context.username
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') let container = await docker.checkContainer( username + '.node')
if (!container) { if (!container) {
throw new Error('没有创建容器,请先初始化容器') throw new Error('没有创建容器,请先初始化容器')
} }
const status = getUserConfig(username, 'status') const rowV2 = await getPgTableData( 'v2', username)
const tenants = getUserConfig(username, 'tenants') const status = rowV2?.status
const port = getUserConfig(username, 'node-port') const tenants = rowV2?.tenants
const debug = getUserConfig(username, 'debug') || { host: '192.168.0.190' } const rowUser = await getPgTableData( 'user', username)
const port = rowUser?.["port"]
const debug = rowV2?.debug
// 有可能本身就关闭了 // 有可能本身就关闭了
try { try {
...@@ -21,9 +22,7 @@ export default defineEventHandler(async (event) => { ...@@ -21,9 +22,7 @@ export default defineEventHandler(async (event) => {
} catch (err) {} } catch (err) {}
await LogUtil.run(username, '初始化调试功能', async () => { await LogUtil.run(username, '初始化调试功能', async () => {
let key = 'InstallSteps' if (rowV2?.has_debugged) return
let steps: string[] = getUserConfig(username, key) || []
if (steps.includes('初始化调试功能')) return
try { try {
if (container) { if (container) {
LogUtil.print(username, `[progress] [Loading] 下载 Nginx ...... \n`) LogUtil.print(username, `[progress] [Loading] 下载 Nginx ...... \n`)
...@@ -37,6 +36,8 @@ export default defineEventHandler(async (event) => { ...@@ -37,6 +36,8 @@ export default defineEventHandler(async (event) => {
await docker.execContainerCommand({ container, cmd: 'make', dir: '/var/tmp/nginx-1.18.0'}) await docker.execContainerCommand({ container, cmd: 'make', dir: '/var/tmp/nginx-1.18.0'})
LogUtil.print(username, `[progress] [Loading] 替换 Nginx 文件 ...... \n`) LogUtil.print(username, `[progress] [Loading] 替换 Nginx 文件 ...... \n`)
await docker.execContainerCommand({ container, cmd: 'cp /var/tmp/nginx-1.18.0/objs/nginx /usr/sbin/'}) await docker.execContainerCommand({ container, cmd: 'cp /var/tmp/nginx-1.18.0/objs/nginx /usr/sbin/'})
await setPgTableData( 'v2', username, 'has_debugged', true)
} }
} catch (err) { } catch (err) {
console.log('err2', err) console.log('err2', err)
...@@ -58,13 +59,9 @@ export default defineEventHandler(async (event) => { ...@@ -58,13 +59,9 @@ export default defineEventHandler(async (event) => {
await docker.execContainerCommand({ container, cmd: 'pm2 start --name backend --no-autorestart java -- -Xms128m -Xmx128m -XX:+UseG1GC -DVALIDATE_XML_ENABLED=false --add-opens java.base/sun.util.locale.provider=ALL-UNNAMED -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=:6666,suspend=n -jar logwire-backend-starter.jar', dir: '/var/logwire-backend/build-output/backend', quiet: true }) await docker.execContainerCommand({ container, cmd: 'pm2 start --name backend --no-autorestart java -- -Xms128m -Xmx128m -XX:+UseG1GC -DVALIDATE_XML_ENABLED=false --add-opens java.base/sun.util.locale.provider=ALL-UNNAMED -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=:6666,suspend=n -jar logwire-backend-starter.jar', dir: '/var/logwire-backend/build-output/backend', quiet: true })
LogUtil.print(username, `[progress] [[1;34mInfo[m] 程序运行中...... \n`) LogUtil.print(username, `[progress] [[1;34mInfo[m] 程序运行中...... \n`)
LogUtil.print(username, `[progress] [[1;34mInfo[m] 请代理后端请求到 <strong>192.168.0.4:${port}</strong> 上 \n`) LogUtil.print(username, `[progress] [[1;34mInfo[m] 请代理后端请求到 <strong>${HOST}:${port}</strong> 上 \n`)
LogUtil.print(username, `[progress] [[1;34mInfo[m] 请设置开发环境域名和端口号 <strong>${tenants.host}</strong> 上\n`) LogUtil.print(username, `[progress] [[1;34mInfo[m] 请设置开发环境域名和端口号 <strong>${tenants?.host}</strong> 上\n`)
LogUtil.print(username, `[progress] [[1;34mInfo[m] 调试程序已启动,请IP地址为 <strong>${debug?.host}</strong> 后端访问 <strong>192.168.0.4:${port}</strong> 进行调试\n`) LogUtil.print(username, `[progress] [[1;34mInfo[m] 调试程序已启动,请IP地址为 <strong>${debug?.host}</strong> 后端访问 <strong>${HOST}:${port}</strong> 进行调试\n`)
setUserConfig(username, 'status', 'running') await setPgTableData( 'v2', username, 'status', 'running')
} catch (err) {
setResponseStatus(event, 500)
return err
}
}) })
\ No newline at end of file
import LogUtil from "~/server/utils/log" import LogUtil from "~/server/utils/log"
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
let username = event.context.username
let pgClient = await createPgClientFactory(username)
try { try {
let username = event.context.username let username = event.context.username
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
// 创建 node 容器 // 创建 node 容器
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') let container = await docker.checkContainer( username + '.node')
let port = getUserConfig(username, 'node-port') let row = await getPgTableData( 'user', username)
let port = row?.["port"]
if (container) { if (container) {
LogUtil.print(username, `[progress] [[1;34mInfo[m] 打包微信小程序zip \n`) LogUtil.print(username, `[progress] [[1;34mInfo[m] 打包微信小程序zip \n`)
await docker.execContainerCommand({ container, cmd: 'rm -f weapp.zip', dir: '/var/logwire-backend/build-output/backend' }) await docker.execContainerCommand({ container, cmd: 'rm -f weapp.zip', dir: '/var/logwire-backend/build-output/backend' })
...@@ -16,6 +19,7 @@ export default defineEventHandler(async (event) => { ...@@ -16,6 +19,7 @@ export default defineEventHandler(async (event) => {
throw new Error('没有创建容器,请先初始化容器') throw new Error('没有创建容器,请先初始化容器')
} }
} catch (err) { } catch (err) {
await stopPgClient(pgClient)
setResponseStatus(event, 500) setResponseStatus(event, 500)
return err return err
} }
......
import LogUtil from "~/server/utils/log" import LogUtil from "~/server/utils/log"
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
let username = event.context.username
try { try {
let username = event.context.username let username = event.context.username
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') let container = await docker.checkContainer( username + '.node')
let tenants = getUserConfig(username, 'tenants') let rowUser = await getPgTableData( 'user', username)
let port = getUserConfig(username, 'node-port') let rowV2 = await getPgTableData( 'v2', username)
let tenants = rowV2?.tenants
let port = rowUser?.["port"]
LogUtil.printInfo(username, `程序初始化中......`) LogUtil.printInfo(username, `程序初始化中......`)
if (!container) { if (!container) {
...@@ -26,12 +29,11 @@ export default defineEventHandler(async (event) => { ...@@ -26,12 +29,11 @@ export default defineEventHandler(async (event) => {
await docker.execContainerCommand({ container, cmd: `pm2 start --name backend --no-autorestart java -- -Xms128m -Xmx128m -XX:+UseG1GC -DVALIDATE_XML_ENABLED=false --add-opens java.base/sun.util.locale.provider=ALL-UNNAMED -jar logwire-backend-starter.jar`, dir: '/var/logwire-backend/build-output/backend', quiet: true }) await docker.execContainerCommand({ container, cmd: `pm2 start --name backend --no-autorestart java -- -Xms128m -Xmx128m -XX:+UseG1GC -DVALIDATE_XML_ENABLED=false --add-opens java.base/sun.util.locale.provider=ALL-UNNAMED -jar logwire-backend-starter.jar`, dir: '/var/logwire-backend/build-output/backend', quiet: true })
LogUtil.printInfo(username, `程序运行中......`) LogUtil.printInfo(username, `程序运行中......`)
LogUtil.printInfo(username, `请代理后端请求到 <strong>192.168.0.4:${port}</strong> 上`) LogUtil.printInfo(username, `请代理后端请求到 <strong>${HOST}:${port}</strong> 上`)
LogUtil.printInfo(username, `请设置开发环境域名和端口号 <strong>${tenants.host}</strong> 上`) LogUtil.printInfo(username, `请设置开发环境域名和端口号 <strong>${tenants?.host}</strong> 上`)
setUserConfig(username, 'status', 'running') await setPgTableData( 'v2', username, 'status', 'running')
} catch (err) { } catch (err) {
let username = event.context.username
LogUtil.printError(username, (err instanceof Error ? err.message : JSON.stringify(err))) LogUtil.printError(username, (err instanceof Error ? err.message : JSON.stringify(err)))
setResponseStatus(event, 500) setResponseStatus(event, 500)
return err return err
......
import { TableV2 } from "~/server/utils/postgres"
export default defineEventHandler(async (event) => {
let username = event.context.username
let docker = createDockerFactory(username)
let pgClient = await createPgClientFactory('backend_helper')
const row = await getPgTableData( 'v2', username)
const info: Partial<TableV2> & { branch: string } = { ...row, branch: '' }
info.status = row?.status || 'null'
try {
let container = await docker.checkContainer(username + '.node')
if (container) {
await docker.startContainer({ container })
let result = await docker.execContainerCommand({ container, cmd: 'git branch', dir: getPlatformRootFolder('v2') })
const arrays = result.split('\n')
let currentBranch = arrays.find(o => o.includes('*'))
currentBranch = currentBranch?.replace(/^.*?\*/, '').trim() || ''
info.branch = removeUnreadCharacter(currentBranch)
} else {
info.branch = '无'
}
} catch (err) {
// 出现任何错误都认为没有未初始化分支
info.branch = '无'
}
return info
})
\ No newline at end of file
import Dockerode from "dockerode" import Dockerode from "dockerode"
import LogUtil from "~/server/utils/log" import LogUtil from "~/server/utils/log"
import { createPgClient, executePgQuery } from "~/server/utils/postgres"
import { checkSshExists } from "~/server/utils/server"
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
function sleep (ms: number) {
return new Promise<void>((resolve, reject) => {
setTimeout(resolve, ms)
})
}
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
let username = event.context.username
let pgClient = await createPgClientFactory('backend_helper')
try { try {
let username = event.context.username
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
let host = docker.host let host = docker.host
let port = getUserConfig(username, 'node-port').toString()
let container: Dockerode.Container = {} as Dockerode.Container let container: Dockerode.Container = {} as Dockerode.Container
const port = (await getPgTableData( 'user', username))?.port
setUserConfig(username, 'status', 'creating') const row = await getPgTableData('v2', username)
if (!row) await insertPgTableData('v2', username)
await setPgTableData( 'v2', username, 'status', 'creating')
// 必须要保证创建容器是首先执行 // 必须要保证创建容器是首先执行
// node 容器根据 dev/prod 环境与否,决定在哪里创建,远程容器还是本机容器 // node 容器根据 dev/prod 环境与否,决定在哪里创建,远程容器还是本机容器
await LogUtil.run(username, '创建 node 容器', async () => { await LogUtil.run(username, '创建 node 容器', async () => {
...@@ -36,58 +30,47 @@ export default defineEventHandler(async (event) => { ...@@ -36,58 +30,47 @@ export default defineEventHandler(async (event) => {
LogUtil.printWarning(username, `${username}@greaconsulting.com 用户 SSH key 为 <strong>${sshKey.replace(/\n/g, '')}</strong>, 若克隆仓库失败, 请检查是否放入 Gitlab SSH 设置中并重试\n`) LogUtil.printWarning(username, `${username}@greaconsulting.com 用户 SSH key 为 <strong>${sshKey.replace(/\n/g, '')}</strong>, 若克隆仓库失败, 请检查是否放入 Gitlab SSH 设置中并重试\n`)
}) })
// 创建 postgres 不一定要在本机,即使是 dev 环境也可以联远程的容器 // 创建 postgres 不一定要在本机,即使是 dev 环境也可以联远程的容器
await LogUtil.run(username, '创建 postgres 容器', async () => { await LogUtil.run(username, '配置 postgres 容器', async () => {
if (getUserConfig(username, 'postgres.ip') !== '192.168.0.4') { let postgres = await docker.checkContainer('postgres_12')
let postgres = await docker.checkAndCreateContainer({ name: 'postgres', img: 'postgres:12', env: ["POSTGRES_PASSWORD=postgres"], portBindings: { '5432/tcp': [{ HostPort: '30001' }] } }) if (!postgres) {
let info = await postgres.inspect() postgres = await docker.checkAndCreateContainer({ name: 'postgres_12', img: 'postgres:12', env: ["POSTGRES_PASSWORD=postgres"], portBindings: { '5432/tcp': [{ HostPort: PgPort.toString() }] } })
await docker.startContainer({ container: postgres }) await docker.startContainer({ container: postgres })
if (info?.State.Status === 'created') { await sleep(3000)
info = await postgres.inspect() }
await sleep(3000)
let client = await createPgClient({ host: host, port: 30001 }) let client = await createPgClientFactory()
await executePgQuery({ client, query: 'CREATE DATABASE logwirev2'}) let result = await executePgQuery({ client, query: "SELECT u.datname FROM pg_catalog.pg_database u where u.datname='" +username+ "';" })
await executePgQuery({ client, query: 'CREATE SCHEMA library' }) if (result.rows.length === 0) {
await client.end() await executePgQuery({ client, query: 'CREATE DATABASE ' + username})
} await stopPgClient(client)
} else { client = await createPgClientFactory(username)
// 如果是服务器环境,检查是否存在对应的数据库表,根据不同用户创建不同的数据库 await executePgQuery({ client, query: 'CREATE SCHEMA library' })
let postgres = await docker.checkContainer('postgres_12') await stopPgClient(client)
if (!postgres) {
throw new Error('没有创建服务器上的 Postgres 容器, 请先创建')
}
let client = await createPgClient({ host: host, port: 25556 })
let result = await executePgQuery({ client, query: "SELECT u.datname FROM pg_catalog.pg_database u where u.datname='" +username+ "';" })
if (result.rows.length === 0) {
await executePgQuery({ client, query: 'CREATE DATABASE ' + username})
client = await createPgClient({ host: host, port: 25556, database: username })
await executePgQuery({ client, query: 'CREATE SCHEMA library' })
await client.end()
}
} }
}) })
await LogUtil.run(username, '创建 redis 容器', async () => { await LogUtil.run(username, '创建 redis 容器', async () => {
if (getUserConfig(username, 'redis.ip') !== '192.168.0.4') { if (process.env.NODE_ENV?.trim() !== 'production') {
let redis = await docker.checkAndCreateContainer({ name: 'redis', img: 'redis', portBindings: { '6379/tcp': [{ HostPort: '30002' }] } }) let redis = await docker.checkAndCreateContainer({ name: 'redis', img: 'redis', portBindings: { '6379/tcp': [{ HostPort: '6379' }] } })
await docker.startContainer({ container: redis }) await docker.startContainer({ container: redis })
} }
}) })
await LogUtil.run(username, '创建 zookeeper 容器', async () => { await LogUtil.run(username, '创建 zookeeper 容器', async () => {
if (getUserConfig(username, 'zookeeper.ip') !== '192.168.0.4') { if (process.env.NODE_ENV?.trim() !== 'production') {
let zookeeper = await docker.checkAndCreateContainer({ name: 'zookeeper', img: 'zookeeper', portBindings: { '2181/tcp': [{ HostPort: '30003' }] } }) let zookeeper = await docker.checkAndCreateContainer({ name: 'zookeeper', img: 'zookeeper', portBindings: { '2181/tcp': [{ HostPort: '2181' }] } })
await docker.startContainer({ container: zookeeper }) await docker.startContainer({ container: zookeeper })
} }
}) })
// 检查本机 rocketmq 端口是否被占用,被占用说明已经有 rockqtmq 服务启动,这时候就不安装容器了 // 检查本机 rocketmq 端口是否被占用,被占用说明已经有 rockqtmq 服务启动,这时候就不安装容器了
await LogUtil.run(username, '创建 rocketmq serv 容器', async () => { await LogUtil.run(username, '创建 rocketmq serv 容器', async () => {
if (getUserConfig(username, 'rocketmq.ip') !== '192.168.0.4') { if (process.env.NODE_ENV?.trim() !== 'production') {
let rockermqsrv = await docker.checkAndCreateContainer({ name: 'rocketmq.srv', img: 'foxiswho/rocketmq:4.8.0', portBindings: { '9876/tcp': [{ 'HostPort': '9876' }] /** , '10909/tcp': [], '10911/tcp': [], '10912/tcp': []*/ }, env: ['JAVA_OPT_EXT=-Xms512M -Xmx512M -Xmn128m'] , cmd: ['bash', '-c', 'mqnamesrv'] }) let rockermqsrv = await docker.checkAndCreateContainer({ name: 'rocketmq.srv', img: 'foxiswho/rocketmq:4.8.0', portBindings: { '9876/tcp': [{ 'HostPort': '9876' }] /** , '10909/tcp': [], '10911/tcp': [], '10912/tcp': []*/ }, env: ['JAVA_OPT_EXT=-Xms512M -Xmx512M -Xmn128m'] , cmd: ['bash', '-c', 'mqnamesrv'] })
await docker.startContainer({ container: rockermqsrv }) await docker.startContainer({ container: rockermqsrv })
} }
}) })
await LogUtil.run(username, '创建 rocketmq broker 容器', async () => { await LogUtil.run(username, '创建 rocketmq broker 容器', async () => {
if (getUserConfig(username, 'rocketmq.ip') !== '192.168.0.4') { if (process.env.NODE_ENV?.trim() !== 'production') {
let rocketmqbroker = await docker.checkAndCreateContainer({ name: 'rocketmq.broker', img: 'foxiswho/rocketmq:4.8.0', portBindings: { '10909/tcp': [{ 'HostPort': '10909' }], '10911/tcp': [{ 'HostPort': '10911' }] /** , '9876/tcp': [],'10912/tcp': [] */ }, env: ['JAVA_OPT_EXT=-Xms512M -Xmx512M -Xmn128m'] }) let rocketmqbroker = await docker.checkAndCreateContainer({ name: 'rocketmq.broker', img: 'foxiswho/rocketmq:4.8.0', portBindings: { '10909/tcp': [{ 'HostPort': '10909' }], '10911/tcp': [{ 'HostPort': '10911' }] /** , '9876/tcp': [],'10912/tcp': [] */ }, env: ['JAVA_OPT_EXT=-Xms512M -Xmx512M -Xmn128m'] })
await docker.startContainer({ container: rocketmqbroker }) await docker.startContainer({ container: rocketmqbroker })
...@@ -117,11 +100,16 @@ export default defineEventHandler(async (event) => { ...@@ -117,11 +100,16 @@ export default defineEventHandler(async (event) => {
await docker.execContainerCommand({ container, cmd: 'apt-get update' }) await docker.execContainerCommand({ container, cmd: 'apt-get update' })
}) })
await LogUtil.run(username, '安装 openjdk ', async () => { await LogUtil.run(username, '安装 openjdk ', async () => {
await docker.putArchive({ container, tarPath: '/var/openlogic-openjdk-17.0.10+7-linux-x64.tar.gz', targetPath: '/var' }) const openjdkFilePath = '/var/openlogic-openjdk-17.0.10+7-linux-x64.tar.gz'
const stat = fs.statSync(openjdkFilePath, { throwIfNoEntry: false })
if (!stat) {
throw new Error('未找到 ' + openjdkFilePath + ', 请下载文件并放置到根目录 var 文件夹内')
}
await docker.putArchive({ container, tarPath: openjdkFilePath , targetPath: '/var' })
await docker.execContainerCommand({ container, cmd: 'cp -r openlogic-openjdk-17.0.10+7-linux-x64/ java-17-openjdk', dir: '/var' }) await docker.execContainerCommand({ container, cmd: 'cp -r openlogic-openjdk-17.0.10+7-linux-x64/ java-17-openjdk', dir: '/var' })
await docker.execContainerCommand({ container, cmd: 'rm -rf openlogic-openjdk-17.0.10+7-linux-x64', dir: '/var' }) await docker.execContainerCommand({ container, cmd: 'rm -rf openlogic-openjdk-17.0.10+7-linux-x64', dir: '/var' })
await docker.execContainerCommand({ container, cmd: 'ln -s /etc/alternatives/java /usr/bin/java' }) await docker.execContainerCommand({ container, cmd: 'ln -sf /var/java-17-openjdk/bin/java /usr/bin/java' })
// await docker.execContainerCommand({ container, cmd: 'ln -s /etc/alternatives/java /usr/bin/java' })
// await docker.execContainerCommand({ container, cmd: 'apt-get install -y openjdk-17-jdk' }) // await docker.execContainerCommand({ container, cmd: 'apt-get install -y openjdk-17-jdk' })
}) })
await LogUtil.run(username, '安装 maven ', async () => { await LogUtil.run(username, '安装 maven ', async () => {
...@@ -184,12 +172,26 @@ export default defineEventHandler(async (event) => { ...@@ -184,12 +172,26 @@ export default defineEventHandler(async (event) => {
} }
} }
}) })
setUserConfig(username, 'status', 'created') await LogUtil.run(username, '配置租户信息', async () => {
LogUtil.printSuccess(username, 'Installed') const tenants = {
id: username,
host: 'a.test.com:23335,a.test.com:23336',
'database-schema': 'library'
}
await setPgTableData('v2', username, 'tenants', JSON.stringify(tenants))
})
await LogUtil.run(username, '配置调试信息', async () => {
const debug = {
host: '192.168.1.94'
}
await setPgTableData('v2', username, 'debug', JSON.stringify(debug))
})
await setPgTableData( 'v2', username, 'status', 'created')
await stopPgClient(pgClient)
LogUtil.printSuccess(username, '安装成功')
} catch (err: any) { } catch (err: any) {
console.log(err) await setPgTableData( 'v2', username, 'status', 'null')
let username = event.context.username await stopPgClient(pgClient)
setUserConfig(username, 'status', 'null')
LogUtil.printError(username, (err instanceof Error ? err.message : JSON.stringify(err))) LogUtil.printError(username, (err instanceof Error ? err.message : JSON.stringify(err)))
setResponseStatus(event, 500) setResponseStatus(event, 500)
return err return err
......
import LogUtil from "~/server/utils/log" import LogUtil from "~/server/utils/log"
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
try {
let username = event.context.username let username = event.context.username
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') let container = await docker.checkContainer( username + '.node')
if (!container) { if (!container) {
throw new Error('没有创建容器,请先初始化容器') throw new Error('没有创建容器,请先初始化容器')
} }
...@@ -13,9 +12,5 @@ export default defineEventHandler(async (event) => { ...@@ -13,9 +12,5 @@ export default defineEventHandler(async (event) => {
await docker.execContainerCommand({ container, cmd: 'pm2 delete gateway', quiet: true }) await docker.execContainerCommand({ container, cmd: 'pm2 delete gateway', quiet: true })
LogUtil.print(username, `程序已停止`) LogUtil.print(username, `程序已停止`)
setUserConfig(username, 'status', 'stopped') await setPgTableData( 'v2', username, 'status', 'compiled')
} catch (err) {
setResponseStatus(event, 500)
return err
}
}) })
\ No newline at end of file
...@@ -6,7 +6,7 @@ export default defineEventHandler(async (event) => { ...@@ -6,7 +6,7 @@ export default defineEventHandler(async (event) => {
let username = event.context.username let username = event.context.username
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
let branch = body.branch let branch = body.branch
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') let container = await docker.checkContainer( username + '.node')
const dir = getPlatformRootFolder(body.platform) const dir = getPlatformRootFolder(body.platform)
if (container) { if (container) {
await docker.startContainer({ container }) await docker.startContainer({ container })
......
import { getPlatformRootFolder } from "~/server/utils/server" import { getPlatformRootFolder } from "~/server/utils/server"
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
try { const body = await readBody(event)
const body = await readBody(event) let username = event.context.username
let username = event.context.username let docker = createDockerFactory(username)
let docker = createDockerFactory(username) let branch = body.branch
let branch = body.branch let container = await docker.checkContainer( username + '.node')
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') const dir = getPlatformRootFolder(body.platform)
const dir = getPlatformRootFolder(body.platform) if (container) {
if (container) { await docker.startContainer({ container })
await docker.startContainer({ container }) await docker.execContainerCommand({ container, cmd: 'git fetch', dir })
await docker.execContainerCommand({ container, cmd: 'git fetch', dir }) // fetch 时有可能本分支被强推了
// fetch 时有可能本分支被强推了 if (branch === 'master') {
if (branch === 'master') { await docker.execContainerCommand({ container, cmd: 'git pull', dir })
await docker.execContainerCommand({ container, cmd: 'git pull', dir })
} else {
await docker.execContainerCommand({ container, cmd: 'git checkout master', dir })
await docker.execContainerCommand({ container, cmd: 'git branch -D ' + branch, dir })
await docker.execContainerCommand({ container, cmd: 'git checkout ' + branch, dir })
}
} else { } else {
throw new Error('没有创建容器,请先初始化容器') await docker.execContainerCommand({ container, cmd: 'git checkout master', dir })
await docker.execContainerCommand({ container, cmd: 'git branch -D ' + branch, dir })
await docker.execContainerCommand({ container, cmd: 'git checkout ' + branch, dir })
} }
} catch (err) { } else {
setResponseStatus(event, 500) throw new Error('没有创建容器,请先初始化容器')
return err
} }
}) })
\ No newline at end of file
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
try{ const body = await readBody(event)
const body = await readBody(event) let gitEmail = body.email
let gitEmail = body.email let username = event.context.username
let username = event.context.username let docker = createDockerFactory(username)
let docker = createDockerFactory(username) // 创建 node 容器
// 创建 node 容器 let container = await docker.checkContainer( username + '.node')
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') if (container) {
if (container) { await docker.startContainer({ container })
await docker.startContainer({ container }) await docker.execContainerCommand({ container, cmd: 'rm -rf /root/.ssh' })
await docker.execContainerCommand({ container, cmd: 'rm -rf /root/.ssh' }) await docker.execContainerCommand({ container, cmd: `ssh-keygen -f /root/.ssh/id_rsa -t ed25519 -C "${gitEmail}"` })
await docker.execContainerCommand({ container, cmd: `ssh-keygen -f /root/.ssh/id_rsa -t ed25519 -C "${gitEmail}"` }) let sshKey = await docker.getFile({ container, path: '/root/.ssh/id_rsa.pub' })
let sshKey = await docker.getFile({ container, path: '/root/.ssh/id_rsa.pub' }) return sshKey
return sshKey
} else {
throw new Error('没有创建容器,请先初始化容器')
}
} catch (err) {
setResponseStatus(event, 500)
return err
} }
}) })
\ No newline at end of file
...@@ -5,7 +5,7 @@ export default defineEventHandler(async (event) => { ...@@ -5,7 +5,7 @@ export default defineEventHandler(async (event) => {
const query = getQuery(event) const query = getQuery(event)
let username = event.context.username let username = event.context.username
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') let container = await docker.checkContainer( username + '.node')
const platform = query.platform as 'v1' | 'v2' const platform = query.platform as 'v1' | 'v2'
if (container) { if (container) {
await docker.startContainer({ container }) await docker.startContainer({ container })
......
...@@ -6,7 +6,7 @@ export default defineEventHandler(async (event) => { ...@@ -6,7 +6,7 @@ export default defineEventHandler(async (event) => {
const { platform } = query const { platform } = query
let username = event.context.username let username = event.context.username
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') let container = await docker.checkContainer(username + '.node')
if (container) { if (container) {
await docker.startContainer({ container }) await docker.startContainer({ container })
let result = await docker.execContainerCommand({ container, cmd: 'git branch', dir: getPlatformRootFolder(platform as 'v1' | 'v2') }) let result = await docker.execContainerCommand({ container, cmd: 'git branch', dir: getPlatformRootFolder(platform as 'v1' | 'v2') })
......
type UserConfig = {
"postgres": {
"visible": boolean,
"username": string,
"password": string,
"ip": string,
"port": string,
"database": string,
"schema": string
},
"redis": {
"visible": boolean,
"ip": string,
"port": string
},
"zookeeper": {
"visible": boolean,
"ip": string,
"port": string
},
"rocketmq": {
"visible": boolean,
"ip": string,
"port": string
},
"tenants": {
"visible": boolean,
"id": string,
"host": string,
"database-schema": string,
"primary-namespace": string
},
"postgresV1": {
"visible": boolean,
"username": string,
"password": string,
"ip": string,
"port": string,
"database": string,
"schema": string
},
"redisV1": {
"visible": boolean,
"ip": string,
"port": string
},
"tenantsV1": {
"visible": boolean,
"id": string,
"port": number
},
"debug": {
"host": string
},
"node-port": number,
"username": string,
"status": string,
"InstallSteps": string[]
}
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const username = event.context.username const username = event.context.username
const configs = getUserAllConfigs(username) let client = await createPgClientFactory('backend_helper')
return configs as unknown as UserConfig let result = await getPgTableData( 'user', username)
return result
}) })
\ No newline at end of file
import { SESSION_PASSWORD, readJson, writeJson } from "../../utils" import { PgPort } from "~/server/utils/postgres"
import { getAvailableNodePort, getUserAllConfigs, getUserConfig } from "../../utils/server" import { SESSION_PASSWORD } from "../../utils"
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
try { try {
const body = await readBody(event) const body = await readBody(event)
const username = body.username const username = body.username
// 当用户数据文件没有 node-port 时,认为该用户还没有注册过
if (getUserConfig(username, 'node-port') === undefined) {
let host = process.env.NODE_ENV?.trim() === 'production' ? '192.168.0.4' : 'localhost'
const port = await getAvailableNodePort(host)
const userDefaultSetting = readJson('./public/files/default-user-setting.json')
const userSetting = Object.assign({}, userDefaultSetting, { "node-port": port })
userSetting['postgres']['database'] = username
userSetting['tenants']['id'] = username
userSetting['tenants']['host'] = `a.test.com:${23333+ (port - 30000)*2},a.test.com:${23334+(port-30000)*2}`
userSetting.username = username
const userDataPath = `./public/data/${username}.json`
writeJson(userDataPath, userSetting)
console.log('usetSetting', userSetting)
}
const session = await useSession(event, { password: SESSION_PASSWORD }) const session = await useSession(event, { password: SESSION_PASSWORD })
await session.update({ username }) await session.update({ username })
return session.data // 生产环境要求必须是先创建好服务器,再启动服务
if (process.env.NODE_ENV === 'development') {
return false
} else {
let result = await getPgTableData('user', username)
return result
}
} catch (err) { } catch (err) {
console.log(err)
throw createError({ throw createError({
statusCode: 500, statusCode: 500,
statusMessage: '登录失败' statusMessage: '登录失败'
......
import fs from 'fs'
import path from 'path'
export default defineEventHandler(async (event) => {
let username = event.context.username
let docker = createDockerFactory(username)
let postgres = await docker.checkContainer('postgres_12')
if (!postgres) {
postgres = await docker.checkAndCreateContainer({ name: 'postgres_12', img: 'postgres:12', env: ["POSTGRES_PASSWORD=postgres"], portBindings: { '5432/tcp': [{ HostPort: PgPort.toString() }] } })
await docker.startContainer({ container: postgres })
await sleep(3000)
} else {
await docker.startContainer({ container: postgres })
await sleep(3000)
}
let client = await createPgClientFactory()
let result = await executePgQuery({ client, query: "SELECT u.datname FROM pg_catalog.pg_database u where u.datname='backend_helper';" })
if (result.rows.length === 0) {
await executePgQuery({ client, query: 'CREATE DATABASE backend_helper'})
await client.end()
client = await createPgClientFactory('backend_helper')
const sqlCreateUser = fs
await executePgQuery({ client, query: fs.readFileSync(path.resolve('./public/files/postgres/create_user.sql'), { encoding: 'utf-8' })})
await executePgQuery({ client, query: fs.readFileSync(path.resolve('./public/files/postgres/create_v2.sql'), { encoding: 'utf-8' })})
await executePgQuery({ client, query: fs.readFileSync(path.resolve('./public/files/postgres/create_v1.sql'), { encoding: 'utf-8'})})
} else {
client = await createPgClientFactory('backend_helper')
}
let user = await getPgTableData('user', username)
if (result.rowCount === 0) {
const port = await getAvailableNodePort(HOST)
await executePgQuery({ client, query: `INSERT INTO public."user"(username, port) VALUES ('${username}', '${port}');`})
user = await getPgTableData('user', username)
}
return user
})
\ No newline at end of file
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
try {
const body = await readBody(event)
const { key, value } = body
let username = event.context.username let username = event.context.username
const body = await readBody(event)
if (typeof key === 'string') { const { table, key, value } = body
setUserConfig(username, key, value) await setPgTableData( table, username, key, value)
}
} catch(err) {
setResponseStatus(event, 500)
return err
}
}) })
\ No newline at end of file
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
import Dockerode from 'dockerode' import Dockerode from 'dockerode'
import { chmod, createReadStream, createWriteStream, mkdirSync, readFileSync, rmdirSync, rmSync } from 'fs' import { chmod, createReadStream, createWriteStream, mkdirSync, readFileSync, rmdirSync, rmSync } from 'fs'
import { resolve } from 'path' import { resolve } from 'path'
import { getUserConfig, setUserConfig } from './server'
import { removeUnreadCharacter } from '.' import { removeUnreadCharacter } from '.'
import LogUtil from './log' import LogUtil from './log'
import tar from 'tar-fs' import tar from 'tar-fs'
...@@ -26,7 +25,7 @@ class Docker { ...@@ -26,7 +25,7 @@ class Docker {
this.env = [] this.env = []
} }
async checkContainer (name: string) { async checkContainer (name: string) {
let targetContainerName = name let targetContainerName = 'logwire_backend_helper.' + name
let containers = await this.docker.listContainers({ filters: JSON.stringify({ status: ['created', 'restarting', 'running', 'removing', 'paused', 'exited', 'dead' ], name: [targetContainerName] }) }) let containers = await this.docker.listContainers({ filters: JSON.stringify({ status: ['created', 'restarting', 'running', 'removing', 'paused', 'exited', 'dead' ], name: [targetContainerName] }) })
if (containers.length) { if (containers.length) {
let container = await this.docker.getContainer(containers[0].Id) let container = await this.docker.getContainer(containers[0].Id)
...@@ -116,9 +115,7 @@ class Docker { ...@@ -116,9 +115,7 @@ class Docker {
} }
async putArchive ({ container, tarPath, targetPath }: { container: Dockerode.Container, tarPath: string, targetPath: string }) { async putArchive ({ container, tarPath, targetPath }: { container: Dockerode.Container, tarPath: string, targetPath: string }) {
return new Promise((resolve) => { return container.putArchive(tarPath, { path: targetPath })
container.putArchive(tarPath, { path: targetPath }).then(resolve)
})
} }
async writeFile ({ container, path, text }: { container: Dockerode.Container, path: string, text: string }) { async writeFile ({ container, path, text }: { container: Dockerode.Container, path: string, text: string }) {
await this.execContainerCommand({ container, cmd: ['bash', '-c', 'echo ' + text + ' > ' + path] }) await this.execContainerCommand({ container, cmd: ['bash', '-c', 'echo ' + text + ' > ' + path] })
...@@ -150,11 +147,11 @@ class Docker { ...@@ -150,11 +147,11 @@ class Docker {
} }
async switchJavaVersion ({ container, username, version }: { container: Dockerode.Container, username: string, version: 17 | 8}) { async switchJavaVersion ({ container, username, version }: { container: Dockerode.Container, username: string, version: 17 | 8}) {
LogUtil.printInfo(username, '更换 Java 环境') LogUtil.printInfo(username, '更换 Java 环境')
await this.execContainerCommand({ container, cmd: 'rm /etc/alternatives/java'}) await this.execContainerCommand({ container, cmd: 'rm /usr/bin/java'})
if (version === 8) { if (version === 8) {
await this.execContainerCommand({ container, cmd: 'ln -s /var/java-8-openjdk/bin/java /etc/alternatives/java'}) await this.execContainerCommand({ container, cmd: 'ln -sf /var/java-8-openjdk/bin/java /usr/bin/java' })
} else { } else {
await this.execContainerCommand({ container, cmd: 'ln -s /var/java-17-openjdk/bin/java /etc/alternatives/java'}) await this.execContainerCommand({ container, cmd: 'ln -sf /var/java-17-openjdk/bin/java /usr/bin/java' })
} }
} }
async preStartSystem ({ container, username, platform }: { container: Dockerode.Container, username: string, platform: 'v1' | 'v2' }) { async preStartSystem ({ container, username, platform }: { container: Dockerode.Container, username: string, platform: 'v1' | 'v2' }) {
...@@ -172,12 +169,12 @@ class Docker { ...@@ -172,12 +169,12 @@ class Docker {
try { await this.execContainerCommand({ container, cmd: 'pm2 delete platform' }) } catch (err) {} try { await this.execContainerCommand({ container, cmd: 'pm2 delete platform' }) } catch (err) {}
if (platform === 'v1') { if (platform === 'v1') {
await this.switchJavaVersion({ container, username, version: 8 }) await this.switchJavaVersion({ container, username, version: 8 })
const statusV2 = getUserConfig(username, 'status') const statusV2 = (await getPgTableData( 'v2', username))?.status
if (statusV2) setUserConfig(username, 'status', 'created') // 如果系统在运行,回退到已创建的状态 if (statusV2) setPgTableData( 'v2', username, 'status', 'created') // 如果系统在运行,回退到已创建的状态
} else { } else {
await this.switchJavaVersion({ container, username, version: 17 }) await this.switchJavaVersion({ container, username, version: 17 })
const statusV1 = getUserConfig(username, 'v1-status') const statusV1 = (await getPgTableData( 'v1', username))?.status
if (statusV1) setUserConfig(username, 'v1-status', 'created') // 如果系统在运行,回退到已创建的状态 if (statusV1) setPgTableData( 'v1', username, 'status', 'created') // 如果系统在运行,回退到已创建的状态
} }
} }
} }
......
declare module global {
namespace NodeJS {
interface ProcessEnv {
HOST: string
NODE_ENV: string
PGUSER: string
postgres: string
}
}
}
\ No newline at end of file
import fs from 'fs' import fs from 'fs'
import path from 'path'
export const HOST = process.env.NODE_ENV === 'production' ? '192.168.0.4' : (process.env.HOST || '192.168.0.190')
export function readJson(path: string): Record<string, any> { export function readJson(path: string): Record<string, any> {
try { try {
...@@ -23,4 +24,10 @@ export function removeUnreadCharacter (str: string) { ...@@ -23,4 +24,10 @@ export function removeUnreadCharacter (str: string) {
return str.replace(/\u0001\u0000\u0000\u0000\u0000\u0000\u0000\n/g, '').replace(/[\u0000-\u0009]/g, '').replace(/[\u000B-\u001F]/g, '') return str.replace(/\u0001\u0000\u0000\u0000\u0000\u0000\u0000\n/g, '').replace(/[\u0000-\u0009]/g, '').replace(/[\u000B-\u001F]/g, '')
} }
export const SESSION_PASSWORD = '80d42cfb-1cd2-462c-8f17-e3237d9027e9' export const SESSION_PASSWORD = '80d42cfb-1cd2-462c-8f17-e3237d9027e9'
\ No newline at end of file
export function sleep (ms: number) {
return new Promise<void>((resolve, reject) => {
setTimeout(resolve, ms)
})
}
\ No newline at end of file
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
* 使用 LogsModule 包裹后,为某个安装流程增加日志处理,并且对外还是返回 Promise 对象 * 使用 LogsModule 包裹后,为某个安装流程增加日志处理,并且对外还是返回 Promise 对象
*/ */
import { setUserConfig, getUserConfig } from "./server"
import { Socket } from "socket.io"; import { Socket } from "socket.io";
let websocket: Map<string, Socket> = new Map() let websocket: Map<string, Socket> = new Map()
...@@ -18,13 +16,9 @@ export function getWebsocketIo (username: string) { ...@@ -18,13 +16,9 @@ export function getWebsocketIo (username: string) {
export default class LogUtil { export default class LogUtil {
// 根据 log 判断用户是否已经执行过 // 根据 log 判断用户是否已经执行过
static async run (username: string, log: string, cb: () => Promise<void>) { static async run (username: string, log: string, cb: () => Promise<void>) {
let key = 'InstallSteps'
let steps: string[] = getUserConfig(username, key) || []
let socket = getWebsocketIo(username) let socket = getWebsocketIo(username)
socket.emit('Log', '[progress] [1m[Loading][m ' + log + '中...\n') socket.emit('Log', '[progress] [1m[Loading][m ' + log + '中...\n')
await cb() await cb()
steps.push(log)
setUserConfig(username, key, steps)
socket.emit('Log', '[progress] [1m[Info][m ' + log + '完成\n') socket.emit('Log', '[progress] [1m[Info][m ' + log + '完成\n')
} }
static async print(username: string, log: string) { static async print(username: string, log: string) {
......
import pg from 'pg' import pg from 'pg'
export async function createPgClient ({ host, port, database }: { host: string, port: number, database?: string }) { export const PgPort = process.env.NODE_ENV?.trim() === 'production' ? 25556 : 30001
export const PgHost = process.env.NODE_ENV?.trim() === 'production' ? '192.168.0.4' : process.env.HOST
let pool: pg.Pool = new pg.Pool({
host: PgHost,
port: PgPort,
database: 'backend_helper'
})
export async function createPgClient (payload: {
user?: string
password?: string
database?: string
host?: string
port?: number
}) {
let client = new pg.Client({ let client = new pg.Client({
user: 'postgres', user: payload.user,
password: 'postgres', password: payload.password,
database, database: payload.database,
host, host: payload.host || PgHost,
port port: payload.port || PgPort
}) })
await client.connect() await client.connect()
return client return client
...@@ -15,4 +30,49 @@ export async function createPgClient ({ host, port, database }: { host: string, ...@@ -15,4 +30,49 @@ export async function createPgClient ({ host, port, database }: { host: string,
export function executePgQuery ({ client, query }: { client: pg.Client, query: string }) { export function executePgQuery ({ client, query }: { client: pg.Client, query: string }) {
return client.query(query) return client.query(query)
} }
export function createPgClientFactory (database?: string) {
return createPgClient({ database })
}
export async function stopPgClient (pgClient: pg.Client) {
await pgClient.end()
}
export type TableUser = { username: string, port: number }
export type TableV2 = {
username: string
status: 'null' | 'created' | 'stopped' | 'running' | 'creating' | 'compiling' | 'compiled'
tenants: Record<string, any>
debug: Record<string, any>
has_debugged: boolean
serverProperties: { id: any, key: string, value: string }[]
}
export type TableV1 = {
username: string
status: 'null' | 'created' | 'stopped' | 'running' | 'creating' | 'compiling' | 'compiled'
serverProperties: { id: any, key: string, value: string }[]
}
export async function getPgTableData (table: 'v1', username: string): Promise<TableV1 | undefined>;
export async function getPgTableData (table: 'v2', username: string): Promise<TableV2 | undefined>;
export async function getPgTableData (table: 'user', username: string): Promise<TableUser | undefined>;
export async function getPgTableData (table: string, username: string) {
const result = await pool.query(`select * from public.${table} where username = '${username}'`)
return result.rows[0]
}
export async function setPgTableData (table: 'user', username: string, key: keyof TableUser, value: TableUser[keyof TableUser]): Promise<void>;
export async function setPgTableData (table: 'v2', username: string, key: keyof TableV2, value: TableV2[keyof TableV2]): Promise<void>;
export async function setPgTableData (table: 'v1', username: string, key: keyof TableV1, value: TableV1[keyof TableV1]): Promise<void>;
export async function setPgTableData (table: string, username: string, key: string, value: any) {
await pool.query(`UPDATE public.${table} SET "${key}"='${value}' WHERE username='${username}'`)
}
export async function insertPgTableData (table: 'v1', username: string): Promise<void>;
export async function insertPgTableData (table: 'v2', username: string): Promise<undefined>;
export async function insertPgTableData (table: 'user', username: string): Promise<undefined>;
export async function insertPgTableData (table: string, username: string) {
await pool.query(`INSERT INTO public."${table}"(username) VALUES ('${username}');`)
}
...@@ -2,17 +2,8 @@ import path from 'path' ...@@ -2,17 +2,8 @@ import path from 'path'
import fs from 'fs' import fs from 'fs'
import lodash from 'lodash' import lodash from 'lodash'
import { Telnet } from 'telnet-client' import { Telnet } from 'telnet-client'
import { getPgTableData } from './postgres'
export function getUserConfig (username: string, config: string) {
let jsonPath = path.resolve('./public/data/' + username + '.json')
try {
let jsonStr = fs.readFileSync(jsonPath, { encoding: 'utf-8' })
let json = JSON.parse(jsonStr) || {}
return lodash.get(json, config)
} catch (err: any) {
console.log(err)
}
}
// 在目标服务器上,获取 node 可用的端口 // 在目标服务器上,获取 node 可用的端口
export async function getAvailableNodePort (ip: string, startPort = 30000) { export async function getAvailableNodePort (ip: string, startPort = 30000) {
...@@ -35,26 +26,11 @@ export async function getAvailableNodePort (ip: string, startPort = 30000) { ...@@ -35,26 +26,11 @@ export async function getAvailableNodePort (ip: string, startPort = 30000) {
return tryPortAvailable(startPort) return tryPortAvailable(startPort)
} }
export function getUserAllConfigs (username: string): Record<string, string> {
let jsonPath = path.resolve('./public/data/' + username + '.json')
let jsonStr = fs.readFileSync(jsonPath, { encoding: 'utf-8' })
let json = JSON.parse(jsonStr) || {}
return json
}
export function setUserConfig (username: string, config: string, value: any) {
let jsonPath = path.resolve('./public/data/' + username + '.json')
let jsonStr = fs.readFileSync(jsonPath, { encoding: 'utf-8' })
let json = JSON.parse(jsonStr) || {}
let newJson = lodash.set(json, config, value)
fs.writeFileSync(jsonPath, JSON.stringify(newJson, null, 2), { encoding: 'utf-8' })
}
export async function checkSshExists (username: string) { export async function checkSshExists (username: string) {
let gitEmail = username + '@greaconsulting.com' let gitEmail = username + '@greaconsulting.com'
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
// 创建 node 容器 // 创建 node 容器
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') let container = await docker.checkContainer(username + '.node')
if (!container) { if (!container) {
throw new Error('没有创建容器,请先初始化容器') throw new Error('没有创建容器,请先初始化容器')
} }
...@@ -73,42 +49,43 @@ export async function checkSshExists (username: string) { ...@@ -73,42 +49,43 @@ export async function checkSshExists (username: string) {
export async function copyAndCreateServerPropertiesV2InDocker (username: string) { export async function copyAndCreateServerPropertiesV2InDocker (username: string) {
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') let container = await docker.checkContainer( username + '.node')
if (!container) { if (!container) {
throw new Error('没有创建容器,请先初始化容器') throw new Error('没有创建容器,请先初始化容器')
} }
let backendText = fs.readFileSync(path.resolve('./files/application-server.properties'), { encoding: 'utf-8' }) let backendText = fs.readFileSync(path.resolve('./public/files/v2/application-server.properties'), { encoding: 'utf-8' })
const userDefaultSettingStr = fs.readFileSync(path.resolve('./files/default-user-setting.json'), { encoding: 'utf-8'}) const userDefaultSettingStr = fs.readFileSync(path.resolve('./public/files/default-user-setting.json'), { encoding: 'utf-8'})
const userConfigsStr = fs.readFileSync(path.resolve('./database/' + username + '.json'), { encoding: 'utf-8'}) const userConfigs = await getPgTableData('v2', username)
const userDefaultSetting = JSON.parse(userDefaultSettingStr) const userDefaultSetting = JSON.parse(userDefaultSettingStr)
const userConfigs = JSON.parse(userConfigsStr)
const fn = (f: (obj: Record<string, any>) => string) => f(userConfigs) || f(userDefaultSetting) const fn = (f: (obj: Record<string, any>) => string) => f(userConfigs!) || f(userDefaultSetting)
// postgres config // postgres config
backendText = backendText.replace(/spring.datasource.url=(.*?)\n/, `spring.datasource.url=jdbc:postgresql://${fn(o => o.postgres?.ip)}:${fn(o => o.postgres?.port)}/${fn(o => o.postgres?.database)}\n`) backendText = backendText.replace(/spring.datasource.url=(.*?)\r?\n/, `spring.datasource.url=jdbc:postgresql://${HOST}:${PgPort}/${username}\n`)
backendText = backendText.replace(/spring.datasource.username=(.*?)\n/, `spring.datasource.username=${fn(o => o.postgres?.username)}\n`) backendText = backendText.replace(/spring.datasource.username=(.*?)\r?\n/, `spring.datasource.username=${process.env.PGUSER}\n`)
backendText = backendText.replace(/spring.datasource.password=(.*?)\n/, `spring.datasource.password=${fn(o => o.postgres?.password)}\n`) backendText = backendText.replace(/spring.datasource.password=(.*?)\r?\n/, `spring.datasource.password=${process.env.PGPASSWORD}\n`)
// redis config // redis config
backendText = backendText.replace(/spring.redis.port=(.*?)\n/, `spring.redis.port=${fn(o => o.redis?.port)}\n`) backendText = backendText.replace(/spring.redis.host=(.*?)\r?\n/, `spring.redis.host=${HOST}\n`)
backendText = backendText.replace(/spring.redis.host=(.*?)\n/, `spring.redis.host=${fn(o => o.redis?.ip)}\n`)
// zookeeper config // zookeeper config
backendText = backendText.replace(/logwire.register-center-server-list=(.*?)\n/, `logwire.register-center-server-list=${fn(o => o.zookeeper?.ip)}:${fn(o => o.zookeeper?.port)}\n`) backendText = backendText.replace(/logwire.register-center-server-list=(.*?)\r?\n/, `logwire.register-center-server-list=${HOST}:2181\n`)
// rocketmq config // rocketmq config
backendText = backendText.replace(/logwire.mq.name-srv-address=(.*?)\n/, `logwire.mq.name-srv-address=${fn(o => o.rocketmq?.ip)}:${fn(o => o.rocketmq?.port)}\n`) backendText = backendText.replace(/logwire.mq.name-srv-address=(.*?)\r?\n/, `logwire.mq.name-srv-address=${HOST}:9876\n`)
// tenants config // tenants config
backendText = backendText.replace(/logwire.tenants\[0\].id=(.*?)\n/, `logwire.tenants[0].id=${fn(o => o.tenants?.id)}\n`) backendText = backendText.replace(/logwire.tenants\[0\].id=(.*?)\r?\n/, `logwire.tenants[0].id=${fn(o => o.tenants?.id)}\n`)
backendText = backendText.replace(/logwire.tenants\[0\].host=(.*?)\n/, `logwire.tenants[0].host=${fn(o => o.tenants?.host)}\n`) backendText = backendText.replace(/logwire.tenants\[0\].host=(.*?)\r?\n/, `logwire.tenants[0].host=${fn(o => o.tenants?.host)}\n`)
backendText = backendText.replace(/logwire.tenants\[0\].database-schema=(.*?)\n/, `logwire.tenants[0].database-schema=${fn(o => o.tenants?.["database-schema"])}\n`) backendText = backendText.replace(/logwire.tenants\[0\].database-schema=(.*?)\r?\n/, `logwire.tenants[0].database-schema=${fn(o => o.tenants?.["database-schema"])}\n`)
backendText = backendText.replace(/logwire.tenants\[0\].primary-namespace=(.*?)\n/, `logwire.tenants[0].primary-namespace=${fn(o => o.tenants?.["primary-namespace"])}\n`) backendText = backendText.replace(/logwire.tenants\[0\].primary-namespace=(.*?)\r?\n/, `logwire.tenants[0].primary-namespace=${fn(o => o.tenants?.["primary-namespace"])}\n`)
// 设计器配置
backendText = backendText.replace(/logwire.tenants\[0\].designer.trust-ip=(.*?)\r?\n/, `logwire.tenants[0].designer.trust-ip=${HOST}\n`)
// user personal config // user personal config
const customServerProperties: { key: string, value: string }[] = userConfigs.serverProperties || [] const customServerProperties: { key: string, value: string }[] = userConfigs!.serverProperties || []
customServerProperties.forEach(item => { customServerProperties.forEach(item => {
const RegExpKey = item.key.replaceAll('[', '\\[').replaceAll(']', '\\]').replace('.', '\\.') const RegExpKey = item.key.replaceAll('[', '\\[').replaceAll(']', '\\]').replace('.', '\\.')
if (backendText.match(new RegExp(`#${RegExpKey}=(.*?)\n`))) { if (backendText.match(new RegExp(`#${RegExpKey}=(.*?)\n`))) {
...@@ -125,51 +102,38 @@ export async function copyAndCreateServerPropertiesV2InDocker (username: string) ...@@ -125,51 +102,38 @@ export async function copyAndCreateServerPropertiesV2InDocker (username: string)
export async function copyAndCreateGatewayPropertiesV2InDocker (username: string) { export async function copyAndCreateGatewayPropertiesV2InDocker (username: string) {
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') let container = await docker.checkContainer( username + '.node')
if (!container) { if (!container) {
throw new Error('没有创建容器,请先初始化容器') throw new Error('没有创建容器,请先初始化容器')
} }
let gatewayText = fs.readFileSync(path.resolve('./files/application-gateway.properties'), { encoding: 'utf-8' }) let gatewayText = fs.readFileSync(path.resolve('./public/files/v2/application-gateway.properties'), { encoding: 'utf-8' })
const userDefaultSettingStr = fs.readFileSync(path.resolve('./files/default-user-setting.json'), { encoding: 'utf-8'})
const userConfigsStr = fs.readFileSync(path.resolve('./database/' + username + '.json'), { encoding: 'utf-8'})
const userDefaultSetting = JSON.parse(userDefaultSettingStr)
const userConfigs = JSON.parse(userConfigsStr)
const fn = (f: (obj: Record<string, any>) => string) => f(userConfigs) || f(userDefaultSetting)
// zookeeper config // zookeeper config
gatewayText = gatewayText.replace(/gateway.register-center-server-list=(.*?)\n/, `gateway.register-center-server-list=${fn(o => o.zookeeper?.ip)}:${fn(o => o.zookeeper?.port)}\n`) gatewayText = gatewayText.replace(/gateway.register-center-server-list=(.*?)\r?\n/, `gateway.register-center-server-list=${HOST}:2181\n`)
// rocketmq config // rocketmq config
gatewayText = gatewayText.replace(/gateway.mq.name-srv-address=(.*?)\n/, `gateway.mq.name-srv-address=${fn(o => o.rocketmq?.ip)}:${fn(o => o.rocketmq?.port)}`) gatewayText = gatewayText.replace(/gateway.mq.name-srv-address=(.*?)\r?\n/, `gateway.mq.name-srv-address=${HOST}:9876`)
await docker.writeFile({ container, path: '/var/logwire-backend/build-output/gateway/config/application-gateway.properties', text: '\'' + gatewayText.replace(/'/g, '"') + '\'' }) await docker.writeFile({ container, path: '/var/logwire-backend/build-output/gateway/config/application-gateway.properties', text: '\'' + gatewayText.replace(/'/g, '"') + '\'' })
} }
export async function copyAndCreateServerPropertiesV1InDocker (username: string) { export async function copyAndCreateServerPropertiesV1InDocker (username: string) {
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') let container = await docker.checkContainer( username + '.node')
if (!container) { if (!container) {
throw new Error('没有创建容器,请先初始化容器') throw new Error('没有创建容器,请先初始化容器')
} }
let backendText = fs.readFileSync(path.resolve('./files/v1/application.properties'), { encoding: 'utf-8' }) let backendText = fs.readFileSync(path.resolve('./public/files/v1/application.properties'), { encoding: 'utf-8' })
const userDefaultSettingStr = fs.readFileSync(path.resolve('./files/default-user-setting.json'), { encoding: 'utf-8'}) const userConfigs = await getPgTableData('v1', username)
const userConfigsStr = fs.readFileSync(path.resolve('./database/' + username + '.json'), { encoding: 'utf-8'})
const userDefaultSetting = JSON.parse(userDefaultSettingStr)
const userConfigs = JSON.parse(userConfigsStr)
const fn = (f: (obj: Record<string, any>) => string) => f(userConfigs) || f(userDefaultSetting)
// postgres config // postgres config
backendText = backendText.replace(/spring.datasource.url=(.*?)\n/, `spring.datasource.url=jdbc:postgresql://${fn(o => o.postgresV1?.ip)}:${fn(o => o.postgresV1?.port)}/${fn(o => o.postgresV1?.database)}\n`) backendText = backendText.replace(/spring.datasource.url=(.*?)\r?\n/, `spring.datasource.url=jdbc:postgresql://${HOST}:${PgPort}/${username + '_v1'}\n`)
backendText = backendText.replace(/spring.datasource.username=(.*?)\n/, `spring.datasource.username=${fn(o => o.postgresV1?.username)}\n`) backendText = backendText.replace(/spring.datasource.username=(.*?)\r?\n/, `spring.datasource.username=${process.env.PGUSER}\n`)
backendText = backendText.replace(/spring.datasource.password=(.*?)\n/, `spring.datasource.password=${fn(o => o.postgresV1?.password)}\n`) backendText = backendText.replace(/spring.datasource.password=(.*?)\r?\n/, `spring.datasource.password=${process.env.PGPASSWORD}\n`)
// redis config // redis config
backendText = backendText.replace(/spring.redis.port=(.*?)\n/, `spring.redis.port=${fn(o => o.redisV1?.port)}\n`) backendText = backendText.replace(/spring.redis.host=(.*?)\r?\n/, `spring.redis.host=${HOST}\n`)
backendText = backendText.replace(/spring.redis.host=(.*?)\n/, `spring.redis.host=${fn(o => o.redisV1?.ip)}\n`)
// user personal config // user personal config
const customServerProperties: { key: string, value: string }[] = userConfigs.serverPropertiesV1 || [] const customServerProperties: { key: string, value: string }[] = userConfigs!.serverProperties || []
customServerProperties.forEach(item => { customServerProperties.forEach(item => {
const RegExpKey = item.key.replaceAll('[', '\\[').replaceAll(']', '\\]').replace('.', '\\.') const RegExpKey = item.key.replaceAll('[', '\\[').replaceAll(']', '\\]').replace('.', '\\.')
if (backendText.match(new RegExp(`#${RegExpKey}=(.*?)\n`))) { if (backendText.match(new RegExp(`#${RegExpKey}=(.*?)\n`))) {
...@@ -186,15 +150,14 @@ export async function copyAndCreateServerPropertiesV1InDocker (username: string) ...@@ -186,15 +150,14 @@ export async function copyAndCreateServerPropertiesV1InDocker (username: string)
export async function copyAndCreateNginxConfInDocker (username: string) { export async function copyAndCreateNginxConfInDocker (username: string) {
let docker = createDockerFactory(username) let docker = createDockerFactory(username)
let container = await docker.checkContainer('logwire_backend_helper.' + username + '.node') let container = await docker.checkContainer( username + '.node')
if (!container) { if (!container) {
throw new Error('没有创建容器,请先初始化容器') throw new Error('没有创建容器,请先初始化容器')
} }
const userDefaultSettingStr = fs.readFileSync(path.resolve('./files/default-user-setting.json'), { encoding: 'utf-8'}) const userDefaultSettingStr = fs.readFileSync(path.resolve('./public/files/default-user-setting.json'), { encoding: 'utf-8'})
const userConfigsStr = fs.readFileSync(path.resolve('./database/' + username + '.json'), { encoding: 'utf-8'})
const userDefaultSetting = JSON.parse(userDefaultSettingStr) const userDefaultSetting = JSON.parse(userDefaultSettingStr)
const userConfigs = JSON.parse(userConfigsStr) const userConfigs = await getPgTableData('v2', username)
const nginxConfigStr = fs.readFileSync(path.resolve('./files/nginx.conf'), { encoding: 'utf-8' }) const nginxConfigStr = fs.readFileSync(path.resolve('./public/files/nginx.conf'), { encoding: 'utf-8' })
// 修改调试端口的 NGINX 配置 // 修改调试端口的 NGINX 配置
const replacedNginxConfig = nginxConfigStr.replace(/192.168.1.94/, userConfigs?.debug?.host || userDefaultSetting.debug.host) const replacedNginxConfig = nginxConfigStr.replace(/192.168.1.94/, userConfigs?.debug?.host || userDefaultSetting.debug.host)
......
...@@ -2,9 +2,9 @@ import { defineStore } from 'pinia' ...@@ -2,9 +2,9 @@ import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => { export const useUserStore = defineStore('user', () => {
const username = ref('') const username = ref('')
const config = ref({}) const config = ref<{ port?: string }>({})
function setUserName (name: string) { function setUserName (name: string, p?: string) {
username.value = name username.value = name
} }
function setUserConfig (name: string, obj: Object) { function setUserConfig (name: string, obj: Object) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment