Add logger
This commit is contained in:
parent
6825db9699
commit
e47e99d4e3
|
@ -35,6 +35,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/miekg/dns"
|
||||
|
@ -55,6 +56,8 @@ func declareDomainsRoutes(cfg *config.Options, router *gin.RouterGroup) {
|
|||
apiDomainsRoutes.PUT("", UpdateDomain)
|
||||
apiDomainsRoutes.DELETE("", delDomain)
|
||||
|
||||
apiDomainsRoutes.GET("/logs", GetDomainLogs)
|
||||
|
||||
declareZonesRoutes(cfg, apiDomainsRoutes)
|
||||
}
|
||||
|
||||
|
@ -144,6 +147,8 @@ func addDomain(c *gin.Context) {
|
|||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Sorry, we are unable to create your domain now."})
|
||||
return
|
||||
} else {
|
||||
storage.MainStore.CreateDomainLog(&uz, happydns.NewDomainLog(c.MustGet("LoggedUser").(*happydns.User), happydns.LOG_INFO, fmt.Sprintf("Domain name %s added.", uz.DomainName)))
|
||||
|
||||
c.JSON(http.StatusOK, uz)
|
||||
}
|
||||
}
|
||||
|
@ -314,3 +319,36 @@ func delDomain(c *gin.Context) {
|
|||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetDomainLogs retrieves actions recorded for the domain.
|
||||
//
|
||||
// @Summary Retrieve Domain actions history.
|
||||
// @Schemes
|
||||
// @Description Retrieve information about the actions performed on the domain by users of happyDomain.
|
||||
// @Tags domains
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param domainId path string true "Domain identifier"
|
||||
// @Security securitydefinitions.basic
|
||||
// @Success 200 {object} []happydns.DomainLog
|
||||
// @Failure 401 {object} happydns.Error "Authentication failure"
|
||||
// @Failure 404 {object} happydns.Error "Domain not found"
|
||||
// @Router /domains/{domainId}/logs [get]
|
||||
func GetDomainLogs(c *gin.Context) {
|
||||
domain := c.MustGet("domain").(*happydns.Domain)
|
||||
|
||||
logs, err := storage.MainStore.GetDomainLogs(domain)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("%s: An error occurs in GetDomainLogs, when retrieving logs: %s", c.ClientIP(), err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to access the domain logs. Please try again later."})
|
||||
return
|
||||
}
|
||||
|
||||
// Sort by date
|
||||
sort.Slice(logs, func(i, j int) bool {
|
||||
return logs[i].Date.After(logs[j].Date)
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, logs)
|
||||
}
|
||||
|
|
|
@ -314,6 +314,8 @@ func retrieveZone(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
storage.MainStore.CreateDomainLog(domain, happydns.NewDomainLog(user, happydns.LOG_INFO, fmt.Sprintf("Zone imported from provider API: %s", myZone.Id.String())))
|
||||
|
||||
c.JSON(http.StatusOK, &myZone.ZoneMeta)
|
||||
}
|
||||
|
||||
|
@ -359,6 +361,8 @@ func importZone(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
storage.MainStore.CreateDomainLog(domain, happydns.NewDomainLog(c.MustGet("LoggedUser").(*happydns.User), happydns.LOG_INFO, fmt.Sprintf("Zone imported from Bind-style file: %s", zone.Id.String())))
|
||||
|
||||
c.JSON(http.StatusOK, zone)
|
||||
}
|
||||
|
||||
|
@ -472,6 +476,7 @@ func applyZone(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
nbcorrections := len(form.WantedCorrections)
|
||||
corrections, err := provider.GetDomainCorrections(domain, dc)
|
||||
for _, cr := range corrections {
|
||||
for ic, wc := range form.WantedCorrections {
|
||||
|
@ -480,6 +485,7 @@ func applyZone(c *gin.Context) {
|
|||
err := cr.F()
|
||||
|
||||
if err != nil {
|
||||
storage.MainStore.CreateDomainLog(domain, happydns.NewDomainLog(user, happydns.LOG_ERR, fmt.Sprintf("Failed zone publishing (%s): %s", zone.Id.String(), err.Error())))
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to update the zone: %s", err.Error())})
|
||||
return
|
||||
}
|
||||
|
@ -492,10 +498,13 @@ func applyZone(c *gin.Context) {
|
|||
}
|
||||
|
||||
if len(form.WantedCorrections) > 0 {
|
||||
storage.MainStore.CreateDomainLog(domain, happydns.NewDomainLog(user, happydns.LOG_ERR, fmt.Sprintf("Failed zone publishing (%s): %d corrections were not applied.", zone.Id.String(), nbcorrections)))
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to perform the following changes: %s", form.WantedCorrections)})
|
||||
return
|
||||
}
|
||||
|
||||
storage.MainStore.CreateDomainLog(domain, happydns.NewDomainLog(user, happydns.LOG_ACK, fmt.Sprintf("Zone published (%s), %d corrections applied with success", zone.Id.String(), nbcorrections)))
|
||||
|
||||
// Create a new zone in history for futher updates
|
||||
newZone := zone.DerivateNew()
|
||||
err = storage.MainStore.CreateZone(newZone)
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright or © or Copr. happyDNS (2020)
|
||||
//
|
||||
// contact@happydomain.org
|
||||
//
|
||||
// This software is a computer program whose purpose is to provide a modern
|
||||
// interface to interact with DNS systems.
|
||||
//
|
||||
// This software is governed by the CeCILL license under French law and abiding
|
||||
// by the rules of distribution of free software. You can use, modify and/or
|
||||
// redistribute the software under the terms of the CeCILL license as
|
||||
// circulated by CEA, CNRS and INRIA at the following URL
|
||||
// "http://www.cecill.info".
|
||||
//
|
||||
// As a counterpart to the access to the source code and rights to copy, modify
|
||||
// and redistribute granted by the license, users are provided only with a
|
||||
// limited warranty and the software's author, the holder of the economic
|
||||
// rights, and the successive licensors have only limited liability.
|
||||
//
|
||||
// In this respect, the user's attention is drawn to the risks associated with
|
||||
// loading, using, modifying and/or developing or reproducing the software by
|
||||
// the user in light of its specific status of free software, that may mean
|
||||
// that it is complicated to manipulate, and that also therefore means that it
|
||||
// is reserved for developers and experienced professionals having in-depth
|
||||
// computer knowledge. Users are therefore encouraged to load and test the
|
||||
// software's suitability as regards their requirements in conditions enabling
|
||||
// the security of their systems and/or data to be ensured and, more generally,
|
||||
// to use and operate it in the same conditions as regards security.
|
||||
//
|
||||
// The fact that you are presently reading this means that you have had
|
||||
// knowledge of the CeCILL license and that you accept its terms.
|
||||
|
||||
package happydns
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
LOG_CRIT = iota
|
||||
LOG_FATAL
|
||||
LOG_ERR2
|
||||
LOG_ERR
|
||||
LOG_STRONG_WARN
|
||||
LOG_WARN
|
||||
LOG_WEIRD
|
||||
LOG_NACK
|
||||
LOG_INFO
|
||||
LOG_ACK
|
||||
LOG_DEBUG
|
||||
)
|
||||
|
||||
type DomainLog struct {
|
||||
// Id is the Log's identifier in the database.
|
||||
Id Identifier `json:"id" swaggertype:"string"`
|
||||
|
||||
// IdUser is the identifier of the person responsible for the action.
|
||||
IdUser Identifier `json:"id_user" swaggertype:"string"`
|
||||
|
||||
// Date is the date of the action.
|
||||
Date time.Time `json:"date"`
|
||||
|
||||
// Content is the description of the action logged.
|
||||
Content string `json:"content"`
|
||||
|
||||
// Level reports the criticity level of the action logged.
|
||||
Level int8 `json:"level"`
|
||||
}
|
||||
|
||||
func NewDomainLog(u *User, level int8, content string) *DomainLog {
|
||||
return &DomainLog{
|
||||
IdUser: u.Id,
|
||||
Date: time.Now(),
|
||||
Content: content,
|
||||
Level: level,
|
||||
}
|
||||
}
|
|
@ -100,6 +100,16 @@ type Storage interface {
|
|||
// ClearDomains deletes all Domains present in the database.
|
||||
ClearDomains() error
|
||||
|
||||
// DOMAIN LOGS --------------------------------------------------
|
||||
|
||||
GetDomainLogs(*happydns.Domain) ([]*happydns.DomainLog, error)
|
||||
|
||||
CreateDomainLog(*happydns.Domain, *happydns.DomainLog) error
|
||||
|
||||
UpdateDomainLog(*happydns.Domain, *happydns.DomainLog) error
|
||||
|
||||
DeleteDomainLog(*happydns.Domain, *happydns.DomainLog) error
|
||||
|
||||
// PROVIDERS ----------------------------------------------------
|
||||
|
||||
// GetProviderMetas retrieves provider's metadatas of all providers own by the given User.
|
||||
|
|
|
@ -71,7 +71,7 @@ func NewLevelDBStorage(path string) (s *LevelDBStorage, err error) {
|
|||
}
|
||||
|
||||
func (s *LevelDBStorage) Tidy() error {
|
||||
for _, tidy := range []func() error{s.TidySessions, s.TidyAuthUsers, s.TidyUsers, s.TidyProviders, s.TidyDomains, s.TidyZones} {
|
||||
for _, tidy := range []func() error{s.TidySessions, s.TidyAuthUsers, s.TidyUsers, s.TidyProviders, s.TidyDomains, s.TidyZones, s.TidyDomainLogs} {
|
||||
if err := tidy(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
// Copyright or © or Copr. happyDNS (2023)
|
||||
//
|
||||
// contact@happydomain.org
|
||||
//
|
||||
// This software is a computer program whose purpose is to provide a modern
|
||||
// interface to interact with DNS systems.
|
||||
//
|
||||
// This software is governed by the CeCILL license under French law and abiding
|
||||
// by the rules of distribution of free software. You can use, modify and/or
|
||||
// redistribute the software under the terms of the CeCILL license as
|
||||
// circulated by CEA, CNRS and INRIA at the following URL
|
||||
// "http://www.cecill.info".
|
||||
//
|
||||
// As a counterpart to the access to the source code and rights to copy, modify
|
||||
// and redistribute granted by the license, users are provided only with a
|
||||
// limited warranty and the software's author, the holder of the economic
|
||||
// rights, and the successive licensors have only limited liability.
|
||||
//
|
||||
// In this respect, the user's attention is drawn to the risks associated with
|
||||
// loading, using, modifying and/or developing or reproducing the software by
|
||||
// the user in light of its specific status of free software, that may mean
|
||||
// that it is complicated to manipulate, and that also therefore means that it
|
||||
// is reserved for developers and experienced professionals having in-depth
|
||||
// computer knowledge. Users are therefore encouraged to load and test the
|
||||
// software's suitability as regards their requirements in conditions enabling
|
||||
// the security of their systems and/or data to be ensured and, more generally,
|
||||
// to use and operate it in the same conditions as regards security.
|
||||
//
|
||||
// The fact that you are presently reading this means that you have had
|
||||
// knowledge of the CeCILL license and that you accept its terms.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"git.happydns.org/happyDomain/model"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
func (s *LevelDBStorage) GetDomainLogs(domain *happydns.Domain) (logs []*happydns.DomainLog, err error) {
|
||||
iter := s.search(fmt.Sprintf("domain.log|%s|", domain.Id.String()))
|
||||
defer iter.Release()
|
||||
|
||||
for iter.Next() {
|
||||
var z happydns.DomainLog
|
||||
|
||||
err = decodeData(iter.Value(), &z)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
logs = append(logs, &z)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *LevelDBStorage) getDomainLog(id string) (l *happydns.DomainLog, d *happydns.Domain, err error) {
|
||||
l = &happydns.DomainLog{}
|
||||
err = s.get(id, l)
|
||||
|
||||
st := strings.Split(id, "|")
|
||||
if len(st) < 3 {
|
||||
return
|
||||
}
|
||||
|
||||
d = &happydns.Domain{}
|
||||
err = s.get(id, fmt.Sprintf("domain-%s", st[1]))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *LevelDBStorage) CreateDomainLog(d *happydns.Domain, l *happydns.DomainLog) error {
|
||||
key, id, err := s.findIdentifierKey(fmt.Sprintf("domain.log|%s|", d.Id.String()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.Id = id
|
||||
return s.put(key, l)
|
||||
}
|
||||
|
||||
func (s *LevelDBStorage) UpdateDomainLog(d *happydns.Domain, l *happydns.DomainLog) error {
|
||||
return s.put(fmt.Sprintf("domain.log|%s|%s", d.Id.String(), l.Id.String()), l)
|
||||
}
|
||||
|
||||
func (s *LevelDBStorage) DeleteDomainLog(d *happydns.Domain, l *happydns.DomainLog) error {
|
||||
return s.delete(fmt.Sprintf("domain.log|%s|%s", d.Id.String(), l.Id.String()))
|
||||
}
|
||||
|
||||
func (s *LevelDBStorage) TidyDomainLogs() error {
|
||||
tx, err := s.db.OpenTransaction()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
iter := tx.NewIterator(util.BytesPrefix([]byte("domain.log|")), nil)
|
||||
defer iter.Release()
|
||||
|
||||
for iter.Next() {
|
||||
l, _, err := s.getDomainLog(string(iter.Key()))
|
||||
|
||||
if err != nil {
|
||||
if l != nil {
|
||||
log.Printf("Deleting log without valid domain: %s (%s)\n", l.Id.String(), err.Error())
|
||||
} else {
|
||||
log.Printf("Deleting unreadable log (%s): %v\n", err.Error(), l)
|
||||
}
|
||||
err = tx.Delete(iter.Key(), nil)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
tx.Discard()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
tx.Discard()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { handleEmptyApiResponse, handleApiResponse } from '$lib/errors';
|
||||
import type { Domain, DomainInList } from '$lib/model/domain';
|
||||
import type { Domain, DomainInList, DomainLog } from '$lib/model/domain';
|
||||
import type { Provider } from '$lib/model/provider';
|
||||
|
||||
export async function listDomains(): Promise<Array<DomainInList>> {
|
||||
|
@ -43,3 +43,9 @@ export async function deleteDomain(id: string): Promise<boolean> {
|
|||
});
|
||||
return await handleEmptyApiResponse(res);
|
||||
}
|
||||
|
||||
export async function getDomainLogs(id: string): Promise<Array<DomainLog>> {
|
||||
id = encodeURIComponent(id);
|
||||
const res = await fetch(`/api/domains/${id}/logs`, {headers: {'Accept': 'application/json'}});
|
||||
return await handleApiResponse<Array<DomainLog>>(res);
|
||||
}
|
||||
|
|
|
@ -29,3 +29,11 @@ export interface Domain {
|
|||
// interface property
|
||||
wait: boolean;
|
||||
};
|
||||
|
||||
export interface DomainLog {
|
||||
id: string;
|
||||
id_user: string;
|
||||
date: Date;
|
||||
content: string;
|
||||
level: number;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
Button,
|
||||
Icon,
|
||||
Table,
|
||||
Spinner,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import { getDomainLogs } from '$lib/api/domains';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
export let data: {domain: DomainInList; history: string; streamed: Object;};
|
||||
</script>
|
||||
|
||||
<div class="flex-fill pb-4 pt-2">
|
||||
<h2>Journal du domaine <span class="font-monospace">{data.domain.domain}</span></h2>
|
||||
{#await getDomainLogs(data.domain.id)}
|
||||
<div class="mt-5 text-center flex-fill">
|
||||
<Spinner label="Spinning" />
|
||||
<p>{$t('wait.loading')}</p>
|
||||
</div>
|
||||
{:then logs}
|
||||
<Table hover stripped>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Utilisateur</th>
|
||||
<th>Action/description</th>
|
||||
<th>Date</th>
|
||||
<th>Niveau</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if logs}
|
||||
{#each logs as log}
|
||||
<tr>
|
||||
<td>{log.id_user}</td>
|
||||
<td>{log.content}</td>
|
||||
<td>
|
||||
{new Intl.DateTimeFormat(undefined, {dateStyle: "short", timeStyle: "short"}).format(log.date)}
|
||||
</td>
|
||||
<td>{log.level}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{:else}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">
|
||||
Aucune entrée dans le journal du domaine.
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</Table>
|
||||
{/await}
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
import type { Load } from '@sveltejs/kit';
|
||||
|
||||
export const load: Load = async({ parent }) => {
|
||||
const data = await parent();
|
||||
|
||||
return {
|
||||
...data,
|
||||
isAuditPage: true,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue