增加组织magicDNS域名随机及更新机制

Signed-off-by: Chenyang Gao <gps949@outlook.com>
This commit is contained in:
Chenyang Gao
2023-03-25 14:37:44 +08:00
parent 97c8675595
commit f43aed2be7
17 changed files with 1638 additions and 1010 deletions
+5
View File
@@ -81,6 +81,8 @@ type Mirage struct {
machineControlCodeCache *cache.Cache
//organizationCache *cache.Cache
tcdCache *cache.Cache
longPollChanPool map[string]chan string
ipAllocationMutex sync.Mutex
@@ -124,6 +126,7 @@ func NewMirage(cfg *Config, db *gorm.DB) (*Mirage, error) {
stateCodeCache: stateCodeCache,
controlCodeCache: controlCodeCache,
machineControlCodeCache: machineControlCodeCache,
tcdCache: cache.New(0, 0),
longPollChanPool: longPollChanPool,
smsCodeCache: smsCodeCache,
shutdownChan: make(chan struct{}),
@@ -317,6 +320,7 @@ func (h *Mirage) initRouter(router *mux.Router) {
console_router.HandleFunc("/api/self", h.ConsoleSelfAPI).Methods(http.MethodGet)
console_router.HandleFunc("/api/machines", h.ConsoleMachinesAPI).Methods(http.MethodGet)
console_router.HandleFunc("/api/dns", h.CAPIGetDNS).Methods(http.MethodGet)
console_router.HandleFunc("/api/tcd/offers", h.CAPIGetTCDOffers).Methods(http.MethodGet)
console_router.HandleFunc("/api/netsettings", h.getNetSettingAPI).Methods(http.MethodGet)
console_router.HandleFunc("/api/keys", h.CAPIGetKeys).Methods(http.MethodGet)
console_router.HandleFunc("/api/acls/tags", h.CAPIGetTags).Methods(http.MethodGet)
@@ -328,6 +332,7 @@ func (h *Mirage) initRouter(router *mux.Router) {
console_router.HandleFunc("/api/keys", h.CAPIPostKeys).Methods(http.MethodPost)
console_router.HandleFunc("/api/acls/tags", h.CAPIPostTags).Methods(http.MethodPost)
console_router.HandleFunc("/api/dns", h.CAPIPostDNS).Methods(http.MethodPost)
console_router.HandleFunc("/api/tcd", h.CAPIPostTCD).Methods(http.MethodPost)
// DELETE(删除类)API
console_router.PathPrefix("/api/keys/").HandlerFunc(h.CAPIDelKeys).Methods(http.MethodDelete)
-123
View File
@@ -114,126 +114,3 @@ func (ac OIDCConfig) Value() (driver.Value, error) {
bytes, err := json.Marshal(ac)
return string(bytes), err
}
/*
type ACLConfig struct {
PolicyPath string
}
func LoadConfig() error {
viper.SetConfigName("config")
viper.AddConfigPath(".mirage")
viper.SetEnvPrefix("mirage")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
viper.SetDefault("oidc.scope", []string{oidc.ScopeOpenID, "profile", "email"})
viper.SetDefault("oidc.strip_email_domain", true)
if err := viper.ReadInConfig(); err != nil {
log.Warn().Err(err).Msg("Failed to read configuration from disk")
return fmt.Errorf("fatal error reading config file: %w", err)
}
return nil
}
func GetACLConfig() ACLConfig {
policyPath := viper.GetString("acl_policy_path")
return ACLConfig{
PolicyPath: policyPath,
}
}
func GetBasedomain() string {
var baseDomain string
if viper.IsSet("base_domain") {
baseDomain = viper.GetString("base_domain")
} else {
baseDomain = "sdp.net" // does not really matter when MagicDNS is not enabled
}
return baseDomain
}
func GetMirageConfig(srvAddr, serverURL string) (*Config, error) {
baseDomain := GetBasedomain()
configuredPrefixes := viper.GetStringSlice("ip_prefixes")
parsedPrefixes := make([]netip.Prefix, 0, len(configuredPrefixes)+1)
for i, prefixInConfig := range configuredPrefixes {
prefix, err := netip.ParsePrefix(prefixInConfig)
if err != nil {
panic(fmt.Errorf("failed to parse ip_prefixes[%d]: %w", i, err))
}
parsedPrefixes = append(parsedPrefixes, prefix)
}
prefixes := make([]netip.Prefix, 0, len(parsedPrefixes))
{
// dedup
normalizedPrefixes := make(map[string]int, len(parsedPrefixes))
for i, p := range parsedPrefixes {
normalized, _ := netipx.RangeOfPrefix(p).Prefix()
normalizedPrefixes[normalized.String()] = i
}
// convert back to list
for _, i := range normalizedPrefixes {
prefixes = append(prefixes, parsedPrefixes[i])
}
}
if len(prefixes) < 1 {
prefixes = append(prefixes, netip.MustParsePrefix("100.64.0.0/10"))
log.Warn().
Msgf("'ip_prefixes' not configured, falling back to default: %v", prefixes)
}
wxScanAdapterURL := viper.GetString("wxscan.url")
return &Config{
ServerURL: serverURL,
Addr: srvAddr,
IPPrefixes: prefixes,
BaseDomain: baseDomain,
AllowRouteDueToMachine: viper.GetBool("allow_route_due_to_machine"),
DERPURL: viper.GetString("derp_url"),
ESURL: viper.GetString("es_url"),
ESKey: viper.GetString("es_apikey"),
OIDC: OIDCConfig{
Issuer: viper.GetString("ali.issuer"),
ClientID: viper.GetString("ali.client_id"),
ClientSecret: viper.GetString("ali.client_secret"),
Scope: viper.GetStringSlice("ali.scope"),
ExtraParams: viper.GetStringMapString("ali.extra_params"),
StripEmaildomain: viper.GetBool("ali.strip_email_domain"),
},
wxScanURL: wxScanAdapterURL,
IDaaS: ALIConfig{
App: viper.GetString("idaas.app_id"),
ClientID: viper.GetString("idaas.cli_id"),
ClientKey: viper.GetString("idaas.cli_key"),
Instance: viper.GetString("idaas.instance"),
OrgID: viper.GetString("idaas.org_id"),
},
SMS: SMSConfig{
ID: viper.GetString("sms.access_id"),
Key: viper.GetString("sms.access_key"),
Sign: viper.GetString("sms.sms_sign"),
Template: viper.GetString("sms.sms_template"),
},
}, nil
}
*/
+2 -2
View File
@@ -155,7 +155,7 @@ func (h *Mirage) ConsoleSelfAPI(
// 用户所属组织将存于数据库,合并实现前,不设置配置文件中该项,均按个人用户处理
renderData := adminTemplateConfig{
Basedomain: h.cfg.BaseDomain,
Basedomain: user.Organization.MagicDnsDomain,
UserNameHead: userNameHead,
UserName: userDisName,
UserAccount: userName,
@@ -426,7 +426,7 @@ func (h *Mirage) ConsoleMachinesAPI(
}
renderData := adminTemplateConfig{
Basedomain: h.cfg.BaseDomain,
Basedomain: user.Organization.MagicDnsDomain,
MList: mlist,
}
+1 -1
View File
@@ -22,7 +22,7 @@ func (h *Mirage) ListIdps(
// @data 响应数据:key值为data的json对象
func (h *Mirage) doAPIResponse(writer http.ResponseWriter, msg string, data interface{}) {
res := APIResponse{}
if data != nil {
if msg == "" {
res.Status = "success"
res.Data = data
} else {
+84
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"net/http"
"strings"
"time"
)
type DNSData struct {
@@ -130,3 +131,86 @@ func (h *Mirage) CAPIDelDNS(
}
h.doAPIResponse(w, "", targetKeyID)
}
type TCDOffer struct {
TCD string `json:"tcd"`
Token string `json:"token"`
}
type TCDOffers struct {
TCDs []TCDOffer `json:"tcds"`
}
// 接受/admin/api/tcd/offers的Get请求,用于获取新一轮随机TCD
func (h *Mirage) CAPIGetTCDOffers(
w http.ResponseWriter,
r *http.Request,
) {
user := h.verifyTokenIDandGetUser(w, r)
if user.CheckEmpty() {
h.doAPIResponse(w, "用户信息核对失败", nil)
return
}
if oldTCDOffers, ok := h.tcdCache.Get(user.Organization.StableID); ok {
for _, tcd := range oldTCDOffers.([]TCDOffer) {
h.tcdCache.Delete(tcd.TCD)
}
}
newTCDOffers := TCDOffers{
TCDs: make([]TCDOffer, 0),
}
count := 4
for count > 0 {
tmpTCD, err := h.GenNewMagicDNSDomain(h.db)
if err != nil {
continue
}
tmpTCDToken := h.GenStateCode()
if _, ok := h.tcdCache.Get(tmpTCD); ok {
continue
}
h.tcdCache.Set(tmpTCD, tmpTCDToken, 24*time.Hour)
newTCDOffers.TCDs = append(newTCDOffers.TCDs, TCDOffer{
TCD: tmpTCD,
Token: tmpTCDToken,
})
count--
}
h.tcdCache.Set(user.Organization.StableID, newTCDOffers.TCDs, 24*time.Hour)
h.doAPIResponse(w, "", newTCDOffers)
}
// 接受/admin/api/tcd的Post请求,用于更新TCD
func (h *Mirage) CAPIPostTCD(
w http.ResponseWriter,
r *http.Request,
) {
user := h.verifyTokenIDandGetUser(w, r)
if user.CheckEmpty() {
h.doAPIResponse(w, "用户信息核对失败", nil)
return
}
err := r.ParseForm()
if err != nil {
h.doAPIResponse(w, "用户请求解析失败:"+err.Error(), nil)
return
}
reqData := TCDOffer{}
json.NewDecoder(r.Body).Decode(&reqData)
token, ok := h.tcdCache.Get(reqData.TCD)
if !ok || token != reqData.Token {
h.doAPIResponse(w, "请求的新蜃境网域名称有误", nil)
return
}
err = h.UpdateMagicDNSDomain(user.OrganizationID, reqData.TCD)
if err != nil {
h.doAPIResponse(w, "更新蜃境网域名称失败", nil)
return
}
if oldTCDOffers, ok := h.tcdCache.Get(user.Organization.StableID); ok {
for _, tcd := range oldTCDOffers.([]TCDOffer) {
h.tcdCache.Delete(tcd.TCD)
}
}
h.doAPIResponse(w, "", nil)
}
+4 -4
View File
@@ -309,7 +309,7 @@ func (h *Mirage) deviceRegPortal(
//未绑定用户,显示connectDevice
user, _ := h.GetUserByID(controlCodeItem.uid)
Hostname := aCodeItem.regReq.Hostinfo.Hostname
Netname := user.Name
Netname := user.Organization.Name
Nodekey := aCodeItem.regReq.NodeKey.String()
OS := aCodeItem.regReq.Hostinfo.OS + "(" + aCodeItem.regReq.Hostinfo.OSVersion + ")"
ClientVer := aCodeItem.regReq.Hostinfo.IPNVersion
@@ -329,7 +329,7 @@ func (h *Mirage) deviceRegPortal(
return
}
Hostname := machine.GivenName
Netname := machine.User.Name
Netname := machine.User.Organization.Name
MIP := machine.IPAddresses[0].String()
if len(machine.IPAddresses) > 1 && machine.IPAddresses[1].Is4() {
MIP = machine.IPAddresses[1].String()
@@ -399,7 +399,7 @@ func (h *Mirage) deviceReg(
return
}
Hostname := machine.GivenName
Netname := machine.User.Name
Netname := machine.User.Organization.Name
MIP := machine.IPAddresses[0].String()
if len(machine.IPAddresses) > 1 && machine.IPAddresses[1].Is4() {
MIP = machine.IPAddresses[1].String()
@@ -419,7 +419,7 @@ func (h *Mirage) deviceReg(
h.longPollChanPool[aCode] <- "ok" // longpoll的救赎
Hostname := machine.GivenName
Netname := machine.User.Name
Netname := machine.User.Organization.Name
MIP := machine.IPAddresses[0].String()
if len(machine.IPAddresses) > 1 && machine.IPAddresses[1].Is4() {
MIP = machine.IPAddresses[1].String()
+2 -6
View File
@@ -194,11 +194,7 @@ func getMapResponseDNSConfig(
// Only inject the Search Domain of the current user - shared nodes should use their full FQDN
dnsConfig.Domains = append(
dnsConfig.Domains,
fmt.Sprintf(
"%s.%s",
machine.User.Name,
baseDomain,
),
baseDomain,
)
/* cgao6: due to we need to add uncomparable field to User, can't use mapset any more
@@ -214,7 +210,7 @@ func getMapResponseDNSConfig(
userSet = append(userSet, p.User)
}
for _, user := range userSet { //cgao6: same as above .ToSlice() {
dnsRoute := fmt.Sprintf("%v.%v", user.Name, baseDomain)
dnsRoute := user.Organization.MagicDnsDomain
dnsConfig.Routes[dnsRoute] = nil
}
} /* else {
+1 -2
View File
@@ -965,9 +965,8 @@ func (h *Mirage) toNode(
if machine.User.Organization.EnableMagic { //[cgao6 removed] dnsConfig != nil && dnsConfig.Proxied { // MagicDNS
_, baseDomain := machine.User.GetDNSConfig(h.cfg.IPPrefixes)
hostname = fmt.Sprintf(
"%s.%s.%s",
"%s.%s",
machine.GivenName,
machine.User.Name,
baseDomain,
)
if len(hostname) > maxHostnameLength {
+49 -11
View File
@@ -2,10 +2,12 @@ package controller
import (
"errors"
"strings"
"time"
"github.com/bwmarrin/snowflake"
"github.com/rs/zerolog/log"
"github.com/sethvargo/go-diceware/diceware"
"gorm.io/gorm"
"tailscale.com/tailcfg"
)
@@ -22,11 +24,11 @@ type Organization struct {
ID int64 `gorm:"primary_key;unique;not null"`
StableID string `gorm:"unique"`
Name string `gorm:"uniqueIndex:idx_name_provider"`
DisplayName string
Provider string `gorm:"uniqueIndex:idx_name_provider"`
ExpiryDuration uint `gorm:"default:180"`
EnableMagic bool `gorm:"default:false"`
OverrideLocal bool `gorm:"default:false"`
MagicDnsDomain string
OverrideLocal bool `gorm:"default:false"`
Nameservers StringList
SplitDns SplitDNS
AclPolicy *ACLPolicy
@@ -50,18 +52,43 @@ func (o *Organization) BeforeCreate(tx *gorm.DB) error {
return nil
}
func (m *Mirage) CreateOrgnaization(name, displayName, provider string) (*Organization, error) {
tx := m.db.Session(&gorm.Session{})
org, err := CreateOrgnaizationInTx(tx, name, displayName, provider)
func (m *Mirage) GenNewMagicDNSDomain(tx *gorm.DB) (string, error) {
list, err := diceware.Generate(2)
if err != nil {
return nil, err
log.Error().Err(err).Msg("Could not generate passphrase")
return "", err
}
m.UpdateACLRulesOfOrg(org)
return org, nil
tmpMagicDNSDomain := strings.Join(list, "-") + "." + m.cfg.BaseDomain
for {
if errors.Is(tx.First(&Organization{}, "magic_dns_domain = ?", tmpMagicDNSDomain).Error, gorm.ErrRecordNotFound) {
break
}
list, err = diceware.Generate(2)
if err != nil {
log.Error().Err(err).Msg("Could not generate passphrase")
return "", err
}
tmpMagicDNSDomain = strings.Join(list, "-") + "." + m.cfg.BaseDomain
}
return tmpMagicDNSDomain, nil
}
func CreateOrgnaizationInTx(tx *gorm.DB, name, displayName, provider string) (*Organization, error) {
if len(name) == 0 || len(displayName) == 0 || len(provider) == 0 {
func (m *Mirage) UpdateMagicDNSDomain(orgID int64, netMagicDomain string) error {
org, err := m.GetOrgnaizationByID(orgID)
if err != nil {
return err
}
org.MagicDnsDomain = netMagicDomain
err = m.db.Save(org).Error
if err != nil {
return err
}
m.setLastStateChangeToNow()
return nil
}
func (m *Mirage) CreateOrgnaizationInTx(tx *gorm.DB, name, provider string) (*Organization, error) {
if len(name) == 0 || len(provider) == 0 {
return nil, ErrCreateOrgParams
}
var count int64
@@ -73,7 +100,6 @@ func CreateOrgnaizationInTx(tx *gorm.DB, name, displayName, provider string) (*O
}
org := Organization{}
org.Name = name
org.DisplayName = displayName
org.Provider = provider
org.AclPolicy = &ACLPolicy{
ACLs: []ACL{{
@@ -84,6 +110,18 @@ func CreateOrgnaizationInTx(tx *gorm.DB, name, displayName, provider string) (*O
}},
}
org.ExpiryDuration = DefaultExpireTime
//cgao6: 添加组织幻域域名roll生成
newMagicDNSDomain, err := m.GenNewMagicDNSDomain(tx)
if err != nil {
log.Error().
Str("func", "CreateOrgnaization").
Err(err).
Msg("Could not generate magic dns domain")
return nil, err
}
org.MagicDnsDomain = newMagicDNSDomain
if err := tx.Create(&org).Error; err != nil {
log.Error().
Str("func", "CreateOrgnaization").
+1 -1
View File
@@ -104,7 +104,7 @@ func (h *Mirage) generateMapResponse(
DNSConfig: dnsConfig,
// TODO: Only send if updated
Domain: h.cfg.BaseDomain,
Domain: org.Name,
// Do not instruct clients to collect services, we do not
// support or do anything with them
+1 -1
View File
@@ -439,7 +439,7 @@
<p>你是否要将名为 <strong>{{.Hostname}}</strong> 的设备接入进 <strong>{{.Netname}}</strong> 蜃境网络?
这将使该设备基于ACL控制规则可能访问到蜃境网络中的其他资源。
这将使该设备基于ACL控制规则可能访问到蜃境网络中的其他资源。
</p>
</header>
+4 -9
View File
@@ -103,7 +103,7 @@ func (h *Mirage) CreateUser(name string, disName string, orgName string, provide
org, trxErr = GetOrgnaizationByNameInTx(tx, orgName, provider)
// 不存在: 创建组织
if errors.Is(trxErr, ErrOrgNotFound) {
org, trxErr = CreateOrgnaizationInTx(tx, orgName, orgName, provider)
org, trxErr = h.CreateOrgnaizationInTx(tx, orgName, provider)
user.Role = RoleAdmin
// 其他错误, 报错返回
} else if trxErr == nil && org.ID == 0 {
@@ -332,7 +332,7 @@ func (n *User) toTailscaleUser() *tailcfg.User {
LoginName: n.Name,
DisplayName: n.Display_Name,
ProfilePicURL: "",
Domain: "headscale.net",
Domain: n.Organization.MagicDnsDomain,
Logins: []tailcfg.LoginID{},
Created: time.Time{},
}
@@ -346,7 +346,7 @@ func (n *User) toTailscaleLogin() *tailcfg.Login {
LoginName: n.Name,
DisplayName: n.Name,
ProfilePicURL: "",
Domain: "headscale.net",
Domain: n.Organization.MagicDnsDomain,
}
return &login
@@ -542,12 +542,7 @@ func (me *User) GetDNSConfig(ipPrefixesCfg []netip.Prefix) (*tailcfg.DNSConfig,
}
}
var baseDomain string
if viper.IsSet("base_domain") {
baseDomain = viper.GetString("base_domain")
} else {
baseDomain = "headscale.net" // does not really matter when MagicDNS is not enabled
}
baseDomain := me.Organization.MagicDnsDomain
return dnsConfig, baseDomain
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+176
View File
@@ -0,0 +1,176 @@
<script setup>
import { ref, watch, computed } from "vue";
import { useDisScroll } from "/src/utils.js";
const emit = defineEmits(["refresh-offers", "update-tcd"]);
useDisScroll();
const props = defineProps({
currenttcd: String,
tcdoffers: Array,
});
const wantedTCD = ref("");
function getTCDOffers() {
wantedTCD.value = "";
axios
.get("/admin/api/tcd/offers")
.then(function (response) {
if (response.data["status"] == "success") {
emit("refresh-offers", response.data["data"]["tcds"]);
} else {
console.log(response.data["status"]);
}
})
.catch(function (error) {
console.log(error);
});
}
function setWantedTCD(newWantedTCD) {
wantedTCD.value = newWantedTCD;
console.log("new Wanted TCD: " + wantedTCD.value);
}
</script>
<template>
<div
@click.self="$emit('close')"
class="fixed overflow-y-auto inset-0 py-8 z-30 bg-gray-900 bg-opacity-[0.07]"
style="pointer-events: auto"
>
<div
class="bg-white rounded-lg relative p-4 md:p-6 text-gray-700 max-w-lg min-w-[19rem] my-8 mx-auto w-[97%] shadow-2xl"
tabindex="-1"
style="pointer-events: auto"
>
<header class="flex items-center justify-between space-x-4 mb-5 mr-8">
<div class="font-semibold text-lg truncate">重命名 {{ currenttcd }}</div>
</header>
<form @submit.prevent="">
<p v-if="tcdoffers.length == 0" class="mb-6">
重命名你的蜃境可能会破坏你蜃境网络中已有的连接
</p>
<footer v-if="tcdoffers.length == 0" class="flex mt-10 justify-end space-x-4">
<button
@click="getTCDOffers"
class="btn border-0 bg-blue-500 hover:bg-blue-900 disabled:bg-blue-500/60 text-white disabled:text-white/60 h-9 min-h-fit w-full"
>
我明白让我重命名蜃境
</button>
</footer>
<div v-if="tcdoffers.length > 0" class="mt-8 mb-8">
<p class="mb-5">
选择一个生成的名称或者摇一摇获得新的一组如果你选择了新的名称你之前的名称会被释放给其他租户使用
</p>
<div class="mb-5">
<label v-for="(tcdOffer, i) in tcdoffers" class="flex mt-1">
<input
:checked="tcdOffer.tcd == wantedTCD"
@change="setWantedTCD(tcdOffer.tcd)"
type="radio"
name="tcd-rad"
class="radio radio-xs mt-1 mr-1"
:value="tcdOffer.tcd"
/>{{ tcdOffer.tcd.split(".")[0]
}}<span class="text-gray-400">{{
tcdOffer.tcd.replace(tcdOffer.tcd.split(".")[0], "")
}}</span>
</label>
</div>
<button
@click="getTCDOffers"
class="btn border border-stone-300 hover:border-stone-300 bg-stone-200 hover:bg-stone-300 text-black h-9 min-h-fit"
type="button"
>
<span>
<svg
width="1.25rem"
height="1.25rem"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="text-gray-500"
>
<path
fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
d="M11.555 2.34556L11.5559 2.34511C11.694 2.27669 11.846 2.24109 12 2.24109C12.1541 2.24109 12.3061 2.27669 12.4442 2.34511L12.445 2.34556L19.759 6.00252L12 9.88198L4.24113 6.00252L11.555 2.34556ZM3.00005 7.61805V16.767C2.99875 16.9534 3.04954 17.1364 3.14672 17.2954C3.24374 17.4542 3.38313 17.5827 3.5492 17.6666L11 21.392V11.618L3.00005 7.61805ZM20.4428 17.6656L13 21.387V11.6181L21 7.61804V16.7695C20.9999 16.9555 20.948 17.1379 20.8499 17.296C20.7519 17.4541 20.6117 17.5817 20.445 17.6645L20.4428 17.6656ZM13.335 0.554497L12.89 1.45003L13.3373 0.555601L21.335 4.5545L21.3363 4.55513C21.8356 4.80352 22.2557 5.18614 22.5496 5.66006C22.8438 6.13439 22.9998 6.6819 23 7.24003V16.7706C22.9998 17.3287 22.8438 17.8757 22.5496 18.35C22.2557 18.824 21.8354 19.2067 21.336 19.4551L21.335 19.4556L13.3373 23.4545C12.9206 23.6629 12.461 23.7715 11.995 23.7715C11.529 23.7715 11.0693 23.6629 10.6525 23.4543L2.65283 19.4545L2.65007 19.4531C2.15077 19.2015 1.7317 18.8154 1.44015 18.3383C1.14927 17.8623 0.996878 17.3147 1.00005 16.7569L1.00005 7.2395C1.00034 6.68137 1.15633 6.13439 1.45047 5.66006C1.74436 5.18613 2.16454 4.80349 2.66381 4.55511L2.66505 4.5545L10.6628 0.555601L10.665 0.554497C11.0799 0.348361 11.5368 0.241089 12 0.241089C12.4633 0.241089 12.9202 0.348361 13.335 0.554497ZM12 7C12.8284 7 13.5 6.55228 13.5 6C13.5 5.44772 12.8284 5 12 5C11.1716 5 10.5 5.44772 10.5 6C10.5 6.55228 11.1716 7 12 7ZM5 14C5.55228 14 6 14.6716 6 15.5C6 16.3284 5.55228 17 5 17C4.44772 17 4 16.3284 4 15.5C4 14.6716 4.44772 14 5 14ZM10 13.5C10 12.6716 9.55228 12 9 12C8.44771 12 8 12.6716 8 13.5C8 14.3284 8.44771 15 9 15C9.55229 15 10 14.3284 10 13.5ZM15 12C15.5523 12 16 12.6716 16 13.5C16 14.3284 15.5523 15 15 15C14.4477 15 14 14.3284 14 13.5C14 12.6716 14.4477 12 15 12ZM16 17.5C16 16.6716 15.5523 16 15 16C14.4477 16 14 16.6716 14 17.5C14 18.3284 14.4477 19 15 19C15.5523 19 16 18.3284 16 17.5ZM19 14C19.5523 14 20 14.6716 20 15.5C20 16.3284 19.5523 17 19 17C18.4477 17 18 16.3284 18 15.5C18 14.6716 18.4477 14 19 14ZM20 11.5C20 10.6716 19.5523 10 19 10C18.4477 10 18 10.6716 18 11.5C18 12.3284 18.4477 13 19 13C19.5523 13 20 12.3284 20 11.5Z"
></path>
</svg>
</span>
<span class="flex-1 ml-3">摇一摇</span>
</button>
</div>
<footer v-if="tcdoffers.length > 0" class="flex mt-10 justify-end space-x-4">
<button
@click="$emit('close')"
class="btn border border-stone-300 hover:border-stone-300 bg-stone-200 hover:bg-stone-300 text-black h-9 min-h-fit"
>
取消
</button>
<button
@click="$emit('update-tcd', wantedTCD)"
:disabled="wantedTCD == ''"
class="btn border-0 bg-blue-500 hover:bg-blue-900 disabled:bg-blue-500/60 text-white disabled:text-white/60 h-9 min-h-fit"
>
保存
</button>
</footer>
</form>
<button
@click="$emit('close')"
class="btn btn-sm btn-ghost absolute top-5 right-5 px-2 py-2 border-0 bg-base-0 focus:bg-base-200 hover:bg-base-200"
type="button"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1.25em"
height="1.25em"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</div>
</template>
<style scoped>
.toggle {
border: 0;
--tglbg: #d6d3d1;
background-color: white;
}
.toggle:checked {
border: 0;
--tglbg: #1e40af;
background-color: white;
}
.toggle:disabled {
--togglehandleborder: 0 0 0 3px white inset,
var(--handleoffsetcalculator) 0 0 3px white inset;
}
.radio {
--chkbg: white;
border-width: 2px;
border-color: #d6d3d1;
}
.radio:checked {
--chkbg: white;
border-width: 5px;
border-color: #3e5db3;
}
</style>
+4 -1
View File
@@ -3,13 +3,13 @@ module MirageNetwork/MirageServer
go 1.20
require (
github.com/dexidp/dex v0.0.0-00010101000000-000000000000
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4
github.com/alibabacloud-go/dysmsapi-20170525/v3 v3.0.5
github.com/alibabacloud-go/eiam-developerapi-20220225/v2 v2.0.1
github.com/alibabacloud-go/tea v1.1.20
github.com/alibabacloud-go/tea-utils/v2 v2.0.1
github.com/coreos/go-oidc/v3 v3.5.0
github.com/dexidp/dex v0.0.0-00010101000000-000000000000
github.com/glebarez/sqlite v1.7.0
github.com/gorilla/mux v1.8.0
github.com/klauspost/compress v1.16.3
@@ -27,7 +27,9 @@ require (
gorm.io/gorm v1.24.6
tailscale.com v1.38.1
)
replace github.com/dexidp/dex => ./dex
require (
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect
@@ -83,6 +85,7 @@ require (
github.com/mdlayher/socket v0.4.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sethvargo/go-diceware v0.3.0
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
+2
View File
@@ -376,6 +376,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA=
github.com/sethvargo/go-diceware v0.3.0 h1:UVVEfmN/uF50JfWAN7nbY6CiAlp5xeSx+5U0lWKkMCQ=
github.com/sethvargo/go-diceware v0.3.0/go.mod h1:lH5Q/oSPMivseNdhMERAC7Ti5oOPqsaVddU1BcN1CY0=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=