广东电信 IPTV + LAN 分线器 - 关闭次级路由器的 DHCP 功能
环境:墙里只预留了一条网线,但现在需要同时使用 IPTV 和 LAN 两条网线。
尝试:按网友说的,简单方法之一:是墙的两头分别用一个分线器,将原八芯的网线,分成两条四芯的网线使用。
遇到的问题:IPTV 能正常使用,但 LAN 连接路由器和笔记本电脑测试时,会间隔着连续丢包。用网线测试仪测试,网线物理连接应该没有问题。尝试关闭 LAN 端连接着的路由器 DHCP 功能。正常了。
环境:墙里只预留了一条网线,但现在需要同时使用 IPTV 和 LAN 两条网线。
尝试:按网友说的,简单方法之一:是墙的两头分别用一个分线器,将原八芯的网线,分成两条四芯的网线使用。
遇到的问题:IPTV 能正常使用,但 LAN 连接路由器和笔记本电脑测试时,会间隔着连续丢包。用网线测试仪测试,网线物理连接应该没有问题。尝试关闭 LAN 端连接着的路由器 DHCP 功能。正常了。
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="windowsPE">
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<UserData>
<AcceptEula>true</AcceptEula>
</UserData>
<DiskConfiguration>
<WillShowUI>OnError</WillShowUI>
<Disk wcm:action="add">
<CreatePartitions>
<CreatePartition wcm:action="add">
<Order>1</Order>
<Size>100</Size>
<Type>Primary</Type>
</CreatePartition>
<CreatePartition wcm:action="add">
<Extend>true</Extend>
<Order>2</Order>
<Type>Primary</Type>
</CreatePartition>
</CreatePartitions>
<ModifyPartitions>
<ModifyPartition wcm:action="add">
<Active>true</Active>
<Format>NTFS</Format>
<Label>System</Label>
<Order>1</Order>
<PartitionID>1</PartitionID>
</ModifyPartition>
<ModifyPartition wcm:action="add">
<Order>2</Order>
<PartitionID>2</PartitionID>
<Label>Windows</Label>
<Letter>C</Letter>
<Format>NTFS</Format>
</ModifyPartition>
</ModifyPartitions>
<DiskID>0</DiskID>
<WillWipeDisk>true</WillWipeDisk>
</Disk>
</DiskConfiguration>
<ImageInstall>
<OSImage>
<InstallTo>
<DiskID>0</DiskID>
<PartitionID>2</PartitionID>
</InstallTo>
<InstallFrom>
<MetaData wcm:action="add">
<Key>/IMAGE/INDEX</Key>
<Value>2</Value>
</MetaData>
</InstallFrom>
<WillShowUI>OnError</WillShowUI>
<InstallToAvailablePartition>false</InstallToAvailablePartition>
</OSImage>
</ImageInstall>
</component>
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<SetupUILanguage>
<UILanguage>en-US</UILanguage>
</SetupUILanguage>
<InputLocale>0409:00000409</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UILanguageFallback>en-US</UILanguageFallback>
<UserLocale>en-US</UserLocale>
</component>
</settings>
<settings pass="oobeSystem">
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<UserAccounts>
<LocalAccounts>
<LocalAccount wcm:action="add">
<Password>
<Value>($PASS)</Value>
<PlainText>true</PlainText>
</Password>
<Description>Local Administrator</Description>
<DisplayName>Administrator</DisplayName>
<Group>Administrators</Group>
<Name>Administrator</Name>
</LocalAccount>
</LocalAccounts>
<AdministratorPassword>
<Value>($PASS)</Value>
</AdministratorPassword>
</UserAccounts>
<OOBE>
<HideEULAPage>true</HideEULAPage>
<SkipMachineOOBE>true</SkipMachineOOBE>
<SkipUserOOBE>true</SkipUserOOBE>
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
</OOBE>
</component>
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<InputLocale>en-US</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UserLocale>en-US</UserLocale>
</component>
</settings>
<settings pass="offlineServicing">
</settings>
<settings pass="specialize">
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-TCPIP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<Interfaces>
<Interface wcm:action="add">
<Ipv4Settings>
<DhcpEnabled>false</DhcpEnabled>
<RouterDiscoveryEnabled>false</RouterDiscoveryEnabled>
</Ipv4Settings>
<Ipv6Settings>
<DhcpEnabled>false</DhcpEnabled>
<RouterDiscoveryEnabled>false</RouterDiscoveryEnabled>
</Ipv6Settings>
<Identifier>($WIN_MAC)</Identifier>
<UnicastIpAddresses>
<IpAddress wcm:action="add" wcm:keyValue="1">($IP)/($NETMASK_SHORT)</IpAddress>
<IpAddress wcm:action="add" wcm:keyValue="2">($IPv6)/($NETMASKv6)</IpAddress>
</UnicastIpAddresses>
<Routes>
<Route wcm:action="add">
<Identifier>1</Identifier>
<Metric>10</Metric>
<NextHopAddress>($GATEWAYv4)</NextHopAddress>
<Prefix>0.0.0.0/0</Prefix>
</Route>
<Route wcm:action="add">
<Identifier>2</Identifier>
<Metric>10</Metric>
<NextHopAddress>($GATEWAYv6)</NextHopAddress>
<Prefix>::/0</Prefix>
</Route>
</Routes>
</Interface>
<!--Interface wcm:action="add">
<Ipv4Settings>
<DhcpEnabled>false</DhcpEnabled>
<RouterDiscoveryEnabled>false</RouterDiscoveryEnabled>
</Ipv4Settings>
<Ipv6Settings>
<DhcpEnabled>false</DhcpEnabled>
<RouterDiscoveryEnabled>false</RouterDiscoveryEnabled>
</Ipv6Settings>
<Identifier>Ethernet 2</Identifier>
</Interface-->
</Interfaces>
</component>
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-DNS-Client" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<Interfaces>
<Interface wcm:action="add">
<Identifier>($WIN_MAC)</Identifier>
<DNSDomain>($HOSTNAME)</DNSDomain>
<DNSServerSearchOrder>
<IpAddress wcm:action="add" wcm:keyValue="1">($NAMESERVER)</IpAddress>
</DNSServerSearchOrder>
<DisableDynamicUpdate>false</DisableDynamicUpdate>
<EnableAdapterDomainNameRegistration>false</EnableAdapterDomainNameRegistration>
</Interface>
</Interfaces>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<fDenyTSConnections>false</fDenyTSConnections>
</component>
<component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<UserAuthentication>0</UserAuthentication>
</component>
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<FirewallGroups>
<FirewallGroup wcm:action="add" wcm:keyValue="RemoteDesktop">
<Active>true</Active>
<Group>@FirewallAPI.dll,-28752</Group>
<Profile>all</Profile>
</FirewallGroup>
</FirewallGroups>
</component>
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Description>Enable administrator</Description>
<Order>1</Order>
<Path>net user administrator /active:yes</Path>
<WillReboot>OnRequest</WillReboot>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Description>Delete route</Description>
<Order>2</Order>
<Path>cmd /c echo IF "($IPv4)"=="" (route delete 0.0.0.0/0) ELSE IF "($IPv6)"=="" (route delete ::/0) >> %windir%\setup\scripts\SetupComplete.cmd</Path>
<WillReboot>OnRequest</WillReboot>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>3</Order>
<Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell" /v ExecutionPolicy /t REG_SZ /d RemoteSigned /f</Path>
<WillReboot>OnRequest</WillReboot>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Description>Disable Network Discovery</Description>
<Order>4</Order>
<Path>cmd /c echo cmd /c %windir%\System32\WindowsPowerShell\v1.0\powershell.exe -command "& {&'Disable-NetFirewallRule' –DisplayName '@FirewallAPI.dll,-32752'}" >> %windir%\setup\scripts\SetupComplete.cmd</Path>
<WillReboot>OnRequest</WillReboot>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Description>Setting multiple nameservers</Description>
<Order>5</Order>
<Path>cmd /c echo cmd /c %windir%\setup\scripts\nameservers.cmd ($NAMESERVERS) >> %windir%\setup\scripts\SetupComplete.cmd</Path>
<WillReboot>OnRequest</WillReboot>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Description>Enable language</Description>
<Order>6</Order>
<Path>cmd /c %windir%\System32\WindowsPowerShell\v1.0\powershell.exe %windir%\setup\scripts\lang.ps1 "($LANG)" >>%windir%\setup\scripts\lang.log 2>&1</Path>
<WillReboot>Never</WillReboot>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Description>Postinstall script listener</Description>
<Order>7</Order>
<Path>cmd /c IF "($HAS_RECIPE)"=="1" (echo cmd /c %windir%\System32\WindowsPowerShell\v1.0\powershell.exe %windir%\setup\scripts\winrm_listener.ps1 "($HOSTNAME)" "($LOCATIONIP)") >> %windir%\setup\scripts\SetupComplete.cmd</Path>
<WillReboot>OnRequest</WillReboot>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Description>TZ setup</Description>
<Order>8</Order>
<Path>cmd /c IF NOT "($TIMEZONE)" == "()" ( IF NOT "($TIMEZONE)" == "" (tzutil /s "($TIMEZONE)"))</Path>
<WillReboot>OnRequest</WillReboot>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Description>finish</Description>
<Order>9</Order>
<Path>cmd /c echo cmd /c %windir%\setup\scripts\wget.exe -O- "http://dcimserver/dcimgr?func=osinstall.finish&id=($AUTH_ID)" >> %windir%\setup\scripts\SetupComplete.cmd</Path>
<WillReboot>OnRequest</WillReboot>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<cpi:offlineImage xmlns:cpi="urn:schemas-microsoft-com:cpi" cpi:source="catalog:c:/distr/7601.17514.101119-1850_x64fre_server_eval_en-us-grmsxeval_en_dvd/sources/install_windows server 2008 r2 serverstandard.clg"/>
</unattend>
环境:debian 12.10 ,iRedMail - 1.7.3
目标:批量配置域名 dkim
步骤:
如需手工操作:
编辑 /etc/amavis/conf.d/50-user 文件内容,将签名的邮件域名和文件等追加进去
# Add dkim_key here.
dkim_key('iredmail.demo.anqun.org', 'dkim', '/var/lib/dkim/iredmail.demo.anqun.org.pem');
@dkim_signature_options_bysender_maps = ({
# 'd' defaults to a domain of an author/sender address,
# 's' defaults to whatever selector is offered by a matching key
# Per-domain dkim key
#"domain.com" => { d => "domain.com", a => 'rsa-sha256', ttl => 10*24*3600 },
# catch-all (one dkim key for all domains)
'.' => {d => 'iredmail.demo.anqun.org',
a => 'rsa-sha256',
c => 'relaxed/simple',
ttl => 30*24*3600 },
});
dkim_setup.py 的文件内容:
#!/usr/bin/env python3
import subprocess
import os
import datetime
import json
AMAVIS_CONFIG_FILE = "/etc/amavis/conf.d/50-user"
DKIM_BASE_PATH = "/var/lib/dkim"
DKIM_KEY_SIZE = 2048 # Or 2048, if desired
AMAVIS_COMMAND = "amavisd" # Changed from amavisd-new
DNS_OUTPUT_FILE = "dkim_dns_records.json"
def generate_dkim_key(domain):
"""Generates a DKIM key for the given domain."""
key_path = os.path.join(DKIM_BASE_PATH, f"{domain}.pem")
command = [AMAVIS_COMMAND, "genrsa", key_path, str(DKIM_KEY_SIZE)]
try:
subprocess.run(command, check=True)
subprocess.run(["chown", "amavis:amavis", key_path], check=True)
subprocess.run(["chmod", "0400", key_path], check=True)
print(f"DKIM key generated for {domain} at {key_path}")
return key_path
except subprocess.CalledProcessError as e:
print(f"Error generating DKIM key for {domain}: {e}")
return None
def get_dkim_public_key(key_path):
"""Extracts the DKIM public key from the .pem file using openssl."""
try:
command = ["openssl", "rsa", "-in", key_path, "-pubout", "-outform", "PEM"]
process = subprocess.run(command, capture_output=True, text=True, check=True)
public_key = process.stdout.strip()
# Remove the BEGIN and END PUBLIC KEY lines and any newlines
public_key = public_key.replace("-----BEGIN PUBLIC KEY-----", "")
public_key = public_key.replace("-----END PUBLIC KEY-----", "")
public_key = public_key.replace("\n", "")
return public_key
except subprocess.CalledProcessError as e:
print(f"Error extracting DKIM public key using openssl: {e}")
print(f"Stderr: {e.stderr}") # Print stderr for more details
return None
except Exception as e:
print(f"Error extracting DKIM public key: {e}")
return None
def domain_exists(domain):
"""Checks if the domain is already configured in Amavis."""
try:
with open(AMAVIS_CONFIG_FILE, "r") as f:
config_content = f.read()
return domain in config_content
except FileNotFoundError:
print(f"Error: Amavis config file not found at {AMAVIS_CONFIG_FILE}")
return False
def backup_amavis_config():
"""Backs up the Amavis configuration file with a timestamp."""
timestamp = datetime.datetime.now().strftime("%Y.%m.%d.%H.%M.%S")
backup_file = f"{AMAVIS_CONFIG_FILE}.{timestamp}"
try:
subprocess.run(["cp", AMAVIS_CONFIG_FILE, backup_file], check=True)
print(f"Amavis config backed up to {backup_file}")
return True
except subprocess.CalledProcessError as e:
print(f"Error backing up Amavis config: {e}")
return False
def update_amavis_config(domain, key_path):
"""Updates the Amavis configuration file with the DKIM settings."""
try:
with open(AMAVIS_CONFIG_FILE, "r") as f:
config_lines = f.readlines()
dkim_key_insert_point = None
start_index = None
end_index = None
last_entry_index = None
for i, line in enumerate(config_lines):
if "dkim_key(" in line:
dkim_key_insert_point = i + 1
if "@dkim_signature_options_bysender_maps = (" in line:
start_index = i
if start_index is not None and "=>" in line:
# 检查是否是一个 domain entry 的开始
if line.lstrip().startswith('"') or line.lstrip().startswith("'"):
last_entry_index = i
if "});" in line and start_index is not None:
end_index = i
break
if dkim_key_insert_point is None:
print("Could not find insertion point for dkim_key.")
return False
if start_index is None or end_index is None:
print("Could not find signature options structure.")
return False
# 如果没有找到任何已有的 domain entry,则默认插入在 start_index + 1
insert_sig_index = last_entry_index + 1 if last_entry_index is not None else start_index + 1
# 构造新行
new_dkim_key_line = f"dkim_key('{domain}', 'dkim', '{key_path}');\n"
new_dkim_sig_line = f' "{domain}" => {{ d => "{domain}", a => \'rsa-sha256\', ttl => 10*24*3600 }},\n'
# 插入 dkim_key
config_lines.insert(dkim_key_insert_point, new_dkim_key_line)
# 如果不是第一个条目,检查前一行是否有逗号
if last_entry_index is not None:
prev_line = config_lines[insert_sig_index - 1]
if not prev_line.strip().endswith(','):
config_lines[insert_sig_index - 1] = prev_line.rstrip('\n') + ',\n'
# 插入新的签名选项
config_lines.insert(insert_sig_index, new_dkim_sig_line)
# 写回文件
with open(AMAVIS_CONFIG_FILE, "w") as f:
f.writelines(config_lines)
print(f"Amavis config updated for {domain}")
return True
except FileNotFoundError:
print(f"Error: Amavis config file not found at {AMAVIS_CONFIG_FILE}")
return False
except Exception as e:
print(f"Error updating Amavis config: {e}")
return False
def restart_amavis():
"""Restarts the Amavis service."""
try:
subprocess.run(["systemctl", "restart", "amavis"], check=True) # Corrected command
print("Amavis service restarted.")
return True
except subprocess.CalledProcessError as e:
print(f"Error restarting Amavis: {e}")
return False
def write_dns_record(domain, public_key):
"""Writes the DNS record to a JSON file."""
dns_record_name = f"dkim._domainkey.{domain}"
# Construct the TXT record value
txt_record_value = f"v=DKIM1; p={public_key}"
data = {dns_record_name: txt_record_value}
try:
# Check if the file exists and load existing data
if os.path.exists(DNS_OUTPUT_FILE):
with open(DNS_OUTPUT_FILE, "r") as f:
try:
existing_data = json.load(f)
except json.JSONDecodeError:
existing_data = {} # Handle empty or corrupted JSON file
else:
existing_data = {}
# Update with the new record
existing_data.update(data)
# Write back to the file
with open(DNS_OUTPUT_FILE, "w") as f:
json.dump(existing_data, f, indent=4, ensure_ascii=False) # indent for readability, disable ASCII escaping
print(f"DNS record written to {DNS_OUTPUT_FILE}")
except Exception as e:
print(f"Error writing DNS record to file: {e}")
if __name__ == "__main__":
import sys
# Backup Amavis config *before* processing any domains
if not backup_amavis_config():
print("Failed to backup Amavis config. Aborting.")
sys.exit(1)
try:
with open("domains.txt", "r") as f:
domains = [line.strip() for line in f.readlines()]
except FileNotFoundError:
print("Error: domains.txt not found")
sys.exit(1)
for domain in domains:
print(f"Processing domain: {domain}")
if domain_exists(domain):
print(f"Domain {domain} already exists in Amavis config. Skipping.")
continue
key_path = generate_dkim_key(domain)
if key_path:
if update_amavis_config(domain, key_path):
public_key = get_dkim_public_key(key_path)
if public_key:
write_dns_record(domain, public_key)
else:
print(f"Failed to get public key for {domain}")
else:
print(f"Failed to update amavis config for {domain}")
if domains: #Only restart if there was at least one domain to process
restart_amavis()
演示视频:https://www.bilibili.com/video/BV1Jj7fztEN3/
参考:
环境:inbucket_3.1.0-beta2_linux_amd64.deb
目标:在已有的网页界面里,添加一个“随机生成邮箱”的菜单标签。访客点击这个标签,会自动生成一个随机邮箱用户名(8位,包含数字和小写字母),且会自动跳转到相应的收件箱页面,如“最近邮箱”标签的作用类似。方便访客使用。
尝试:照着 inbucket 的文档,在 AI (poe 和 deepseek)的帮助下,对 ui/src 里的 Layout.elm 文件内容做了修改。尽可能不改动其它文件内容。
这里是我修改后的 Layout.elm 文件内容:
module Layout exposing (Model, Msg, Page(..), frame, init, reset, update)
import Data.Session as Session exposing (Session)
import Effect exposing (Effect)
import Html
exposing
( Attribute
, Html
, a
, button
, div
, footer
, form
, h2
, header
, i
, input
, li
, nav
, pre
, span
, td
, text
, th
, tr
, ul
)
import Html.Attributes
exposing
( attribute
, class
, classList
, href
, placeholder
, rel
, target
, type_
, value
)
import Html.Events as Events
import Modal
import Route
import Timer exposing (Timer)
import Random
import Time
generateRandomMailbox : Time.Posix -> Int -> String
generateRandomMailbox time length =
let
chars = "abcdefghijklmnopqrstuvwxyz0123456789"
charsLength = String.length chars
-- 使用当前时间的毫秒数作为种子
seed =
Random.initialSeed (Time.posixToMillis time)
-- 生成随机索引列表
randomIndices =
Random.step (Random.list length (Random.int 0 (charsLength - 1))) seed
|> Tuple.first
-- 将随机索引转换为随机字符
randomChars =
List.map (\index -> String.slice index (index + 1) chars) randomIndices
in
String.fromList (List.concat (List.map String.toList randomChars))
{-| Used to highlight current page in navbar.
-}
type Page
= Other
| Mailbox
| Monitor
| Status
type alias Model msg =
{ mapMsg : Msg -> msg
, mainMenuVisible : Bool
, recentMenuVisible : Bool
, recentMenuTimer : Timer
, mailboxName : String
}
init : (Msg -> msg) -> Model msg
init mapMsg =
{ mapMsg = mapMsg
, mainMenuVisible = False
, recentMenuVisible = False
, recentMenuTimer = Timer.empty
, mailboxName = ""
}
{-| Resets layout state, used when navigating to a new page.
-}
reset : Model msg -> Model msg
reset model =
{ model
| mainMenuVisible = False
, recentMenuVisible = False
, recentMenuTimer = Timer.cancel model.recentMenuTimer
, mailboxName = ""
}
type Msg
= ClearFlash
| MainMenuToggled
| ModalFocused Modal.Msg
| ModalUnfocused
| OnMailboxNameInput String
| OpenMailbox
| RecentMenuMouseOver
| RecentMenuMouseOut
| RecentMenuTimeout Timer
| RecentMenuToggled
| RandomMailbox
| GotTimeForRandomMailbox Time.Posix
update : Msg -> Model msg -> ( Model msg, Effect msg )
update msg model =
case msg of
ClearFlash ->
( model, Effect.clearFlash )
MainMenuToggled ->
( { model | mainMenuVisible = not model.mainMenuVisible }, Effect.none )
ModalFocused message ->
( model, Effect.focusModalResult message )
ModalUnfocused ->
( model, Effect.focusModal (ModalFocused >> model.mapMsg) )
OnMailboxNameInput name ->
( { model | mailboxName = name }, Effect.none )
OpenMailbox ->
if model.mailboxName == "" then
( model, Effect.none )
else
( model
, Effect.navigateRoute True (Route.Mailbox model.mailboxName)
)
RecentMenuMouseOver ->
( { model
| recentMenuVisible = True
, recentMenuTimer = Timer.cancel model.recentMenuTimer
}
, Effect.none
)
RecentMenuMouseOut ->
let
-- Keep the recent menu open for a moment even if the mouse leaves it.
newTimer =
Timer.replace model.recentMenuTimer
in
( { model
| recentMenuTimer = newTimer
}
, Effect.schedule (RecentMenuTimeout >> model.mapMsg) newTimer 400
)
RecentMenuTimeout timer ->
if timer == model.recentMenuTimer then
( { model
| recentMenuVisible = False
, recentMenuTimer = Timer.cancel timer
}
, Effect.none
)
else
-- Timer was no longer valid.
( model, Effect.none )
RecentMenuToggled ->
( { model | recentMenuVisible = not model.recentMenuVisible }
, Effect.none
)
RandomMailbox ->
-- 获取当前时间,并通过 Effect.map 转换为 msg
( model
, Effect.map model.mapMsg (Effect.posixTime GotTimeForRandomMailbox)
)
GotTimeForRandomMailbox time ->
-- 生成随机邮箱并导航
let
randomMailbox = generateRandomMailbox time 8
in
( model
, Effect.navigateRoute True (Route.Mailbox randomMailbox)
)
type alias State msg =
{ model : Model msg
, session : Session
, activePage : Page
, activeMailbox : String
, modal : Maybe (Html msg)
, content : List (Html msg)
}
frame : State msg -> Html msg
frame { model, session, activePage, activeMailbox, modal, content } =
div [ class "app" ]
[ header []
[ nav [ class "navbar" ]
[ button [ class "navbar-toggle", Events.onClick (MainMenuToggled |> model.mapMsg) ]
[ i [ class "fas fa-bars" ] [] ]
, span [ class "navbar-brand" ]
[ a [ href <| session.router.toPath Route.Home ] [ text "@ inbucket" ] ]
, ul [ class "main-nav", classList [ ( "active", model.mainMenuVisible ) ] ]
[ if session.config.monitorVisible then
navbarLink Monitor (session.router.toPath Route.Monitor) [ text "Monitor" ] activePage
else
text ""
, li []
[ a [ href "#", Events.onClick (RandomMailbox |> model.mapMsg) ]
[ text "Random Mailbox" ]
]
, navbarLink Status (session.router.toPath Route.Status) [ text "Status" ] activePage
, navbarRecent activePage activeMailbox model session
, li [ class "navbar-mailbox" ]
[ form [ Events.onSubmit (OpenMailbox |> model.mapMsg) ]
[ input
[ type_ "text"
, placeholder "mailbox"
, value model.mailboxName
, Events.onInput (OnMailboxNameInput >> model.mapMsg)
]
[]
]
]
]
]
]
, div [ class "navbar-bg" ] [ text "" ]
, Modal.view (ModalUnfocused |> model.mapMsg) modal
, div [ class "page" ] (errorFlash model session.flash :: content)
, footer []
[ div [ class "footer" ]
[ externalLink "https://www.inbucket.org" "Inbucket"
, text " is an open source project hosted on "
, externalLink "https://github.com/inbucket/inbucket" "GitHub"
, text "."
]
]
]
errorFlash : Model msg -> Maybe Session.Flash -> Html msg
errorFlash model maybeFlash =
let
row ( heading, message ) =
tr []
[ th [] [ text (heading ++ ":") ]
, td [] [ pre [] [ text message ] ]
]
in
case maybeFlash of
Nothing ->
text ""
Just flash ->
div [ class "well well-error" ]
[ div [ class "flash-header" ]
[ h2 [] [ text flash.title ]
, a [ href "#", Events.onClick (ClearFlash |> model.mapMsg) ] [ text "Close" ]
]
, div [ class "flash-table" ] (List.map row flash.table)
]
externalLink : String -> String -> Html a
externalLink url title =
a [ href url, target "_blank", rel "noopener" ] [ text title ]
navbarLink : Page -> String -> List (Html a) -> Page -> Html a
navbarLink page url linkContent activePage =
li [ classList [ ( "navbar-active", page == activePage ) ] ]
[ a [ href url ] linkContent ]
{-| Renders list of recent mailboxes, selecting the currently active mailbox.
-}
navbarRecent : Page -> String -> Model msg -> Session -> Html msg
navbarRecent page activeMailbox model session =
let
-- Active means we are viewing a specific mailbox.
active =
page == Mailbox
-- Recent tab title is the name of the current mailbox when active.
title =
if active then
activeMailbox
else
"Recent Mailboxes"
-- Mailboxes to show in recent list, doesn't include active mailbox.
recentMailboxes =
if active then
List.tail session.persistent.recentMailboxes |> Maybe.withDefault []
else
session.persistent.recentMailboxes
recentLink mailbox =
a [ href <| session.router.toPath <| Route.Mailbox mailbox ] [ text mailbox ]
in
li
[ class "navbar-dropdown-container"
, classList [ ( "navbar-active", active ) ]
, attribute "aria-haspopup" "true"
, ariaExpanded model.recentMenuVisible
, Events.onMouseOver (RecentMenuMouseOver |> model.mapMsg)
, Events.onMouseOut (RecentMenuMouseOut |> model.mapMsg)
]
[ span [ class "navbar-dropdown" ]
[ text title
, button
[ class "navbar-dropdown-button"
, Events.onClick (RecentMenuToggled |> model.mapMsg)
]
[ i [ class "fas fa-chevron-down" ] [] ]
]
, div [ class "navbar-dropdown-content" ] (List.map recentLink recentMailboxes)
]
ariaExpanded : Bool -> Attribute msg
ariaExpanded value =
attribute "aria-expanded" <|
if value then
"true"
else
"false"
修改 Layout.elm 文件过程中,顺便也可以翻译一下 Web 界面的英文。
参考:
问题:在 Hestiacp 的 nginx proxy + apache2 环境中,下载最新的 FOSSBilling 0.6.22 zip 版本文件,顺利安装。但安装后,无论访问 /admin 还是 news 等各个网址,都是显示 首页 的内容。
尝试:在线安装成功后,网站根目录 .htaccess 的文件内容,关键的一条静态化规则是 RewriteRule ^(.*)$ index.php [QSA,L] ,但安装包里的,是 RewriteRule ^(.*)$ index.php?_url=/$1 [QSA,L] 。用安装包里的 .htaccess 替代,即可。
参考: