apiserver/ews/ews.go
2023-12-08 15:45:50 +08:00

790 lines
20 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ews
import (
"APIServer/cnf"
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
type EWS struct{}
type UutInfo struct {
USN string
MAC string
IPAddr string
Relay string
Item string
Status string
Message string
PartNO string
MfgMO string
MfgSKU string
MfgLine string
MfgStage string
FirstAck string
LastAck string
LastChg string
}
type DefectInfo struct {
USN string
PartNO string
MfgMO string
SKU string
Line string
Location string
Item string
Status string
Message string
FirstAck string
LastAck string
LastChg string
DiffMins string
}
func (e *EWS) SetUutInfo(cfg cnf.Cfg, logger *log.Logger, host string, addr string, uri string, params map[string]string) map[string]string {
if cfg.Settings.LogRequest {
logger.Printf("[MSG] %s; %s; Request: %#v\r\n", addr, uri, params)
}
rst := map[string]string{"RESULT": ""}
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s",
cfg.MySQL.User,
cfg.MySQL.Password,
cfg.MySQL.Server,
cfg.MySQL.Port,
cfg.MySQL.Database,
)
dbo, err := sql.Open("mysql", dsn)
if err != nil {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = err.Error()
return rst
}
defer dbo.Close()
err = dbo.Ping()
if err != nil {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = err.Error()
return rst
}
rec := dbo.QueryRow(
fmt.Sprintf(`SELECT usn,mac,ipaddr,relay,item,status,message,partno,mo,sku,line,stage,first_ack,last_ack,last_change
FROM uutinfo
WHERE usn='%s';`, params["USN"]))
ui := new(UutInfo)
err = rec.Scan(
&ui.USN,
&ui.MAC,
&ui.IPAddr,
&ui.Relay,
&ui.Item,
&ui.Status,
&ui.Message,
&ui.PartNO,
&ui.MfgMO,
&ui.MfgSKU,
&ui.MfgLine,
&ui.MfgStage,
&ui.FirstAck,
&ui.LastAck,
&ui.LastChg,
)
//妿žœåœ¨uutinfo表中未查找到此USN的记录, 则æ°å¢žä¸€æ<E282AC>¡æ­¤USN的记录
if err == sql.ErrNoRows {
dmlInsert := fmt.Sprintf("INSERT INTO uutinfo (usn,mac,ipaddr,relay,item,status,message,partno,mo,sku,line,stage,first_ack,last_ack,last_change) VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s',%s,%s,%s);",
params["USN"],
params["MAC"],
addr,
host,
params["ITEM"],
params["STATUS"],
params["MESSAGE"],
params["PARTNO"],
params["MO"],
params["SKU"],
params["LINE"],
params["STAGE"],
"NOW()",
"NOW()",
"NOW()",
)
_, err = dbo.Exec(dmlInsert)
if err != nil {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = err.Error()
return rst
}
if cfg.Settings.LogResponse {
logger.Printf("[MSG] %s; %s; Response: %#v\r\n", addr, uri, rst)
}
rst["RESULT"] = "OK"
rst["ErrMsg"] = "Inserted 1 new record."
return rst
}
//妿žœæ­¤USN的记录存在与uutinfo表中, 且此USNçš„MAC/Item/Status/Messageå·²å<C2B2>˜åŒ, 则:
//1. 备份此USN对应记录行到uutinfobkup表
//2. æ´æ°uutinfo表中对应USN记录的MAC/Item/Status/Message/PartNO/MO/SKU/Line/Stage
//3. æ´æ°uutinfo表中此USN记录行的last_ackåŒlast_changeæ <C3A6>ä½<C3A4>为当å‰<C3A5>æ—¶é—´
extFlag := false
chgFlag := false
_, extFlag = params["MAC"]
if extFlag && params["MAC"] != ui.MAC {
chgFlag = true
}
_, extFlag = params["ITEM"]
if extFlag && params["ITEM"] != ui.Item {
chgFlag = true
}
_, extFlag = params["STATUS"]
if extFlag && params["STATUS"] != ui.Status {
chgFlag = true
}
_, extFlag = params["MESSAGE"]
if extFlag && params["MESSAGE"] != ui.Message {
chgFlag = true
}
if chgFlag {
tx, err := dbo.Begin()
if err != nil {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = err.Error()
return rst
}
stmt, err := tx.Prepare("INSERT INTO uutinfobkup (usn,mac,ipaddr,relay,item,status,message,partno,mo,sku,line,stage,first_ack,last_ack,last_change) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);")
if err != nil {
tx.Rollback()
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = err.Error()
return rst
}
defer stmt.Close()
_, err = stmt.Exec(
ui.USN,
ui.MAC,
addr,
host,
ui.Item,
ui.Status,
ui.Message,
ui.PartNO,
ui.MfgMO,
ui.MfgSKU,
ui.MfgLine,
ui.MfgStage,
ui.FirstAck,
ui.LastAck,
ui.LastChg,
)
if err != nil {
tx.Rollback()
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = err.Error()
return rst
}
stmt, err = tx.Prepare("UPDATE uutinfo SET mac=?, ipaddr=?, relay=?, item=?, status=?, message=?, partno=?, mo=?, sku=?, line=?, stage=?, last_ack=NOW(), last_change=NOW() WHERE usn=?;")
if err != nil {
tx.Rollback()
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = err.Error()
return rst
}
defer stmt.Close()
_, err = stmt.Exec(
params["MAC"],
addr,
host,
params["ITEM"],
params["STATUS"],
params["MESSAGE"],
params["PARTNO"],
params["MO"],
params["SKU"],
params["LINE"],
params["STAGE"],
params["USN"],
)
if err != nil {
tx.Rollback()
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = err.Error()
return rst
}
err = tx.Commit()
if err != nil {
tx.Rollback()
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = err.Error()
return rst
}
if cfg.Settings.LogResponse {
logger.Printf("[MSG] %s; %s; Response: %#v\r\n", addr, uri, rst)
}
rst["RESULT"] = "OK"
rst["ErrMsg"] = "Updated and backed up 1 record."
return rst
}
//妿žœæ­¤USN的记录存在与uutinfo表中, 且MAC/Item/Status/Message未å<C2AA>˜åŒ, åˆ™æ´æ°æ­¤USN记录的ipaddr,relay,stage, å¹¶æ´æ°last_ackæ <C3A6>ä½<C3A4>为当å‰<C3A5>æ—¶é—´
dmlUpdate := fmt.Sprintf("UPDATE uutinfo SET ipaddr='%s', relay='%s', stage='%s', last_ack=NOW() WHERE usn='%s'",
addr, host, params["STAGE"], params["USN"])
_, err = dbo.Exec(dmlUpdate)
if err != nil {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = err.Error()
return rst
}
if cfg.Settings.LogResponse {
logger.Printf("[MSG] %s; %s; Response: %#v\r\n", addr, uri, rst)
}
rst["RESULT"] = "OK"
rst["ErrMsg"] = "Updated 1 record."
return rst
}
func (e *EWS) GetUutInfo(cfg cnf.Cfg, logger *log.Logger, host string, addr string, uri string, params map[string]string) map[string]string {
if cfg.Settings.LogRequest {
logger.Printf("[MSG] %s; %s; Request: %#v\r\n", addr, uri, params)
}
rst := map[string]string{"RESULT": ""}
if params["KEY"] == "" || params["VALUE"] == "" {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, "Invalid Parameters !")
rst["RESULT"] = "NG"
rst["ErrMsg"] = e.SimpleMsgHTML("red", "Invalid Parameters !")
return rst
}
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s",
cfg.MySQL.User,
cfg.MySQL.Password,
cfg.MySQL.Server,
cfg.MySQL.Port,
cfg.MySQL.Database,
)
dbo, err := sql.Open("mysql", dsn)
if err != nil {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = e.SimpleMsgHTML("red", err.Error())
return rst
}
defer dbo.Close()
err = dbo.Ping()
if err != nil {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = e.SimpleMsgHTML("red", err.Error())
return rst
}
ul := new(UutInfo)
partNO := ""
mfgMO := ""
mfgSKU := ""
fstAck := ""
lstAck := ""
//Get Last Record
last := ""
curr, err := dbo.Query(
fmt.Sprintf(`SELECT usn,mac,ipaddr,relay,item,status,message,partno,mo,sku,line,stage,first_ack,last_ack,last_change
FROM uutinfo
WHERE %s='%s'
ORDER BY usn ASC, seqid ASC;`, params["KEY"], params["VALUE"]))
if err != nil {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = e.SimpleMsgHTML("red", err.Error())
return rst
}
defer curr.Close()
for curr.Next() {
err := curr.Scan(
&ul.USN,
&ul.MAC,
&ul.IPAddr,
&ul.Relay,
&ul.Item,
&ul.Status,
&ul.Message,
&ul.PartNO,
&ul.MfgMO,
&ul.MfgSKU,
&ul.MfgLine,
&ul.MfgStage,
&ul.FirstAck,
&ul.LastAck,
&ul.LastChg,
)
if err != nil {
break
}
last = last + fmt.Sprintf(`<tr>
<td style="color: blue;">%s</td>
<td style="color: blue;">%s</td>
<td style="color: blue;">%s</td>
<td style="color: blue;">%s</td>
<td style="color: blue;">%s</td>
<td style="color: blue;">%s</td>
<td style="color: blue;">%s</td>
<td style="color: blue;">%s</td>
</tr>%s`, ul.USN, ul.MAC, ul.IPAddr, ul.Relay, ul.Item, ul.Status, ul.Message, ul.LastChg, "\r\n")
if ul.PartNO != "" {
partNO = ul.PartNO
}
if ul.MfgMO != "" {
mfgMO = ul.MfgMO
}
if ul.MfgSKU != "" {
mfgSKU = ul.MfgSKU
}
if ul.FirstAck != "" {
fstAck = ul.FirstAck
}
if ul.LastAck != "" {
lstAck = ul.LastAck
}
}
//Get Location Information
loc, locTime := "", ""
if last != "" {
qry := dbo.QueryRow(fmt.Sprintf(`SELECT CONCAT_WS('-',line,col,row,num) AS loc, update_time FROM locinfo WHERE mac='%s'`, ul.MAC))
qry.Scan(&loc, &locTime)
}
//Get History Records
uh := new(UutInfo)
bkup := ""
prev, err := dbo.Query(
fmt.Sprintf(`SELECT usn,mac,ipaddr,relay,item,status,message,partno,mo,sku,line,stage,first_ack,last_ack,last_change
FROM uutinfobkup
WHERE %s='%s'
ORDER BY usn ASC, seqid ASC;`, params["KEY"], params["VALUE"]))
if err != nil {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = e.SimpleMsgHTML("red", err.Error())
return rst
}
defer prev.Close()
for prev.Next() {
err := prev.Scan(
&uh.USN,
&uh.MAC,
&uh.IPAddr,
&uh.Relay,
&uh.Item,
&uh.Status,
&uh.Message,
&uh.PartNO,
&uh.MfgMO,
&uh.MfgSKU,
&uh.MfgLine,
&uh.MfgStage,
&uh.FirstAck,
&uh.LastAck,
&uh.LastChg,
)
if err != nil {
break
}
bkup = bkup + fmt.Sprintf(`<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>%s`,
uh.USN, uh.MAC, uh.IPAddr, uh.Relay, uh.Item, uh.Status, uh.Message, uh.LastChg, "\r\n")
}
//Get Misc. Info
if partNO == "" {
partNO = uh.PartNO
}
if mfgMO == "" {
mfgMO = uh.MfgMO
}
if mfgSKU == "" {
mfgSKU = uh.MfgSKU
}
if fstAck == "" {
fstAck = uh.FirstAck
}
if lstAck == "" {
lstAck = uh.LastAck
}
misc := ""
misc = fmt.Sprintf("<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>",
partNO, mfgMO, mfgSKU, fstAck, lstAck)
rst["RESULT"] = "OK"
rst["ErrMsg"] = e.TemplateUutInfo(loc, locTime, misc, last, bkup)
if cfg.Settings.LogResponse {
logger.Printf("[MSG] %s; %s; Response: Length=%d\r\n", addr, uri, len(rst["ErrMsg"]))
}
return rst
}
func (e *EWS) GetDefectReport(cfg cnf.Cfg, logger *log.Logger, host string, addr string, uri string, params map[string]string) map[string]string {
if cfg.Settings.LogRequest {
logger.Printf("[MSG] %s; %s; Request: %#v\r\n", addr, uri, params)
}
rst := map[string]string{"RESULT": ""}
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s",
cfg.MySQL.User,
cfg.MySQL.Password,
cfg.MySQL.Server,
cfg.MySQL.Port,
cfg.MySQL.Database,
)
dbo, err := sql.Open("mysql", dsn)
if err != nil {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = e.SimpleMsgHTML("red", err.Error())
return rst
}
defer dbo.Close()
err = dbo.Ping()
if err != nil {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = e.SimpleMsgHTML("red", err.Error())
return rst
}
di := new(DefectInfo)
defect := ""
rows, err := dbo.Query(
`SELECT usn,partno,mo,sku,line,location,item,status,message,first_ack,last_ack,last_change,diffmins
FROM v_ngreport
ORDER BY diffmins ASC;`)
if err != nil {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = e.SimpleMsgHTML("red", err.Error())
return rst
}
defer rows.Close()
num := 1
for rows.Next() {
err := rows.Scan(
&di.USN,
&di.PartNO,
&di.MfgMO,
&di.SKU,
&di.Line,
&di.Location,
&di.Item,
&di.Status,
&di.Message,
&di.FirstAck,
&di.LastAck,
&di.LastChg,
&di.DiffMins,
)
if err != nil {
break
}
temp := fmt.Sprintf(`<tr><td>%d</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>%s`,
num, di.USN, di.PartNO, di.MfgMO, di.SKU, di.Line, di.Location, di.Item, di.Status, di.Message, di.FirstAck, di.LastAck, di.LastChg, di.DiffMins, "\r\n")
defect = defect + temp
num += 1
}
rst["RESULT"] = "OK"
rst["ErrMsg"] = e.TemplateDefectInfo(defect)
if cfg.Settings.LogResponse {
logger.Printf("[MSG] %s; %s; Response: Length=%d\r\n", addr, uri, len(rst["ErrMsg"]))
}
return rst
}
func (e *EWS) GetOfflineReport(cfg cnf.Cfg, logger *log.Logger, host string, addr string, uri string, params map[string]string) map[string]string {
if cfg.Settings.LogRequest {
logger.Printf("[MSG] %s; %s; Request: %#v\r\n", addr, uri, params)
}
rst := map[string]string{"RESULT": ""}
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s",
cfg.MySQL.User,
cfg.MySQL.Password,
cfg.MySQL.Server,
cfg.MySQL.Port,
cfg.MySQL.Database,
)
dbo, err := sql.Open("mysql", dsn)
if err != nil {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = e.SimpleMsgHTML("red", err.Error())
return rst
}
defer dbo.Close()
err = dbo.Ping()
if err != nil {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = e.SimpleMsgHTML("red", err.Error())
return rst
}
di := new(DefectInfo)
offline := ""
rows, err := dbo.Query(
`SELECT usn,partno,mo,sku,line,location,item,status,message,first_ack,last_ack,last_change,diffmins
FROM v_offline
ORDER BY diffmins DESC;`)
if err != nil {
logger.Printf("[ERR] %s; %s; %#v; %s\r\n", addr, uri, params, err.Error())
rst["RESULT"] = "NG"
rst["ErrMsg"] = e.SimpleMsgHTML("red", err.Error())
return rst
}
defer rows.Close()
num := 1
for rows.Next() {
err := rows.Scan(
&di.USN,
&di.PartNO,
&di.MfgMO,
&di.SKU,
&di.Line,
&di.Location,
&di.Item,
&di.Status,
&di.Message,
&di.FirstAck,
&di.LastAck,
&di.LastChg,
&di.DiffMins,
)
if err != nil {
break
}
temp := fmt.Sprintf(`<tr><td>%d</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>%s`,
num, di.USN, di.PartNO, di.MfgMO, di.SKU, di.Line, di.Location, di.Item, di.Status, di.Message, di.FirstAck, di.LastAck, di.LastChg, di.DiffMins, "\r\n")
offline = offline + temp
num += 1
}
rst["RESULT"] = "OK"
rst["ErrMsg"] = e.TemplateDefectInfo(offline)
if cfg.Settings.LogResponse {
logger.Printf("[MSG] %s; %s; Response: Length=%d\r\n", addr, uri, len(rst["ErrMsg"]))
}
return rst
}
func (e *EWS) SimpleMsgHTML(clr, msg string) string {
now := time.Now().Format("2006-01-02 15:04:05")
resp := fmt.Sprintf(`<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<head>
<style>pre {word-wrap: break-word;}</style>
<style>body {font-family: Consolas; font-size: 14px;}</style>
</head>
<body>
<br/><span style="color: %s;">[%s] %s</span><br/>
</body>
</html>`, clr, now, msg)
return resp
}
func (e *EWS) TemplateUutInfo(loc, locTime, misc, last, bkup string) string {
now := time.Now().Format("2006-01-02 15:04:05")
if loc == "" {
loc = `<span style="color: red; font-size: 14px; font-family: Consolas;">Unknown</span>`
}
// if misc == "" {
// misc = `<span style="color: red;">NO Records !</span>`
// }
// if bkup == "" {
// bkup = `<span style="color: red;">NO Records !</span>`
// }
resp := fmt.Sprintf(`<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Test Records</title>
<style type="text/css">
table {
border: 1px solid rgb(81, 130, 187);
border-collapse: separate;
border-spacing: 1px;
position: static;
}
th {
background-color: rgb(81, 130, 187);
color: #fff;
border: 1px solid rgb(81, 130, 187);
padding: 5px 10px;
font-size: 12px;
font-family: Verdana;
font-weight: bold;
}
tr {
border: 1px solid rgb(81, 130, 187);
}
td {
color: #000;
padding: 5px 10px;
font-size: 12px;
font-family: Consolas;
}
span {
font-size: 14px;
font-family: Consolas;
}
</style>
<body>
<span>Response Time: %s</span><br/><br/>
<span style="font-weight: bold; font-size: 16px;">Location Info</span>
<table>
<tr>
<th>Location</th>
<th>Location Time</th>
</tr>
<tr>
<td>%s</td>
<td>%s</td>
</tr>
</table>
<br/>
<span style="font-weight: bold; font-size: 16px;">Misc. Info</span>
<br/>
<table>
<tr>
<th>PartNO.</th>
<th>MO</th>
<th>SKU</th>
<th>First Ack</th>
<th>Last Ack</th>
</tr>
%s
</table>
<br/>
<span style="font-weight: bold; font-size: 16px;">测试记录</span>
<br/>
<table>
<tr>
<th>USN</th>
<th>MAC</th>
<th>IP Address</th>
<th>Server</th>
<th>Test Item</th>
<th>Status</th>
<th>Message</th>
<th>Last Change</th>
</tr>
%s
%s
</table>
</body>
</html>`, now, loc, locTime, misc, last, bkup)
return resp
}
func (e *EWS) TemplateDefectInfo(src string) string {
now := time.Now().Format("2006-01-02 15:04:05")
if src == "" {
src = fmt.Sprintf(`<span style="color: red;">[%s] NO Records !</span><br/>`, now)
}
resp := fmt.Sprintf(`<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Test Records</title>
<style type="text/css">
table {
border: 1px solid rgb(81, 130, 187);
border-collapse: separate;
border-spacing: 1px;
position: static;
}
th {
background-color: rgb(81, 130, 187);
color: #fff;
border: 1px solid rgb(81, 130, 187);
padding: 5px 10px;
font-size: 12px;
font-family: Verdana;
font-weight: bold;
}
tr {
border: 1px solid rgb(81, 130, 187);
}
td {
color: #000;
padding: 5px 10px;
font-size: 12px;
font-family: Consolas;
}
span {
font-size: 14px;
font-family: Consolas;
}
</style>
<body>
<span>Response Time: %s</span><br/>
<table>
<tr>
<th>NO.</th>
<th>USN</th>
<th>PartNO.</th>
<th>MO</th>
<th>SKU</th>
<th>Line</th>
<th>Location</th>
<th>Item</th>
<th>Status</th>
<th>Message</th>
<th>First Ack</th>
<th>Last Ack</th>
<th>Last Change</th>
<th>Differ Time (Min)</th>
</tr>
%s
</table>
</body>
</html>`, now, src)
return resp
}