Commit bbb2f516 by 이병복

테스트 프로그램 추가

parent 1a0d1a7f
server_address: 127.0.0.1:12345
max_retries: 3
reconnect_interval: 5
read_timeout: 60
rest_api_endpoint: http://localhost:8081/api/data
...@@ -5,225 +5,185 @@ import ( ...@@ -5,225 +5,185 @@ import (
"encoding/binary" "encoding/binary"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"log"
"net" "net"
"net/http" "net/http"
"os" "os"
"os/exec"
"time" "time"
"golang.org/x/text/encoding/korean" "golang.org/x/text/encoding/korean"
"golang.org/x/text/transform"
"gopkg.in/yaml.v2"
) )
// Config 구조체 정의 // Config 구조체 정의
type Config struct { type Config struct {
ServerAddress string `json:"serverAddress"` ServerAddress string `yaml:"server_address"`
RetryLimit int `json:"retryLimit"` MaxRetries int `yaml:"max_retries"`
RetryDelay int `json:"retryDelay"` // milliseconds ReconnectInterval int `yaml:"reconnect_interval"`
LogFilePath string `json:"logFilePath"` ReadTimeout int `yaml:"read_timeout"`
MaxLogSize int64 `json:"maxLogSize"` // bytes RestAPIEndpoint string `yaml:"rest_api_endpoint"`
ApiEndpoint string `json:"apiEndpoint"`
} }
// Packet 구조체 정의 // LogEntry 구조체 정의 (이전과 동일)
type Packet struct { type LogEntry struct {
LogSize byte Size byte
LogType byte Type uint8
LogDatetime [6]byte DateTime time.Time
LogOn byte On uint8
LogAddress [7]byte Address [7]byte
LogArea [24]byte Area [24]byte
LogDevice [24]byte Device [24]byte
} }
type ParsedData struct { // JSONLogEntry 구조체 정의 (이전과 동일)
LogSize byte `json:"logSize"` type JSONLogEntry struct {
LogType byte `json:"logType"` Type uint8 `json:"type"`
LogDatetime string `json:"logDatetime"` DateTime time.Time `json:"dateTime"`
LogOn byte `json:"logOn"` On uint8 `json:"on"`
LogAddress string `json:"logAddress"` Address string `json:"address"`
LogArea string `json:"logArea"` Area string `json:"area"`
LogDevice string `json:"logDevice"` Device string `json:"device"`
} }
const readTimeout = 1 * time.Minute // 응답 타임아웃 설정 (1분) var config Config
func main() { func loadConfig() error {
// 설정 파일 읽기 data, err := ioutil.ReadFile("config.yaml")
config, err := loadConfig("config.json")
if err != nil { if err != nil {
log.Fatalf("Failed to load config: %v", err) return err
} }
// 로그 파일 설정 return yaml.Unmarshal(data, &config)
logFile, err := os.OpenFile(config.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) }
if err != nil {
log.Fatalf("Failed to open log file: %v", err) func main() {
if err := loadConfig(); err != nil {
fmt.Printf("Error loading config: %v\n", err)
os.Exit(1)
}
for {
if err := runClient(); err != nil {
fmt.Printf("Error: %v\n", err)
time.Sleep(time.Duration(config.ReconnectInterval) * time.Second)
} }
defer logFile.Close() }
log.SetOutput(io.MultiWriter(os.Stdout, logFile)) }
func runClient() error {
var conn net.Conn var conn net.Conn
var err error
for { for i := 0; i < config.MaxRetries; i++ {
// 재시도 로직
for attempts := 0; attempts < config.RetryLimit; attempts++ {
conn, err = net.Dial("tcp", config.ServerAddress) conn, err = net.Dial("tcp", config.ServerAddress)
if err == nil { if err == nil {
log.Println("Connected to server")
break break
} }
log.Printf("Failed to connect to server (attempt %d/%d): %v", attempts+1, config.RetryLimit, err) fmt.Printf("Connection attempt %d failed: %v\n", i+1, err)
time.Sleep(time.Duration(config.RetryDelay) * time.Millisecond) time.Sleep(time.Duration(config.ReconnectInterval) * time.Second)
} }
if err != nil { if err != nil {
log.Printf("Could not connect to server after %d attempts: %v", config.RetryLimit, err) return fmt.Errorf("failed to connect after %d attempts", config.MaxRetries)
rebootSystem()
return
} }
defer conn.Close()
// 서버와 지속적인 연결 유지 fmt.Println("Connected to server")
for {
conn.SetReadDeadline(time.Now().Add(readTimeout))
packet := &Packet{} for {
err = binary.Read(conn, binary.BigEndian, packet) if err := handleConnection(conn); err != nil {
if err != nil { return err
log.Printf("Read error: %v", err)
conn.Close()
break
} }
// 각 필드 파싱 및 출력
parsedData := ParsedData{
LogSize: packet.LogSize,
LogType: packet.LogType,
LogDatetime: parseDatetime(packet.LogDatetime[:]),
LogOn: packet.LogOn,
LogAddress: string(bytes.Trim(packet.LogAddress[:], "\x00")),
LogArea: decodeEUC_KR(packet.LogArea[:]),
LogDevice: decodeEUC_KR(packet.LogDevice[:]),
} }
}
log.Printf("Parsed Data: %+v\n", parsedData) func handleConnection(conn net.Conn) error {
conn.SetReadDeadline(time.Now().Add(time.Duration(config.ReadTimeout) * time.Second))
// 데이터 전송 buf := make([]byte, 64)
err = sendToApi(config.ApiEndpoint, parsedData) _, err := conn.Read(buf)
if err != nil { if err != nil {
log.Printf("Failed to send data to API: %v", err) return fmt.Errorf("read error: %v", err)
} }
// 로그 파일 크기 체크 및 리셋 logEntry, err := Deserialize(buf)
if err := checkLogSizeAndReset(config.LogFilePath, config.MaxLogSize); err != nil {
log.Fatalf("Failed to check log size: %v", err)
}
}
}
}
// 설정 파일을 읽어오는 함수
func loadConfig(filename string) (*Config, error) {
data, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
return nil, err return fmt.Errorf("deserialization error: %v", err)
} }
var config Config jsonData, err := convertToJSON(logEntry)
err = json.Unmarshal(data, &config)
if err != nil { if err != nil {
return nil, err return fmt.Errorf("JSON conversion error: %v", err)
} }
return &config, nil if err := sendToRESTAPI(jsonData); err != nil {
} return fmt.Errorf("REST API error: %v", err)
// EUC-KR 인코딩을 UTF-8로 디코딩하는 함수
func decodeEUC_KR(data []byte) string {
dec := korean.EUCKR.NewDecoder()
decoded, err := dec.Bytes(data)
if err != nil {
log.Fatalf("Failed to decode EUC-KR: %v", err)
} }
return string(decoded)
}
// 시스템을 재부팅하는 함수 return nil
func rebootSystem() {
log.Println("Rebooting the system...")
cmd := exec.Command("reboot")
err := cmd.Run()
if err != nil {
log.Fatalf("Failed to reboot the system: %v", err)
}
} }
// 로그 파일 크기 체크 및 리셋하는 함수 func Deserialize(data []byte) (*LogEntry, error) {
func checkLogSizeAndReset(logFilePath string, maxSize int64) error { if len(data) != 64 {
fileInfo, err := os.Stat(logFilePath) return nil, fmt.Errorf("invalid data length: expected 64, got %d", len(data))
if err != nil {
return err
} }
if fileInfo.Size() >= maxSize { le := &LogEntry{}
log.Println("Log file size exceeded, resetting log file") le.Size = data[0]
return resetLogFile(logFilePath) le.Type = data[1]
} year := binary.LittleEndian.Uint16(data[2:4])
le.DateTime = time.Date(int(year), time.Month(data[4]), int(data[5]), int(data[6]), int(data[7]), int(data[8]), 0, time.UTC)
le.On = data[9]
copy(le.Address[:], data[10:17])
copy(le.Area[:], data[17:41])
copy(le.Device[:], data[41:])
return nil return le, nil
} }
// 로그 파일을 리셋하는 함수 func convertToJSON(le *LogEntry) ([]byte, error) {
func resetLogFile(logFilePath string) error { area, err := decodeEUCKR(bytes.TrimRight(le.Area[:], "\x00"))
// 기존 파일 닫기
logFile, err := os.OpenFile(logFilePath, os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil { if err != nil {
return err return nil, err
} }
logFile.Close()
// 새로운 로그 파일 열기 device, err := decodeEUCKR(bytes.TrimRight(le.Device[:], "\x00"))
logFile, err = os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
return err return nil, err
} }
log.SetOutput(io.MultiWriter(os.Stdout, logFile)) jsonLE := JSONLogEntry{
return nil Type: le.Type,
DateTime: le.DateTime,
On: le.On,
Address: string(bytes.TrimRight(le.Address[:], "\x00")),
Area: area,
Device: device,
}
return json.Marshal(jsonLE)
} }
// 데이터를 REST API로 전송하는 함수 func decodeEUCKR(b []byte) (string, error) {
func sendToApi(apiEndpoint string, data ParsedData) error { decoder := korean.EUCKR.NewDecoder()
jsonData, err := json.Marshal(data) result, _, err := transform.Bytes(decoder, b)
if err != nil { if err != nil {
return err return "", err
} }
return string(result), nil
}
req, err := http.NewRequest("POST", apiEndpoint, bytes.NewBuffer(jsonData)) func sendToRESTAPI(data []byte) error {
if err != nil { resp, err := http.Post(config.RestAPIEndpoint, "application/json", bytes.NewBuffer(data))
return err
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil { if err != nil {
return err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return fmt.Errorf("API request failed with status: %s", resp.Status) return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
} }
return nil return nil
} }
// 로그 발생 시각을 문자열로 변환하는 함수
func parseDatetime(data []byte) string {
return fmt.Sprintf("%02x-%02x-%02x %02x:%02x:%02x", data[0], data[1], data[2], data[3], data[4], data[5])
}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
http.HandleFunc("/api/data", handleData)
fmt.Println("Mock REST API server listening on :8081")
http.ListenAndServe(":8081", nil)
}
func handleData(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed)
return
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading request body", http.StatusInternalServerError)
return
}
fmt.Printf("Received data: %s\n", string(body))
w.WriteHeader(http.StatusOK)
w.Write([]byte("Data received successfully"))
}
package main
import (
"encoding/binary"
"fmt"
"net"
"time"
"golang.org/x/text/encoding/korean"
)
func main() {
listener, err := net.Listen("tcp", ":12345")
if err != nil {
fmt.Println("Error starting server:", err)
return
}
defer listener.Close()
fmt.Println("Mock server listening on :12345")
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting connection:", err)
continue
}
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
for {
// 테스트 데이터 생성
testData := createTestData()
_, err := conn.Write(testData)
if err != nil {
fmt.Println("Error sending data:", err)
return
}
time.Sleep(10 * time.Second) // 10초마다 데이터 전송
}
}
func createTestData() []byte {
data := make([]byte, 64)
// Log_size
data[0] = 64
// Log_type
data[1] = 1
// Log_datetime (2024-07-01 15:30:00)
binary.LittleEndian.PutUint16(data[2:4], 2024)
data[4] = 7 // month
data[5] = 1 // day
data[6] = 15 // hour
data[7] = 30 // minute
data[8] = 0 // second
// Log_On
data[9] = 1
// Log_Address
copy(data[10:17], []byte("A1B2C3D"))
// Log_Area (EUC-KR 인코딩)
area := "소방서 1"
areaEncoded, _ := korean.EUCKR.NewEncoder().Bytes([]byte(area))
copy(data[17:41], areaEncoded)
// Log_Device (EUC-KR 인코딩)
device := "연기감지기"
deviceEncoded, _ := korean.EUCKR.NewEncoder().Bytes([]byte(device))
copy(data[41:], deviceEncoded)
return data
}
server_address: example.com:8081
max_retries: 10
reconnect_interval: 5
read_timeout: 60
rest_api_endpoint: http://example.com/api/data
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sync"
"gopkg.in/yaml.v2"
)
type Config struct {
ServerAddress string `yaml:"server_address" json:"server_address"`
MaxRetries int `yaml:"max_retries" json:"max_retries"`
ReconnectInterval int `yaml:"reconnect_interval" json:"reconnect_interval"`
ReadTimeout int `yaml:"read_timeout" json:"read_timeout"`
RestAPIEndpoint string `yaml:"rest_api_endpoint" json:"rest_api_endpoint"`
}
var (
config Config
configLock sync.RWMutex
)
func loadConfig() error {
data, err := ioutil.ReadFile("config.yaml")
if err != nil {
return err
}
configLock.Lock()
defer configLock.Unlock()
return yaml.Unmarshal(data, &config)
}
func saveConfig() error {
configLock.RLock()
defer configLock.RUnlock()
data, err := yaml.Marshal(config)
if err != nil {
return err
}
return ioutil.WriteFile("config.yaml", data, 0644)
}
func getConfigHandler(w http.ResponseWriter, r *http.Request) {
configLock.RLock()
defer configLock.RUnlock()
json.NewEncoder(w).Encode(config)
}
func updateConfigHandler(w http.ResponseWriter, r *http.Request) {
var newConfig Config
if err := json.NewDecoder(r.Body).Decode(&newConfig); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
configLock.Lock()
config = newConfig
configLock.Unlock()
if err := saveConfig(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
// 실제 구현에서는 사용자 인증을 처리해야 합니다.
// 여기서는 간단한 예시만 제공합니다.
username := r.FormValue("username")
password := r.FormValue("password")
if username == "admin" && password == "password" {
// 로그인 성공 시 세션 생성 또는 토큰 발급
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "Login successful")
} else {
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
}
}
func main() {
if err := loadConfig(); err != nil {
panic(err)
}
http.HandleFunc("/login", loginHandler)
http.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) {
// 여기에 인증 검사 로직을 추가해야 합니다.
switch r.Method {
case http.MethodGet:
getConfigHandler(w, r)
case http.MethodPost:
updateConfigHandler(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
// 정적 파일 제공을 위한 파일 서버 설정
fs := http.FileServer(http.Dir("static"))
// 루트 경로에 대한 핸들러 추가
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
http.ServeFile(w, r, "static/index.html")
} else {
fs.ServeHTTP(w, r)
}
})
fmt.Println("Server is running on http://localhost:8081")
http.ListenAndServe(":8081", nil)
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Config Manager</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="login-form">
<h2>Login</h2>
<input type="text" id="username" placeholder="Username">
<input type="password" id="password" placeholder="Password">
<button onclick="login()">Login</button>
</div>
<div id="config-form" style="display: none;">
<h2>Configuration</h2>
<form id="config-update-form">
<label for="server_address">Server Address:</label>
<input type="text" id="server_address" name="server_address">
<label for="max_retries">Max Retries:</label>
<input type="number" id="max_retries" name="max_retries">
<label for="reconnect_interval">Reconnect Interval:</label>
<input type="number" id="reconnect_interval" name="reconnect_interval">
<label for="read_timeout">Read Timeout:</label>
<input type="number" id="read_timeout" name="read_timeout">
<label for="rest_api_endpoint">REST API Endpoint:</label>
<input type="text" id="rest_api_endpoint" name="rest_api_endpoint">
<button type="submit">Update Configuration</button>
</form>
</div>
<script src="script.js"></script>
</body>
</html>
\ No newline at end of file
function login() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`,
})
.then(response => {
if (response.ok) {
document.getElementById('login-form').style.display = 'none';
document.getElementById('config-form').style.display = 'block';
loadConfig();
} else {
alert('Login failed. Please try again.');
}
})
.catch(error => console.error('Error:', error));
}
function loadConfig() {
fetch('/config')
.then(response => response.json())
.then(config => {
document.getElementById('server_address').value = config.server_address;
document.getElementById('max_retries').value = config.max_retries;
document.getElementById('reconnect_interval').value = config.reconnect_interval;
document.getElementById('read_timeout').value = config.read_timeout;
document.getElementById('rest_api_endpoint').value = config.rest_api_endpoint;
})
.catch(error => console.error('Error:', error));
}
document.getElementById('config-update-form').addEventListener('submit', function(e) {
e.preventDefault();
const config = {
server_address: document.getElementById('server_address').value,
max_retries: parseInt(document.getElementById('max_retries').value),
reconnect_interval: parseInt(document.getElementById('reconnect_interval').value),
read_timeout: parseInt(document.getElementById('read_timeout').value),
rest_api_endpoint: document.getElementById('rest_api_endpoint').value,
};
fetch('/config', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(config),
})
.then(response => {
if (response.ok) {
alert('Configuration updated successfully');
} else {
alert('Failed to update configuration');
}
})
.catch(error => console.error('Error:', error));
});
\ No newline at end of file
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
input, button {
display: block;
width: 100%;
margin-bottom: 10px;
padding: 5px;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
\ No newline at end of file
{ {
"serverAddress": "localhost:8080", "serverAddress": "localhost:8080",
"panelId": 1,
"order_code": "1",
"retryLimit": 5, "retryLimit": 5,
"retryDelay": 2000, "retryDelay": 2000,
"logFilePath": "app.log", "logFilePath": "app.log",
......
module falinux.com/lte
go 1.17
require (
github.com/gorilla/mux v1.8.1
github.com/gorilla/sessions v1.3.0
golang.org/x/text v0.16.0
gopkg.in/yaml.v2 v2.4.0
)
require github.com/gorilla/securecookie v1.1.2 // indirect
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg=
github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Configuration</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
}
label {
display: block;
margin-bottom: 5px;
}
input, button {
margin-bottom: 10px;
padding: 5px;
width: 300px;
}
button {
cursor: pointer;
}
#status {
margin-top: 10px;
}
</style>
</head>
<body>
<div id="loginPage">
<h1>Login</h1>
<label for="username">Username:</label>
<input type="text" id="username" value="">
<br>
<label for="password">Password:</label>
<input type="password" id="password" value="">
<br>
<button onclick="login()">Login</button>
<div id="loginStatus"></div>
</div>
<div id="editPage" style="display: none;">
<h1>Edit Configuration</h1>
<label for="serverAddress">Server Address:</label>
<input type="text" id="serverAddress" value="">
<br>
<label for="retryLimit">Retry Limit:</label>
<input type="number" id="retryLimit" value="">
<br>
<label for="retryDelay">Retry Delay (ms):</label>
<input type="number" id="retryDelay" value="">
<br>
<label for="logFilePath">Log File Path:</label>
<input type="text" id="logFilePath" value="">
<br>
<label for="maxLogSize">Max Log Size (bytes):</label>
<input type="number" id="maxLogSize" value="">
<br>
<label for="apiEndpoint">API Endpoint:</label>
<input type="text" id="apiEndpoint" value="">
<br>
<button onclick="saveConfig()">Save Configuration</button>
<div id="status"></div>
<br>
<button onclick="logout()">Logout</button>
</div>
<script>
let loggedIn = false;
function showLoginPage() {
document.getElementById('loginPage').style.display = 'block';
document.getElementById('editPage').style.display = 'none';
}
function showEditPage() {
document.getElementById('loginPage').style.display = 'none';
document.getElementById('editPage').style.display = 'block';
}
function login() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
// 실제로는 서버에서 로그인을 처리해야 합니다 (여기서는 단순 예시로 처리)
if (username === 'admin' && password === 'password') {
loggedIn = true;
showEditPage();
fetchConfig();
} else {
document.getElementById('loginStatus').textContent = 'Invalid username or password';
}
}
function logout() {
loggedIn = false;
showLoginPage();
}
// 초기 설정 값을 입력 폼에 설정하는 함수
function populateFields(config) {
document.getElementById('serverAddress').value = config.serverAddress;
document.getElementById('retryLimit').value = config.retryLimit;
document.getElementById('retryDelay').value = config.retryDelay;
document.getElementById('logFilePath').value = config.logFilePath;
document.getElementById('maxLogSize').value = config.maxLogSize;
document.getElementById('apiEndpoint').value = config.apiEndpoint;
}
// 서버에서 현재 설정을 가져오는 함수
function fetchConfig() {
fetch('/config').then(response => response.json())
.then(config => {
populateFields(config);
})
.catch(error => {
console.error('Error fetching configuration:', error);
});
}
// 설정을 서버에 저장하는 함수
function saveConfig() {
var newConfig = {
serverAddress: document.getElementById('serverAddress').value,
retryLimit: parseInt(document.getElementById('retryLimit').value),
retryDelay: parseInt(document.getElementById('retryDelay').value),
logFilePath: document.getElementById('logFilePath').value,
maxLogSize: parseInt(document.getElementById('maxLogSize').value),
apiEndpoint: document.getElementById('apiEndpoint').value
};
fetch('/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newConfig),
})
.then(response => {
if (response.ok) {
document.getElementById('status').textContent = 'Configuration updated successfully';
} else {
document.getElementById('status').textContent = 'Failed to update configuration';
}
})
.catch(error => {
console.error('Error saving configuration:', error);
document.getElementById('status').textContent = 'Error saving configuration';
});
}
// 페이지 로드 시 로그인 페이지 보여주기
document.addEventListener('DOMContentLoaded', () => {
showLoginPage();
});
</script>
</body>
</html>
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
)
// Config 구조체 정의
type Config struct {
ServerAddress string `json:"serverAddress"`
RetryLimit int `json:"retryLimit"`
RetryDelay int `json:"retryDelay"`
LogFilePath string `json:"logFilePath"`
MaxLogSize int64 `json:"maxLogSize"`
ApiEndpoint string `json:"apiEndpoint"`
}
var config Config
// 세션 저장소
var store = sessions.NewCookieStore([]byte("your-secret-key"))
func main() {
// 초기 설정 파일 읽기
configJSON := `
{
"serverAddress": "localhost:8080",
"retryLimit": 5,
"retryDelay": 2000,
"logFilePath": "app.log",
"maxLogSize": 10485760,
"apiEndpoint": "http://example.com/api/logs"
}`
err := json.Unmarshal([]byte(configJSON), &config)
if err != nil {
log.Fatalf("Failed to unmarshal config JSON: %v", err)
}
r := mux.NewRouter()
// 정적 파일 서빙
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("."))))
// 로그인 페이지
r.HandleFunc("/", serveLogin).Methods("GET")
r.HandleFunc("/login", loginHandler).Methods("POST")
// 보호된 페이지
editSubrouter := r.PathPrefix("/edit").Subrouter()
editSubrouter.HandleFunc("/", serveEdit).Methods("GET")
editSubrouter.HandleFunc("/config", getConfig).Methods("GET")
editSubrouter.HandleFunc("/save", saveConfig).Methods("POST")
editSubrouter.HandleFunc("/logout", logoutHandler).Methods("GET")
fmt.Println("Server listening on localhost:8000...")
log.Fatal(http.ListenAndServe(":8000", r))
}
// 로그인 페이지 서빙
func serveLogin(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "index.html")
}
// 로그인 처리
func loginHandler(w http.ResponseWriter, r *http.Request) {
username := r.FormValue("username")
password := r.FormValue("password")
// 실제로는 여기에서 데이터베이스나 다른 인증 방식을 사용하여 검증해야 합니다.
if username == "admin" && password == "password" {
session, _ := store.Get(r, "session-name")
session.Values["authenticated"] = true
session.Save(r, w)
http.Redirect(w, r, "/edit", http.StatusSeeOther)
} else {
http.Error(w, "Invalid username or password", http.StatusUnauthorized)
}
}
// 로그아웃 처리
func logoutHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
session.Values["authenticated"] = false
session.Save(r, w)
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// 보호된 페이지 서빙
func serveEdit(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
val := session.Values["authenticated"]
if val == nil || !val.(bool) {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
http.ServeFile(w, r, "index.html")
}
// 현재 설정을 제공하는 핸들러
func getConfig(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(config)
}
// 설정을 업데이트하는 핸들러
func saveConfig(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
val := session.Values["authenticated"]
if val == nil || !val.(bool) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var newConfig Config
err := json.NewDecoder(r.Body).Decode(&newConfig)
if err != nil {
http.Error(w, "Failed to decode JSON", http.StatusBadRequest)
return
}
config = newConfig
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Configuration updated successfully")
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment