feat: sync streams

This commit is contained in:
Jacky 2025-02-25 15:53:08 +08:00
parent 082ccc18bc
commit 34fa4eb204
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
27 changed files with 788 additions and 557 deletions

View file

@ -1,12 +1,13 @@
package streams
import (
"net/http"
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin"
"github.com/uozi-tech/cosy"
"net/http"
)
func AdvancedEdit(c *gin.Context) {
@ -21,7 +22,7 @@ func AdvancedEdit(c *gin.Context) {
name := c.Param("name")
path := nginx.GetConfPath("streams-available", name)
s := query.Site
s := query.Stream
_, err := s.Where(s.Path.Eq(path)).FirstOrCreate()
if err != nil {
@ -39,5 +40,4 @@ func AdvancedEdit(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
}

View file

@ -6,6 +6,7 @@ func InitRouter(r *gin.RouterGroup) {
r.GET("streams", GetStreams)
r.GET("streams/:name", GetStream)
r.POST("streams/:name", SaveStream)
r.POST("streams/:name/rename", RenameStream)
r.POST("streams/:name/enable", EnableStream)
r.POST("streams/:name/disable", DisableStream)
r.POST("streams/:name/advance", AdvancedEdit)

View file

@ -1,18 +1,19 @@
package streams
import (
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/config"
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin"
"github.com/sashabaranov/go-openai"
"github.com/uozi-tech/cosy"
"net/http"
"os"
"strings"
"time"
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/config"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/internal/stream"
"github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin"
"github.com/sashabaranov/go-openai"
"github.com/uozi-tech/cosy"
)
type Stream struct {
@ -24,6 +25,7 @@ type Stream struct {
ChatGPTMessages []openai.ChatCompletionMessage `json:"chatgpt_messages,omitempty"`
Tokenized *nginx.NgxConfig `json:"tokenized,omitempty"`
Filepath string `json:"filepath"`
SyncNodeIDs []uint64 `json:"sync_node_ids" gorm:"serializer:json"`
}
func GetStreams(c *gin.Context) {
@ -32,14 +34,12 @@ func GetStreams(c *gin.Context) {
sort := c.DefaultQuery("sort", "desc")
configFiles, err := os.ReadDir(nginx.GetConfPath("streams-available"))
if err != nil {
api.ErrHandler(c, err)
return
}
enabledConfig, err := os.ReadDir(nginx.GetConfPath("streams-enabled"))
if err != nil {
api.ErrHandler(c, err)
return
@ -77,15 +77,8 @@ func GetStreams(c *gin.Context) {
}
func GetStream(c *gin.Context) {
rewriteName, ok := c.Get("rewriteConfigFileName")
name := c.Param("name")
// for modify filename
if ok {
name = rewriteName.(string)
}
path := nginx.GetConfPath("streams-available", name)
file, err := os.Stat(path)
if os.IsNotExist(err) {
@ -114,14 +107,13 @@ func GetStream(c *gin.Context) {
}
s := query.Stream
stream, err := s.Where(s.Path.Eq(path)).FirstOrInit()
streamModel, err := s.Where(s.Path.Eq(path)).FirstOrCreate()
if err != nil {
api.ErrHandler(c, err)
return
}
if stream.Advanced {
if streamModel.Advanced {
origContent, err := os.ReadFile(path)
if err != nil {
api.ErrHandler(c, err)
@ -130,12 +122,13 @@ func GetStream(c *gin.Context) {
c.JSON(http.StatusOK, Stream{
ModifiedAt: file.ModTime(),
Advanced: stream.Advanced,
Advanced: streamModel.Advanced,
Enabled: enabled,
Name: name,
Config: string(origContent),
ChatGPTMessages: chatgpt.Content,
Filepath: path,
SyncNodeIDs: streamModel.SyncNodeIDs,
})
return
}
@ -149,207 +142,86 @@ func GetStream(c *gin.Context) {
c.JSON(http.StatusOK, Stream{
ModifiedAt: file.ModTime(),
Advanced: stream.Advanced,
Advanced: streamModel.Advanced,
Enabled: enabled,
Name: name,
Config: nginxConfig.FmtCode(),
Tokenized: nginxConfig,
ChatGPTMessages: chatgpt.Content,
Filepath: path,
SyncNodeIDs: streamModel.SyncNodeIDs,
})
}
func SaveStream(c *gin.Context) {
name := c.Param("name")
if name == "" {
c.JSON(http.StatusNotAcceptable, gin.H{
"message": "param name is empty",
})
return
}
var json struct {
Name string `json:"name" binding:"required"`
Content string `json:"content" binding:"required"`
Overwrite bool `json:"overwrite"`
Content string `json:"content" binding:"required"`
SyncNodeIDs []uint64 `json:"sync_node_ids"`
Overwrite bool `json:"overwrite"`
}
if !cosy.BindAndValid(c, &json) {
return
}
path := nginx.GetConfPath("streams-available", name)
if !json.Overwrite && helper.FileExists(path) {
c.JSON(http.StatusNotAcceptable, gin.H{
"message": "File exists",
})
return
}
err := os.WriteFile(path, []byte(json.Content), 0644)
err := stream.Save(name, json.Content, json.Overwrite, json.SyncNodeIDs)
if err != nil {
api.ErrHandler(c, err)
return
}
enabledConfigFilePath := nginx.GetConfPath("streams-enabled", name)
// rename the config file if needed
if name != json.Name {
newPath := nginx.GetConfPath("streams-available", json.Name)
s := query.Stream
_, err = s.Where(s.Path.Eq(path)).Update(s.Path, newPath)
// check if dst file exists, do not rename
if helper.FileExists(newPath) {
c.JSON(http.StatusNotAcceptable, gin.H{
"message": "File exists",
})
return
}
// recreate a soft link
if helper.FileExists(enabledConfigFilePath) {
_ = os.Remove(enabledConfigFilePath)
enabledConfigFilePath = nginx.GetConfPath("streams-enabled", json.Name)
err = os.Symlink(newPath, enabledConfigFilePath)
if err != nil {
api.ErrHandler(c, err)
return
}
}
err = os.Rename(path, newPath)
if err != nil {
api.ErrHandler(c, err)
return
}
name = json.Name
c.Set("rewriteConfigFileName", name)
}
enabledConfigFilePath = nginx.GetConfPath("streams-enabled", name)
if helper.FileExists(enabledConfigFilePath) {
// Test nginx configuration
output := nginx.TestConf()
if nginx.GetLogLevel(output) > nginx.Warn {
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
return
}
output = nginx.Reload()
if nginx.GetLogLevel(output) > nginx.Warn {
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
return
}
}
GetStream(c)
}
func EnableStream(c *gin.Context) {
configFilePath := nginx.GetConfPath("streams-available", c.Param("name"))
enabledConfigFilePath := nginx.GetConfPath("streams-enabled", c.Param("name"))
_, err := os.Stat(configFilePath)
err := stream.Enable(c.Param("name"))
if err != nil {
api.ErrHandler(c, err)
return
}
if _, err = os.Stat(enabledConfigFilePath); os.IsNotExist(err) {
err = os.Symlink(configFilePath, enabledConfigFilePath)
if err != nil {
api.ErrHandler(c, err)
return
}
}
// Test nginx config, if not pass, then disable the stream.
output := nginx.TestConf()
if nginx.GetLogLevel(output) > nginx.Warn {
_ = os.Remove(enabledConfigFilePath)
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
return
}
output = nginx.Reload()
if nginx.GetLogLevel(output) > nginx.Warn {
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
}
func DisableStream(c *gin.Context) {
enabledConfigFilePath := nginx.GetConfPath("streams-enabled", c.Param("name"))
_, err := os.Stat(enabledConfigFilePath)
err := stream.Disable(c.Param("name"))
if err != nil {
api.ErrHandler(c, err)
return
}
err = os.Remove(enabledConfigFilePath)
if err != nil {
api.ErrHandler(c, err)
return
}
output := nginx.Reload()
if nginx.GetLogLevel(output) > nginx.Warn {
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
}
func DeleteStream(c *gin.Context) {
var err error
name := c.Param("name")
availablePath := nginx.GetConfPath("streams-available", name)
enabledPath := nginx.GetConfPath("streams-enabled", name)
if _, err = os.Stat(availablePath); os.IsNotExist(err) {
c.JSON(http.StatusNotFound, gin.H{
"message": "stream not found",
})
return
}
if _, err = os.Stat(enabledPath); err == nil {
c.JSON(http.StatusNotAcceptable, gin.H{
"message": "stream is enabled",
})
return
}
if err = os.Remove(availablePath); err != nil {
err := stream.Delete(c.Param("name"))
if err != nil {
api.ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
}
func RenameStream(c *gin.Context) {
oldName := c.Param("name")
var json struct {
NewName string `json:"new_name"`
}
if !cosy.BindAndValid(c, &json) {
return
}
err := stream.Rename(oldName, json.NewName)
if err != nil {
api.ErrHandler(c, err)
return
}

View file

@ -12,6 +12,7 @@ export interface Stream {
config: string
chatgpt_messages: ChatComplicationMessage[]
tokenized?: NgxConfig
sync_node_ids: number[]
}
class StreamCurd extends Curd<Stream> {
@ -31,6 +32,10 @@ class StreamCurd extends Curd<Stream> {
advance_mode(name: string, data: { advanced: boolean }) {
return http.post(`${this.baseUrl}/${name}/advance`, data)
}
rename(name: string, newName: string) {
return http.post(`${this.baseUrl}/${name}/rename`, { new_name: newName })
}
}
const stream = new StreamCurd('/streams')

View file

@ -32,6 +32,12 @@ function newSSE() {
s.onmessage = (e: SSEvent) => {
data.value = JSON.parse(e.data)
nextTick(() => {
data_map.value = data.value.reduce((acc, node) => {
acc[node.id] = node
return acc
}, {} as Record<number, Environment>)
})
}
// reconnect

View file

@ -0,0 +1,5 @@
export default {
40401: () => $gettext('Stream not found'),
50001: () => $gettext('Destination file already exists'),
50002: () => $gettext('Stream is enabled'),
}

View file

@ -3,6 +3,7 @@ export default {
40303: () => $gettext('User banned'),
40304: () => $gettext('Invalid otp code'),
40305: () => $gettext('Invalid recovery code'),
40306: () => $gettext('Legacy recovery code not allowed since totp is not enabled'),
50000: () => $gettext('WebAuthn settings are not configured'),
50001: () => $gettext('User not enabled otp as 2fa'),
50002: () => $gettext('Otp or recovery code empty'),

View file

@ -91,7 +91,7 @@ export const routes: RouteRecordRaw[] = [
},
},
{
path: 'stream/:name',
path: 'streams/:name',
name: 'Edit Stream',
component: () => import('@/views/stream/StreamEdit.vue'),
meta: {

View file

@ -23,7 +23,7 @@ watch(route, () => {
name.value = route.params?.name?.toString() ?? ''
})
const ngx_config: NgxConfig = reactive({
const ngxConfig: NgxConfig = reactive({
name: '',
upstreams: [],
servers: [],
@ -31,81 +31,81 @@ const ngx_config: NgxConfig = reactive({
const enabled = ref(false)
const configText = ref('')
const advance_mode_ref = ref(false)
const advanceModeRef = ref(false)
const saving = ref(false)
const filename = ref('')
const filepath = ref('')
const parse_error_status = ref(false)
const parse_error_message = ref('')
const data = ref({})
const parseErrorStatus = ref(false)
const parseErrorMessage = ref('')
const data = ref<Stream>({} as Stream)
init()
const advance_mode = computed({
const advanceMode = computed({
get() {
return advance_mode_ref.value || parse_error_status.value
return advanceModeRef.value || parseErrorStatus.value
},
set(v: boolean) {
advance_mode_ref.value = v
advanceModeRef.value = v
},
})
const history_chatgpt_record = ref([]) as Ref<ChatComplicationMessage[]>
const historyChatgptRecord = ref([]) as Ref<ChatComplicationMessage[]>
function handle_response(r: Stream) {
function handleResponse(r: Stream) {
if (r.advanced)
advance_mode.value = true
advanceMode.value = true
if (r.advanced)
advance_mode.value = true
advanceMode.value = true
parse_error_status.value = false
parse_error_message.value = ''
parseErrorStatus.value = false
parseErrorMessage.value = ''
filename.value = r.name
filepath.value = r.filepath
configText.value = r.config
enabled.value = r.enabled
history_chatgpt_record.value = r.chatgpt_messages
historyChatgptRecord.value = r.chatgpt_messages
data.value = r
Object.assign(ngx_config, r.tokenized)
Object.assign(ngxConfig, r.tokenized)
}
function init() {
if (name.value) {
stream.get(name.value).then(r => {
handle_response(r)
}).catch(handle_parse_error)
handleResponse(r)
}).catch(handleParseError)
}
else {
history_chatgpt_record.value = []
historyChatgptRecord.value = []
}
}
function handle_parse_error(e: { error?: string, message: string }) {
function handleParseError(e: { error?: string, message: string }) {
console.error(e)
parse_error_status.value = true
parse_error_message.value = e.message
parseErrorStatus.value = true
parseErrorMessage.value = e.message
config.get(`streams-available/${name.value}`).then(r => {
configText.value = r.content
})
}
function on_mode_change(advanced: CheckedType) {
function onModeChange(advanced: CheckedType) {
stream.advance_mode(name.value, { advanced: advanced as boolean }).then(() => {
advance_mode.value = advanced as boolean
advanceMode.value = advanced as boolean
if (advanced) {
build_config()
buildConfig()
}
else {
return ngx.tokenize_config(configText.value).then(r => {
Object.assign(ngx_config, r)
}).catch(handle_parse_error)
Object.assign(ngxConfig, r)
}).catch(handleParseError)
}
})
}
async function build_config() {
return ngx.build_config(ngx_config).then(r => {
async function buildConfig() {
return ngx.build_config(ngxConfig).then(r => {
configText.value = r.content
})
}
@ -113,9 +113,9 @@ async function build_config() {
async function save() {
saving.value = true
if (!advance_mode.value) {
if (!advanceMode.value) {
try {
await build_config()
await buildConfig()
}
catch {
saving.value = false
@ -129,22 +129,23 @@ async function save() {
name: filename.value || name.value,
content: configText.value,
overwrite: true,
sync_node_ids: data.value?.sync_node_ids,
}).then(r => {
handle_response(r)
handleResponse(r)
router.push({
path: `/stream/${filename.value}`,
path: `/streams/${filename.value}`,
query: route.query,
})
message.success($gettext('Saved successfully'))
}).catch(handle_parse_error).finally(() => {
}).catch(handleParseError).finally(() => {
saving.value = false
})
}
provide('save_config', save)
provide('configText', configText)
provide('ngx_config', ngx_config)
provide('history_chatgpt_record', history_chatgpt_record)
provide('ngx_config', ngxConfig)
provide('history_chatgpt_record', historyChatgptRecord)
provide('enabled', enabled)
provide('name', name)
provide('filename', filename)
@ -180,12 +181,12 @@ provide('data', data)
<div class="switch">
<ASwitch
size="small"
:disabled="parse_error_status"
:checked="advance_mode"
@change="on_mode_change"
:disabled="parseErrorStatus"
:checked="advanceMode"
@change="onModeChange"
/>
</div>
<template v-if="advance_mode">
<template v-if="advanceMode">
<div>{{ $gettext('Advance Mode') }}</div>
</template>
<template v-else>
@ -196,16 +197,16 @@ provide('data', data)
<Transition name="slide-fade">
<div
v-if="advance_mode"
v-if="advanceMode"
key="advance"
>
<div
v-if="parse_error_status"
v-if="parseErrorStatus"
class="parse-error-alert-wrapper"
>
<AAlert
:message="$gettext('Nginx Configuration Parse Error')"
:description="parse_error_message"
:description="parseErrorMessage"
type="error"
show-icon
/>

View file

@ -18,7 +18,6 @@ const columns: Column[] = [{
type: input,
},
search: true,
width: 200,
}, {
title: () => $gettext('Status'),
dataIndex: 'enabled',
@ -38,17 +37,18 @@ const columns: Column[] = [{
},
sorter: true,
pithy: true,
width: 100,
width: 200,
}, {
title: () => $gettext('Updated at'),
dataIndex: 'modified_at',
customRender: datetime,
sorter: true,
pithy: true,
width: 200,
}, {
title: () => $gettext('Action'),
dataIndex: 'action',
width: 120,
width: 250,
fixed: 'right',
}]
@ -132,7 +132,7 @@ function handleAddStream() {
disable-view
:scroll-x="800"
@click-edit="r => $router.push({
path: `/stream/${r}`,
path: `/streams/${r}`,
})"
>
<template #actions="{ record }">

View file

@ -0,0 +1,61 @@
<script setup lang="ts">
import stream from '@/api/stream'
import { message } from 'ant-design-vue'
const props = defineProps<{
name: string
}>()
const router = useRouter()
const modify = ref(false)
const buffer = ref('')
const loading = ref(false)
onMounted(() => {
buffer.value = props.name
})
function clickModify() {
modify.value = true
}
function save() {
loading.value = true
stream.rename(props.name, buffer.value).then(() => {
modify.value = false
message.success($gettext('Renamed successfully'))
router.push({
path: `/streams/${buffer.value}`,
})
}).finally(() => {
loading.value = false
})
}
</script>
<template>
<div v-if="!modify" class="flex items-center">
<div class="mr-2">
{{ buffer }}
</div>
<div>
<AButton type="link" size="small" @click="clickModify">
{{ $gettext('Rename') }}
</AButton>
</div>
</div>
<div v-else>
<AInput v-model:value="buffer">
<template #suffix>
<AButton :disabled="buffer === name" type="link" size="small" :loading @click="save">
{{ $gettext('Save') }}
</AButton>
</template>
</AInput>
</div>
</template>
<style scoped lang="less">
</style>

View file

@ -1,120 +0,0 @@
<script setup lang="ts">
import type { Ref } from 'vue'
import stream from '@/api/stream'
import NodeSelector from '@/components/NodeSelector/NodeSelector.vue'
import { InfoCircleOutlined } from '@ant-design/icons-vue'
import { Modal, notification } from 'ant-design-vue'
const node_map = reactive({})
const target = ref([])
const overwrite = ref(false)
const enabled = ref(false)
const name = inject('name') as Ref<string>
const [modal, ContextHolder] = Modal.useModal()
function deploy() {
modal.confirm({
title: () => $ngettext('Do you want to deploy this file to remote server?', 'Do you want to deploy this file to remote servers?', target.value.length),
mask: false,
centered: true,
okText: $gettext('OK'),
cancelText: $gettext('Cancel'),
onOk() {
target.value.forEach(id => {
const node_name = node_map[id]
// get source content
stream.get(name.value).then(r => {
stream.save(name.value, {
name: name.value,
content: r.config,
overwrite: overwrite.value,
}, { headers: { 'X-Node-ID': id } }).then(async () => {
notification.success({
message: $gettext('Deploy successfully'),
description:
$gettext('Deploy %{conf_name} to %{node_name} successfully', { conf_name: name.value, node_name }),
})
if (enabled.value) {
stream.enable(name.value).then(() => {
notification.success({
message: $gettext('Enable successfully'),
description:
$gettext('Enable %{conf_name} in %{node_name} successfully', { conf_name: name.value, node_name }),
})
}).catch(e => {
notification.error({
message: $gettext('Enable %{conf_name} in %{node_name} failed', {
conf_name: name.value,
node_name,
}),
description: $gettext(e?.message ?? 'Server error'),
})
})
}
}).catch(e => {
notification.error({
message: $gettext('Deploy %{conf_name} to %{node_name} failed', {
conf_name: name.value,
node_name,
}),
description: $gettext(e?.message ?? 'Server error'),
})
})
})
})
},
})
}
</script>
<template>
<ContextHolder />
<NodeSelector
v-model:target="target"
hidden-local
:map="node_map"
/>
<div class="node-deploy-control">
<ACheckbox v-model:checked="enabled">
{{ $gettext('Enable') }}
</ACheckbox>
<div class="overwrite">
<ACheckbox v-model:checked="overwrite">
{{ $gettext('Overwrite') }}
</ACheckbox>
<ATooltip placement="bottom">
<template #title>
{{ $gettext('Overwrite exist file') }}
</template>
<InfoCircleOutlined />
</ATooltip>
</div>
<AButton
:disabled="target.length === 0"
type="primary"
ghost
@click="deploy"
>
{{ $gettext('Deploy') }}
</AButton>
</div>
</template>
<style scoped lang="less">
.overwrite {
margin-right: 15px;
span {
color: #9b9b9b;
}
}
.node-deploy-control {
display: flex;
justify-content: flex-end;
margin-top: 10px;
align-items: center;
}
</style>

View file

@ -5,18 +5,18 @@ import type { CheckedType } from '@/types'
import type { Ref } from 'vue'
import stream from '@/api/stream'
import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
import NodeSelector from '@/components/NodeSelector/NodeSelector.vue'
import { formatDateTime } from '@/lib/helper'
import { useSettingsStore } from '@/pinia'
import Deploy from '@/views/stream/components/Deploy.vue'
import { message, Modal } from 'ant-design-vue'
import ConfigName from './ConfigName.vue'
const settings = useSettingsStore()
const configText = inject('configText') as Ref<string>
const enabled = inject('enabled') as Ref<boolean>
const name = inject('name') as Ref<string>
const history_chatgpt_record = inject('history_chatgpt_record') as Ref<ChatComplicationMessage[]>
const filename = inject('filename') as Ref<string>
const historyChatgptRecord = inject('history_chatgpt_record') as Ref<ChatComplicationMessage[]>
const filepath = inject('filepath') as Ref<string>
const data = inject('data') as Ref<Stream>
@ -42,7 +42,7 @@ function disable() {
})
}
function on_change_enabled(checked: CheckedType) {
function onChangeEnabled(checked: CheckedType) {
modal.confirm({
title: checked ? $gettext('Do you want to enable this stream?') : $gettext('Do you want to disable this stream?'),
mask: false,
@ -76,11 +76,11 @@ function on_change_enabled(checked: CheckedType) {
<AFormItem :label="$gettext('Enabled')">
<ASwitch
:checked="enabled"
@change="on_change_enabled"
@change="onChangeEnabled"
/>
</AFormItem>
<AFormItem :label="$gettext('Name')">
<AInput v-model:value="filename" />
<ConfigName :name="name" />
</AFormItem>
<AFormItem :label="$gettext('Updated at')">
{{ formatDateTime(data.modified_at) }}
@ -89,16 +89,20 @@ function on_change_enabled(checked: CheckedType) {
<ACollapsePanel
v-if="!settings.is_remote"
key="2"
:header="$gettext('Deploy')"
:header="$gettext('Sync')"
>
<Deploy />
<NodeSelector
v-model:target="data.sync_node_ids"
class="mb-4"
hidden-local
/>
</ACollapsePanel>
<ACollapsePanel
key="3"
header="ChatGPT"
>
<ChatGPT
v-model:history-messages="history_chatgpt_record"
v-model:history-messages="historyChatgptRecord"
:content="configText"
:path="filepath"
/>

View file

@ -1,35 +1,22 @@
<script setup lang="ts">
import stream from '@/api/stream'
import NodeSelector from '@/components/NodeSelector/NodeSelector.vue'
import gettext from '@/gettext'
import { useSettingsStore } from '@/pinia'
import { Form, message, notification } from 'ant-design-vue'
import { Form, message } from 'ant-design-vue'
const props = defineProps<{
visible: boolean
name: string
}>()
const emit = defineEmits(['update:visible', 'duplicated'])
const emit = defineEmits(['duplicated'])
const settings = useSettingsStore()
const show = computed({
get() {
return props.visible
},
set(v) {
emit('update:visible', v)
},
})
const visible = defineModel<boolean>('visible')
interface Model {
name: string // site name
target: number[] // ids of deploy targets
}
const modelRef: Model = reactive({ name: '', target: [] })
const modelRef: Model = reactive({ name: '' })
const rulesRef = reactive({
name: [
@ -39,73 +26,29 @@ const rulesRef = reactive({
+ 'this will be used as the filename of the new configuration!'),
},
],
target: [
{
required: true,
message: () => $gettext('Please select at least one node!'),
},
],
})
const { validate, validateInfos, clearValidate } = Form.useForm(modelRef, rulesRef)
const loading = ref(false)
const node_map: Record<number, string> = reactive({})
function onSubmit() {
validate().then(async () => {
loading.value = true
modelRef.target.forEach(id => {
if (id === 0) {
stream.duplicate(props.name, { name: modelRef.name }).then(() => {
message.success($gettext('Duplicate to local successfully'))
show.value = false
emit('duplicated')
}).catch(e => {
message.error($gettext(e?.message ?? 'Server error'))
})
}
else {
// get source content
stream.get(props.name).then(r => {
stream.save(modelRef.name, {
name: modelRef.name,
content: r.config,
}, { headers: { 'X-Node-ID': id } }).then(() => {
notification.success({
message: $gettext('Duplicate successfully'),
description:
$gettext('Duplicate %{conf_name} to %{node_name} successfully', { conf_name: props.name, node_name: node_map[id] }),
})
}).catch(e => {
notification.error({
message: $gettext('Duplicate failed'),
description: $gettext(e?.message ?? 'Server error'),
})
})
if (r.enabled) {
stream.enable(modelRef.name, { headers: { 'X-Node-ID': id } }).then(() => {
notification.success({
message: $gettext('Enabled successfully'),
})
})
}
})
}
stream.duplicate(props.name, { name: modelRef.name }).then(() => {
message.success($gettext('Duplicate to local successfully'))
visible.value = false
emit('duplicated')
}).finally(() => {
loading.value = false
})
loading.value = false
})
}
watch(() => props.visible, v => {
watch(visible, v => {
if (v) {
modelRef.name = props.name // default with source name
modelRef.target = [0]
nextTick(() => clearValidate())
}
})
@ -117,7 +60,7 @@ watch(() => gettext.current, () => {
<template>
<AModal
v-model:open="show"
v-model:open="visible"
:title="$gettext('Duplicate')"
:confirm-loading="loading"
:mask="false"
@ -130,16 +73,6 @@ watch(() => gettext.current, () => {
>
<AInput v-model:value="modelRef.name" />
</AFormItem>
<AFormItem
v-if="!settings.is_remote"
:label="$gettext('Target')"
v-bind="validateInfos.target"
>
<NodeSelector
v-model:target="modelRef.target"
v-model:map="node_map"
/>
</AFormItem>
</AForm>
</AModal>
</template>

View file

@ -18,7 +18,7 @@ func main() {
// specify the output directory (default: "./query")
// ### if you want to query without context constrain, set mode gen.WithoutContext ###
g := gen.NewGenerator(gen.Config{
OutPath: "../../query",
OutPath: "query",
Mode: gen.WithoutContext | gen.WithDefaultQuery,
//if you want the nullable field generation property to be pointer type, set FieldNullable true
FieldNullable: true,

1
go.mod
View file

@ -100,7 +100,6 @@ require (
github.com/civo/civogo v0.3.94 // indirect
github.com/cloudflare/cloudflare-go v0.115.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cpu/goacmedns v0.1.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect

117
go.sum
View file

@ -39,8 +39,6 @@ cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRY
cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
cloud.google.com/go v0.118.2 h1:bKXO7RXMFDkniAAvvuMrAPtQ/VHrs9e7J5UT3yrGdTY=
cloud.google.com/go v0.118.2/go.mod h1:CFO4UPEPi8oV21xoezZCrd3d81K4fFkDTEJu4R8K+9M=
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
@ -180,8 +178,6 @@ cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvj
cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA=
cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=
cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=
cloud.google.com/go/compute v1.33.0 h1:abGcwWokP7/bBpvRjUKlgchrZXYgRpwcKZIlNUHWf6Y=
cloud.google.com/go/compute v1.33.0/go.mod h1:Z8NErRhrWA3RmVWczlAPJjZcRTlqZB1pcpD0MaIc1ug=
cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
@ -618,13 +614,10 @@ github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 h1:1mvYtZfWQAnwNah/C+Z+Jb9rQH95LPE2vlmMuWAHJk8=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1/go.mod h1:75I/mXtme1JyWFtz8GocPHVFyH421IBoZErnO16dd0k=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1 h1:Bk5uOhSAenHyR5P61D/NzeQCv+4fEVV8mOkJ82NqpWw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1/go.mod h1:QZ4pw3or1WPmRBxf0cHd1tknzrT54WPBOQoGutCPvSU=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
@ -667,8 +660,6 @@ github.com/Azure/go-autorest/tracing v0.6.1 h1:YUMSrC/CeD1ZnnXcNYU4a/fzsO35u2Fsf
github.com/Azure/go-autorest/tracing v0.6.1/go.mod h1:/3EgjbsjraOqiicERAeu3m7/z0x1TzjQGAwDrJrXGkc=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.0 h1:MUkXAnvvDHgvPItl0nBj0hgk0f7hnnQbGm0h0+YxbN4=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@ -705,8 +696,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.84 h1:8IpC2i1mtsuUt13cbZtVCtQRSjzuMvLiDrbOJcaS+Z4=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.84/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.88 h1:87jNTxliGqU2yB3H09xCd4U3cZCmR4AkOMqWgaluo5Q=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.88/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
@ -723,66 +712,36 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
github.com/aws/aws-sdk-go-v2 v1.36.0 h1:b1wM5CcE65Ujwn565qcwgtOTT1aT4ADOHHgglKjG7fk=
github.com/aws/aws-sdk-go-v2 v1.36.0/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM=
github.com/aws/aws-sdk-go-v2 v1.36.2 h1:Ub6I4lq/71+tPb/atswvToaLGVMxKZvjYDVOWEExOcU=
github.com/aws/aws-sdk-go-v2 v1.36.2/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k=
github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg=
github.com/aws/aws-sdk-go-v2/config v1.29.7 h1:71nqi6gUbAUiEQkypHQcNVSFJVUFANpSeUNShiwWX2M=
github.com/aws/aws-sdk-go-v2/config v1.29.7/go.mod h1:yqJQ3nh2HWw/uxd56bicyvmDW4KSc+4wN6lL8pYjynU=
github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y=
github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A=
github.com/aws/aws-sdk-go-v2/credentials v1.17.60 h1:1dq+ELaT5ogfmqtV1eocq8SpOK1NRsuUfmhQtD/XAh4=
github.com/aws/aws-sdk-go-v2/credentials v1.17.60/go.mod h1:HDes+fn/xo9VeszXqjBVkxOo/aUy8Mc6QqKvZk32GlE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPdVAqZ6A+LheHWb+mHbNOq8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29 h1:JO8pydejFKmGcUNiiwt75dzLHRWthkwApIvPoyUtXEg=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29/go.mod h1:adxZ9i9DRmB8zAT0pO0yGnsmu0geomp5a3uq5XpgOJ8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 h1:lWm9ucLSRFiI4dQQafLrEOmEDGry3Swrz0BIRdiHJqQ=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31/go.mod h1:Huu6GG0YTfbPphQkDSo4dEGmQRTKb9k9G7RdtyQWxuI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33 h1:knLyPMw3r3JsU8MFHWctE4/e2qWbPaxDYLlohPvnY8c=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33/go.mod h1:EBp2HQ3f+XCB+5J+IoEbGhoV7CpJbnrsd4asNXmTL0A=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 h1:ACxDklUKKXb48+eg5ROZXi1vDgfMyfIA/WyvqHcHI0o=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31/go.mod h1:yadnfsDwqXeVaohbGc/RaD287PuyRw2wugkh5ZL2J6k=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33 h1:K0+Ne08zqti8J9jwENxZ5NoUyBnaFDTu3apwQJWrwwA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33/go.mod h1:K97stwwzaWzmqxO8yLGHhClbVW1tC6VT1pDLk1pGrq4=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 h1:O+8vD2rGjfihBewr5bT+QUfYUHIxCVgG61LHoT59shM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12/go.mod h1:usVdWJaosa66NMvmCrr08NcWDBRv4E6+YFG2pUdw1Lk=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14 h1:2scbY6//jy/s8+5vGrk7l1+UtHl0h9A4MjOO2k/TM2E=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14/go.mod h1:bRpZPHZpSe5YRHmPfK3h1M7UBFCn2szHzyx0rw04zro=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.14 h1:GH3vnPsdH2sTkZRBPnAeMqwkJXdwPNrEh9nI+DEdD0o=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.14/go.mod h1:fHFrxpH3kA2iK2NBg/jj3jxgVVfNS7WaKYC5axxr/PY=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.16 h1:Wg+SyAmJFupMcHW9CHn2QK0M5nksu8JeXWVJIRVL8Nk=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.16/go.mod h1:t2tzigPR3e5R46iVnpIQrfVbA9AIuy5VLYqyk3gffjg=
github.com/aws/aws-sdk-go-v2/service/route53 v1.48.6 h1:O7L9iEodiF07vJoXShMrw2XyeAqZhLUIXqWEitCq6EE=
github.com/aws/aws-sdk-go-v2/service/route53 v1.48.6/go.mod h1:E93uWfli9RToQzVA7+bYnynKOFcYOhNWqhY1hWSMZRc=
github.com/aws/aws-sdk-go-v2/service/route53 v1.48.8 h1:abeu0IVRqYXSts7Tl1Yoi/BxC59xdXYX0uVSN0fbPOk=
github.com/aws/aws-sdk-go-v2/service/route53 v1.48.8/go.mod h1:bOsuAIYHQbL+AqCldJ286MeljQL1sjUVGlpz9JMxCRM=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 h1:c5WJ3iHz7rLIgArznb3JCSQT3uUMiz9DLZhIX+1G8ok=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.16 h1:YV6xIKDJp6U7YB2bxfud9IENO1LRpGhe2Tv/OKtPrOQ=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.16/go.mod h1:DvbmMKgtpA6OihFJK13gHMZOZrCHttz8wPHGKXqU+3o=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 h1:f1L/JtUkVODD+k1+IiSJUUv8A++2qVr+Xvb3xWXETMU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15 h1:kMyK3aKotq1aTBsj1eS8ERJLjqYRRRcsmP33ozlCvlk=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15/go.mod h1:5uPZU7vSNzb8Y0dm75xTikinegPYK3uJmIHQZFq5Aqo=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 h1:3LXNnmtH3TURctC23hnC0p/39Q5gre3FI7BNOiDcVWc=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.13/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.15 h1:ht1jVmeeo2anR7zDiYJLSnRYnO/9NILXXu42FP3rJg0=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.15/go.mod h1:xWZ5cOiFe3czngChE4LhCBqUxNwgfwndEF7XlYP/yD8=
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k=
github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
@ -804,8 +763,6 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bsm/redislock v0.9.4 h1:X/Wse1DPpiQgHbVYRE9zv6m070UcKoOGekgvpNhiSvw=
github.com/bsm/redislock v0.9.4/go.mod h1:Epf7AJLiSFwLCiZcfi6pWFO/8eAYrYpQXFxEDPoDeAk=
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
github.com/bytedance/sonic v1.12.9 h1:Od1BvK55NnewtGaJsTDeAOSnLVO2BTSLOe0+ooKokmQ=
github.com/bytedance/sonic v1.12.9/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
@ -816,8 +773,6 @@ github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdf
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casdoor/casdoor-go-sdk v1.3.0 h1:iUZKsrNUkhtAoyitFIFw3e6TchctAdoxmVgLDtNAgpc=
github.com/casdoor/casdoor-go-sdk v1.3.0/go.mod h1:cMnkCQJgMYpgAlgEx8reSt1AVaDIQLcJ1zk5pzBaz+4=
github.com/casdoor/casdoor-go-sdk v1.4.0 h1:EhnIcMeCPiDE66tedy6EISkVjndR78slnwXqTfUnyhU=
github.com/casdoor/casdoor-go-sdk v1.4.0/go.mod h1:cMnkCQJgMYpgAlgEx8reSt1AVaDIQLcJ1zk5pzBaz+4=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
@ -827,7 +782,6 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -839,8 +793,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/civo/civogo v0.3.93 h1:wxzMamDKYu2lszObvx92tTFDpi0sCJbDO+CL3cR/P28=
github.com/civo/civogo v0.3.93/go.mod h1:7UCYX+qeeJbrG55E1huv+0ySxcHTqq/26FcHLVelQJM=
github.com/civo/civogo v0.3.94 h1:VhdqaJ2m4z8Jz8arzyzVjokRnO8JQ3lGjLKLshJ1eJI=
github.com/civo/civogo v0.3.94/go.mod h1:LaEbkszc+9nXSh4YNG0sYXFGYqdQFmXXzQg0gESs2hc=
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
@ -871,8 +823,6 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@ -965,8 +915,6 @@ github.com/gin-contrib/static v1.1.3 h1:WLOpkBtMDJ3gATFZgNJyVibFMio/UHonnueqJsQ0
github.com/gin-contrib/static v1.1.3/go.mod h1:zejpJ/YWp8cZj/6EpiL5f/+skv5daQTNwRx1E8Pci30=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-acme/lego/v4 v4.21.0 h1:arEW+8o5p7VI8Bk1kr/PDlgD1DrxtTH1gJ4b7mehL8o=
github.com/go-acme/lego/v4 v4.21.0/go.mod h1:HrSWzm3Ckj45Ie3i+p1zKVobbQoMOaGu9m4up0dUeDI=
github.com/go-acme/lego/v4 v4.22.2 h1:ck+HllWrV/rZGeYohsKQ5iKNnU/WAZxwOdiu6cxky+0=
github.com/go-acme/lego/v4 v4.22.2/go.mod h1:E2FndyI3Ekv0usNJt46mFb9LVpV/XBYT+4E3tz02Tzo=
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
@ -1016,15 +964,11 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo=
github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@ -1034,12 +978,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc=
github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0=
github.com/go-webauthn/webauthn v0.12.1 h1:fQNKWc+gd7i1TW8FmlB0jQTHyc2GYYlV/QdLUxo+MSA=
github.com/go-webauthn/webauthn v0.12.1/go.mod h1:Q13xKHZi459wU8IoFjm8jQ6CMRyad+kegblwMFFhQGU=
github.com/go-webauthn/x v0.1.16 h1:EaVXZntpyHviN9ykjdRBQIw9B0Ed3LO5FW7mDiMQEa8=
github.com/go-webauthn/x v0.1.16/go.mod h1:jhYjfwe/AVYaUs2mUXArj7vvZj+SpooQPyyQGNab+Us=
github.com/go-webauthn/x v0.1.18 h1:9xxiKRKCHx/1R2RF+4xb1qY5QDIO0RlTmH5L02lmRH4=
github.com/go-webauthn/x v0.1.18/go.mod h1:Q/uHdGGFrZ7psEcoEStYunurZuG3Z9UDZJetM8qDTtA=
github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
@ -1275,8 +1215,6 @@ github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKEN
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.134 h1:s0IUKcV6UCu84UZMKejNiUnJ4l2Jw9HM0IxHvdJCg9A=
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.134/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI=
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.136 h1:T785NUg5245nWpPVHLVR8lBd+zGQYR14Vi/TCX1iu3A=
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.136/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY=
github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
@ -1595,12 +1533,8 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE=
github.com/oracle/oci-go-sdk/v65 v65.83.0 h1:KFI0oyyCTPmgevHF+QlN02Zdf23Jx1p1X+4KPyH14H8=
github.com/oracle/oci-go-sdk/v65 v65.83.0/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
github.com/oracle/oci-go-sdk/v65 v65.83.2 h1:4DtSCVe/AaHcqb08wXgjplOM8+tc4pqNwcUYZmplbv8=
github.com/oracle/oci-go-sdk/v65 v65.83.2/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
github.com/ovh/go-ovh v1.6.0 h1:ixLOwxQdzYDx296sXcgS35TOPEahJkpjMGtzPadCjQI=
github.com/ovh/go-ovh v1.6.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
github.com/ovh/go-ovh v1.7.0 h1:V14nF7FwDjQrZt9g7jzcvAAQ3HN6DNShRFRMC3jLoPw=
github.com/ovh/go-ovh v1.7.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@ -1714,8 +1648,6 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/sashabaranov/go-openai v1.36.1 h1:EVfRXwIlW2rUzpx6vR+aeIKCK/xylSrVYAx1TMTSX3g=
github.com/sashabaranov/go-openai v1.36.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sashabaranov/go-openai v1.37.0 h1:hQQowgYm4OXJ1Z/wTrE+XZaO20BYsL0R3uRPSpfNZkY=
github.com/sashabaranov/go-openai v1.37.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
@ -1817,12 +1749,8 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1091 h1:RxogX8ZCPBmZ6PY7DjnWnwGRkAkYEEinT5WNNxbLVeo=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1091/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1101 h1:pz6QIjHR7TXQfEogg4pwvvTDgsB1L+RQGgnr2tBDzc4=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1101/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1091 h1:36WwNgrtoGKszQovUj3+0CjNsM1gMQ3a5lvZx2bkjng=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1091/go.mod h1:cWRGvOUnMQMky4oliMX1dXT6Z4CbsSGOIxaUcrD5Zvw=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1101 h1:9c05Ky7Ppww06YFE579TjI89pfNnC2zdJufx7SXUTi8=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1101/go.mod h1:fBdcH58lmwIwePei24b9QFdE1w8+brIX9yTrf82n7yM=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
@ -1847,8 +1775,6 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec h1:2s/ghQ8wKE+UzD/hf3P4Gd1j0JI9ncbxv+nsypPoUYI=
github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7Qs3ku1ckpqed8tCRSqTlp8NAeZfAVpfx4OzXMss=
github.com/uozi-tech/cosy v1.14.3 h1:YDleGHghw5Dtd8H7Fy0Iq0caXfxmhk7Zt6tJBONjq5Q=
github.com/uozi-tech/cosy v1.14.3/go.mod h1:DSKLtoVaGLUlJ8KiQ1vWEsnv85epRrAAMXSijuq+asM=
github.com/uozi-tech/cosy v1.14.4 h1:9X9CzxYjTg9DRQKgBjYvDNOAYYFclOXYYq518nO4vr0=
github.com/uozi-tech/cosy v1.14.4/go.mod h1:DSKLtoVaGLUlJ8KiQ1vWEsnv85epRrAAMXSijuq+asM=
github.com/uozi-tech/cosy-driver-mysql v0.2.2 h1:22S/XNIvuaKGqxQPsYPXN8TZ8hHjCQdcJKVQ83Vzxoo=
@ -1862,8 +1788,6 @@ github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjc
github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ=
github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q=
github.com/volcengine/volc-sdk-golang v1.0.194 h1:3o0INQzdtYJWvdGrtX02booCqPL5TsWSq2W1Ur7Bzlo=
github.com/volcengine/volc-sdk-golang v1.0.194/go.mod h1:u0VtPvlXWpXDTmc9IHkaW1q+5Jjwus4oAqRhNMDRInE=
github.com/volcengine/volc-sdk-golang v1.0.195 h1:hKX4pBhmKcB3652BTdcAmtgizEPBnoQUpTM+j5blMA4=
github.com/volcengine/volc-sdk-golang v1.0.195/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ=
github.com/vultr/govultr/v3 v3.14.1 h1:9BpyZgsWasuNoR39YVMcq44MSaF576Z4D+U3ro58eJQ=
@ -1879,12 +1803,8 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yandex-cloud/go-genproto v0.0.0-20250203115010-0bcba64c41f6 h1:CHYGew+KO1JaK5sx/N2ApgVCTGCKvfSl0sSPplTyCog=
github.com/yandex-cloud/go-genproto v0.0.0-20250203115010-0bcba64c41f6/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo=
github.com/yandex-cloud/go-genproto v0.0.0-20250217111757-ccaea642a16c h1:WTK2XiEf68Uv0rT6mjrB5hKkwZvMnWWHPF3OjK/fYL8=
github.com/yandex-cloud/go-genproto v0.0.0-20250217111757-ccaea642a16c/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo=
github.com/yandex-cloud/go-sdk v0.0.0-20250203123950-24786ecffd92 h1:UTcY1921ZXBABB5JpSWxccyZaCjMuSbniyifW6nmLZ4=
github.com/yandex-cloud/go-sdk v0.0.0-20250203123950-24786ecffd92/go.mod h1:MQm5WxsYpQRdUklz2C8q3uDhuIaDzzV7seLErAILLBE=
github.com/yandex-cloud/go-sdk v0.0.0-20250210144447-399a857b9c4e h1:RiNKkceZPeMWLSIl31RSgPeSmpT9K7eTXOcA9YxTBfg=
github.com/yandex-cloud/go-sdk v0.0.0-20250210144447-399a857b9c4e/go.mod h1:OCW2kKPZ900GNQ9aKDaX7/FUQmxGdm+CKeXVocbM4d0=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
@ -1911,7 +1831,6 @@ go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsX
go.etcd.io/etcd/client/v2 v2.305.7/go.mod h1:GQGT5Z3TBuAQGvgPfhR7VPySu/SudxmEkRq9BgzFU6s=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA=
go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0=
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM=
go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
@ -2009,11 +1928,6 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA=
golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -2031,10 +1945,6 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
@ -2160,8 +2070,6 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -2334,7 +2242,6 @@ golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
@ -2352,9 +2259,8 @@ golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -2374,7 +2280,6 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -2461,8 +2366,6 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -2541,8 +2444,6 @@ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60c
google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0=
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms=
google.golang.org/api v0.219.0 h1:nnKIvxKs/06jWawp2liznTBnMRQBEPpGo7I+oEypTX0=
google.golang.org/api v0.219.0/go.mod h1:K6OmjGm+NtLrIkHxv1U3a0qIf/0JOvAHd5O/6AoyKYE=
google.golang.org/api v0.221.0 h1:qzaJfLhDsbMeFee8zBRdt/Nc+xmOuafD/dbdgGfutOU=
google.golang.org/api v0.221.0/go.mod h1:7sOU2+TL4TxUTdbi0gWgAIg7tH5qBXxoyhtL+9x3biQ=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@ -2685,16 +2586,10 @@ google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOl
google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/genproto v0.0.0-20250204164813-702378808489 h1:nQcbCCOg2h2CQ0yA8SY3AHqriNKDvsetuq9mE/HFjtc=
google.golang.org/genproto v0.0.0-20250204164813-702378808489/go.mod h1:wkQ2Aj/xvshAUDtO/JHvu9y+AaN9cqs28QuSVSHtZSY=
google.golang.org/genproto v0.0.0-20250218202821-56aae31c358a h1:Xx6e5r1AOINOgm2ZuzvwDueGlOOml4PKBUry8jqyS6U=
google.golang.org/genproto v0.0.0-20250218202821-56aae31c358a/go.mod h1:Cmg1ztsSOnOsWxOiPTOUX8gegyHg5xADRncIHdtec8U=
google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 h1:fCuMM4fowGzigT89NCIsW57Pk9k2D12MMi2ODn+Nk+o=
google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4=
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489 h1:5bKytslY8ViY0Cj/ewmRtrWHW64bNF03cAatUUFCdFI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@ -2759,8 +2654,6 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
@ -2840,12 +2733,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc=
k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k=
k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw=
k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y=
k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs=
k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ=
k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=

79
internal/stream/delete.go Normal file
View file

@ -0,0 +1,79 @@
package stream
import (
"fmt"
"net/http"
"os"
"runtime"
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/internal/notification"
"github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/query"
"github.com/go-resty/resty/v2"
"github.com/uozi-tech/cosy/logger"
)
// Delete deletes a site by removing the file in sites-available
func Delete(name string) (err error) {
availablePath := nginx.GetConfPath("streams-available", name)
syncDelete(name)
s := query.Site
_, err = s.Where(s.Path.Eq(availablePath)).Unscoped().Delete(&model.Site{})
if err != nil {
return
}
enabledPath := nginx.GetConfPath("streams-enabled", name)
if !helper.FileExists(availablePath) {
return ErrStreamNotFound
}
if helper.FileExists(enabledPath) {
return ErrStreamIsEnabled
}
certModel := model.Cert{Filename: name}
_ = certModel.Remove()
err = os.Remove(availablePath)
if err != nil {
return
}
return
}
func syncDelete(name string) {
nodes := getSyncNodes(name)
for _, node := range nodes {
go func() {
defer func() {
if err := recover(); err != nil {
buf := make([]byte, 1024)
runtime.Stack(buf, false)
logger.Error(err)
}
}()
client := resty.New()
client.SetBaseURL(node.URL)
resp, err := client.R().
SetHeader("X-Node-Secret", node.Token).
Delete(fmt.Sprintf("/api/streams/%s", name))
if err != nil {
notification.Error("Delete Remote Stream Error", err.Error())
return
}
if resp.StatusCode() != http.StatusOK {
notification.Error("Delete Remote Stream Error", NewSyncResult(node.Name, name, resp).String())
return
}
notification.Success("Delete Remote Stream Success", NewSyncResult(node.Name, name, resp).String())
}()
}
}

View file

@ -0,0 +1,81 @@
package stream
import (
"fmt"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/internal/notification"
"github.com/0xJacky/Nginx-UI/model"
"github.com/go-resty/resty/v2"
"github.com/uozi-tech/cosy/logger"
"net/http"
"os"
"runtime"
"sync"
)
// Disable disables a site by removing the symlink in sites-enabled
func Disable(name string) (err error) {
enabledConfigFilePath := nginx.GetConfPath("streams-enabled", name)
_, err = os.Stat(enabledConfigFilePath)
if err != nil {
return
}
err = os.Remove(enabledConfigFilePath)
if err != nil {
return
}
// delete auto cert record
certModel := model.Cert{Filename: name}
err = certModel.Remove()
if err != nil {
return
}
output := nginx.Reload()
if nginx.GetLogLevel(output) > nginx.Warn {
return fmt.Errorf("%s", output)
}
go syncDisable(name)
return
}
func syncDisable(name string) {
nodes := getSyncNodes(name)
wg := &sync.WaitGroup{}
wg.Add(len(nodes))
for _, node := range nodes {
go func() {
defer func() {
if err := recover(); err != nil {
buf := make([]byte, 1024)
runtime.Stack(buf, false)
logger.Error(err)
}
}()
defer wg.Done()
client := resty.New()
client.SetBaseURL(node.URL)
resp, err := client.R().
SetHeader("X-Node-Secret", node.Token).
Post(fmt.Sprintf("/api/streams/%s/disable", name))
if err != nil {
notification.Error("Disable Remote Stream Error", err.Error())
return
}
if resp.StatusCode() != http.StatusOK {
notification.Error("Disable Remote Stream Error", NewSyncResult(node.Name, name, resp).String())
return
}
notification.Success("Disable Remote Stream Success", NewSyncResult(node.Name, name, resp).String())
}()
}
wg.Wait()
}

View file

@ -0,0 +1,23 @@
package stream
import (
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
)
// Duplicate duplicates a site by copying the file
func Duplicate(src, dst string) (err error) {
src = nginx.GetConfPath("streams-available", src)
dst = nginx.GetConfPath("streams-available", dst)
if helper.FileExists(dst) {
return ErrDstFileExists
}
_, err = helper.CopyFile(src, dst)
if err != nil {
return
}
return
}

87
internal/stream/enable.go Normal file
View file

@ -0,0 +1,87 @@
package stream
import (
"fmt"
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/internal/notification"
"github.com/go-resty/resty/v2"
"github.com/uozi-tech/cosy/logger"
"net/http"
"os"
"runtime"
"sync"
)
// Enable enables a site by creating a symlink in sites-enabled
func Enable(name string) (err error) {
configFilePath := nginx.GetConfPath("streams-available", name)
enabledConfigFilePath := nginx.GetConfPath("streams-enabled", name)
_, err = os.Stat(configFilePath)
if err != nil {
return
}
if helper.FileExists(enabledConfigFilePath) {
return
}
err = os.Symlink(configFilePath, enabledConfigFilePath)
if err != nil {
return
}
// Test nginx config, if not pass, then disable the site.
output := nginx.TestConf()
if nginx.GetLogLevel(output) > nginx.Warn {
_ = os.Remove(enabledConfigFilePath)
return fmt.Errorf("%s", output)
}
output = nginx.Reload()
if nginx.GetLogLevel(output) > nginx.Warn {
return fmt.Errorf("%s", output)
}
go syncEnable(name)
return
}
func syncEnable(name string) {
nodes := getSyncNodes(name)
wg := &sync.WaitGroup{}
wg.Add(len(nodes))
for _, node := range nodes {
go func() {
defer func() {
if err := recover(); err != nil {
buf := make([]byte, 1024)
runtime.Stack(buf, false)
logger.Error(err)
}
}()
defer wg.Done()
client := resty.New()
client.SetBaseURL(node.URL)
resp, err := client.R().
SetHeader("X-Node-Secret", node.Token).
Post(fmt.Sprintf("/api/streams/%s/enable", name))
if err != nil {
notification.Error("Enable Remote Stream Error", err.Error())
return
}
if resp.StatusCode() != http.StatusOK {
notification.Error("Enable Remote Stream Error", NewSyncResult(node.Name, name, resp).String())
return
}
notification.Success("Enable Remote Stream Success", NewSyncResult(node.Name, name, resp).String())
}()
}
wg.Wait()
}

10
internal/stream/errors.go Normal file
View file

@ -0,0 +1,10 @@
package stream
import "github.com/uozi-tech/cosy"
var (
e = cosy.NewErrorScope("stream")
ErrStreamNotFound = e.New(40401, "stream not found")
ErrDstFileExists = e.New(50001, "destination file already exists")
ErrStreamIsEnabled = e.New(50002, "stream is enabled")
)

108
internal/stream/rename.go Normal file
View file

@ -0,0 +1,108 @@
package stream
import (
"fmt"
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/internal/notification"
"github.com/0xJacky/Nginx-UI/query"
"github.com/go-resty/resty/v2"
"github.com/uozi-tech/cosy/logger"
"net/http"
"os"
"runtime"
"sync"
)
func Rename(oldName string, newName string) (err error) {
oldPath := nginx.GetConfPath("streams-available", oldName)
newPath := nginx.GetConfPath("streams-available", newName)
if oldPath == newPath {
return
}
// check if dst file exists, do not rename
if helper.FileExists(newPath) {
return ErrDstFileExists
}
s := query.Site
_, _ = s.Where(s.Path.Eq(oldPath)).Update(s.Path, newPath)
err = os.Rename(oldPath, newPath)
if err != nil {
return
}
// recreate a soft link
oldEnabledConfigFilePath := nginx.GetConfPath("streams-enabled", oldName)
if helper.SymbolLinkExists(oldEnabledConfigFilePath) {
_ = os.Remove(oldEnabledConfigFilePath)
newEnabledConfigFilePath := nginx.GetConfPath("streams-enabled", newName)
err = os.Symlink(newPath, newEnabledConfigFilePath)
if err != nil {
return
}
}
// test nginx configuration
output := nginx.TestConf()
if nginx.GetLogLevel(output) > nginx.Warn {
return fmt.Errorf("%s", output)
}
// reload nginx
output = nginx.Reload()
if nginx.GetLogLevel(output) > nginx.Warn {
return fmt.Errorf("%s", output)
}
go syncRename(oldName, newName)
return
}
func syncRename(oldName, newName string) {
nodes := getSyncNodes(newName)
wg := &sync.WaitGroup{}
wg.Add(len(nodes))
for _, node := range nodes {
go func() {
defer func() {
if err := recover(); err != nil {
buf := make([]byte, 1024)
runtime.Stack(buf, false)
logger.Error(err)
}
}()
defer wg.Done()
client := resty.New()
client.SetBaseURL(node.URL)
resp, err := client.R().
SetHeader("X-Node-Secret", node.Token).
SetBody(map[string]string{
"new_name": newName,
}).
Post(fmt.Sprintf("/api/streams/%s/rename", oldName))
if err != nil {
notification.Error("Rename Remote Stream Error", err.Error())
return
}
if resp.StatusCode() != http.StatusOK {
notification.Error("Rename Remote Stream Error",
NewSyncResult(node.Name, oldName, resp).
SetNewName(newName).String())
return
}
notification.Success("Rename Remote Stream Success",
NewSyncResult(node.Name, oldName, resp).
SetNewName(newName).String())
}()
}
wg.Wait()
}

107
internal/stream/save.go Normal file
View file

@ -0,0 +1,107 @@
package stream
import (
"fmt"
"net/http"
"os"
"runtime"
"sync"
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/internal/notification"
"github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/query"
"github.com/go-resty/resty/v2"
"github.com/uozi-tech/cosy/logger"
)
// Save saves a site configuration file
func Save(name string, content string, overwrite bool, syncNodeIds []uint64) (err error) {
path := nginx.GetConfPath("streams-available", name)
if !overwrite && helper.FileExists(path) {
return ErrDstFileExists
}
err = os.WriteFile(path, []byte(content), 0644)
if err != nil {
return
}
enabledConfigFilePath := nginx.GetConfPath("streams-enabled", name)
if helper.FileExists(enabledConfigFilePath) {
// Test nginx configuration
output := nginx.TestConf()
if nginx.GetLogLevel(output) > nginx.Warn {
return fmt.Errorf("%s", output)
}
output = nginx.Reload()
if nginx.GetLogLevel(output) > nginx.Warn {
return fmt.Errorf("%s", output)
}
}
s := query.Stream
_, err = s.Where(s.Path.Eq(path)).
Select(s.SyncNodeIDs).
Updates(&model.Site{
SyncNodeIDs: syncNodeIds,
})
if err != nil {
return
}
go syncSave(name, content)
return
}
func syncSave(name string, content string) {
nodes := getSyncNodes(name)
wg := &sync.WaitGroup{}
wg.Add(len(nodes))
for _, node := range nodes {
go func() {
defer func() {
if err := recover(); err != nil {
buf := make([]byte, 1024)
runtime.Stack(buf, false)
logger.Error(err)
}
}()
defer wg.Done()
client := resty.New()
client.SetBaseURL(node.URL)
resp, err := client.R().
SetHeader("X-Node-Secret", node.Token).
SetBody(map[string]interface{}{
"content": content,
"overwrite": true,
}).
Post(fmt.Sprintf("/api/streams/%s", name))
if err != nil {
notification.Error("Save Remote Stream Error", err.Error())
return
}
if resp.StatusCode() != http.StatusOK {
notification.Error("Save Remote Stream Error", NewSyncResult(node.Name, name, resp).String())
return
}
notification.Success("Save Remote Stream Success", NewSyncResult(node.Name, name, resp).String())
// Check if the site is enabled, if so then enable it on the remote node
enabledConfigFilePath := nginx.GetConfPath("streams-enabled", name)
if helper.FileExists(enabledConfigFilePath) {
syncEnable(name)
}
}()
}
wg.Wait()
}

74
internal/stream/sync.go Normal file
View file

@ -0,0 +1,74 @@
package stream
import (
"encoding/json"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
"github.com/samber/lo"
"github.com/uozi-tech/cosy/logger"
)
// getSyncNodes returns the nodes that need to be synchronized by site name
func getSyncNodes(name string) (nodes []*model.Environment) {
configFilePath := nginx.GetConfPath("streams-available", name)
s := query.Site
site, err := s.Where(s.Path.Eq(configFilePath)).
Preload(s.SiteCategory).First()
if err != nil {
logger.Error(err)
return
}
syncNodeIds := site.SyncNodeIDs
// inherit sync node ids from site category
if site.SiteCategory != nil {
syncNodeIds = append(syncNodeIds, site.SiteCategory.SyncNodeIds...)
}
syncNodeIds = lo.Uniq(syncNodeIds)
e := query.Environment
nodes, err = e.Where(e.ID.In(syncNodeIds...)).Find()
if err != nil {
logger.Error(err)
return
}
return
}
type SyncResult struct {
StatusCode int `json:"status_code"`
Node string `json:"node"`
Name string `json:"name"`
NewName string `json:"new_name,omitempty"`
Response gin.H `json:"response"`
Error string `json:"error"`
}
func NewSyncResult(node string, siteName string, resp *resty.Response) (s *SyncResult) {
s = &SyncResult{
StatusCode: resp.StatusCode(),
Node: node,
Name: siteName,
}
err := json.Unmarshal(resp.Body(), &s.Response)
if err != nil {
logger.Error(err)
}
return
}
func (s *SyncResult) SetNewName(name string) *SyncResult {
s.NewName = name
return s
}
func (s *SyncResult) String() string {
b, err := json.Marshal(s)
if err != nil {
logger.Error(err)
}
return string(b)
}

View file

@ -2,6 +2,7 @@ package model
type Stream struct {
Model
Path string `json:"path"`
Advanced bool `json:"advanced"`
Path string `json:"path"`
Advanced bool `json:"advanced"`
SyncNodeIDs []uint64 `json:"sync_node_ids" gorm:"serializer:json"`
}

View file

@ -34,6 +34,7 @@ func newStream(db *gorm.DB, opts ...gen.DOOption) stream {
_stream.DeletedAt = field.NewField(tableName, "deleted_at")
_stream.Path = field.NewString(tableName, "path")
_stream.Advanced = field.NewBool(tableName, "advanced")
_stream.SyncNodeIDs = field.NewField(tableName, "sync_node_ids")
_stream.fillFieldMap()
@ -43,13 +44,14 @@ func newStream(db *gorm.DB, opts ...gen.DOOption) stream {
type stream struct {
streamDo
ALL field.Asterisk
ID field.Uint64
CreatedAt field.Time
UpdatedAt field.Time
DeletedAt field.Field
Path field.String
Advanced field.Bool
ALL field.Asterisk
ID field.Uint64
CreatedAt field.Time
UpdatedAt field.Time
DeletedAt field.Field
Path field.String
Advanced field.Bool
SyncNodeIDs field.Field
fieldMap map[string]field.Expr
}
@ -72,6 +74,7 @@ func (s *stream) updateTableName(table string) *stream {
s.DeletedAt = field.NewField(table, "deleted_at")
s.Path = field.NewString(table, "path")
s.Advanced = field.NewBool(table, "advanced")
s.SyncNodeIDs = field.NewField(table, "sync_node_ids")
s.fillFieldMap()
@ -88,13 +91,14 @@ func (s *stream) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
}
func (s *stream) fillFieldMap() {
s.fieldMap = make(map[string]field.Expr, 6)
s.fieldMap = make(map[string]field.Expr, 7)
s.fieldMap["id"] = s.ID
s.fieldMap["created_at"] = s.CreatedAt
s.fieldMap["updated_at"] = s.UpdatedAt
s.fieldMap["deleted_at"] = s.DeletedAt
s.fieldMap["path"] = s.Path
s.fieldMap["advanced"] = s.Advanced
s.fieldMap["sync_node_ids"] = s.SyncNodeIDs
}
func (s stream) clone(db *gorm.DB) stream {