1442 lines
39 KiB
Go
1442 lines
39 KiB
Go
package web
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"html/template"
|
|
"io/ioutil"
|
|
"log"
|
|
"math"
|
|
"net/http"
|
|
"net/url"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/andybalholm/cascadia"
|
|
"github.com/antchfx/htmlquery"
|
|
"github.com/robfig/cron/v3"
|
|
"github.com/spf13/viper"
|
|
"github.com/tidwall/gjson"
|
|
lualibs "github.com/vadv/gopher-lua-libs"
|
|
lua "github.com/yuin/gopher-lua"
|
|
"golang.org/x/net/html"
|
|
"gorm.io/gorm"
|
|
|
|
. "github.com/broodjeaap/go-watch/models"
|
|
)
|
|
|
|
// ProcessFilters takes the filters of a watch, already 'connected' with buildFilterTree(), and processes the filters in the right order.
|
|
func ProcessFilters(filters []Filter, web *Web, watch *Watch, debug bool, scheduleID *FilterID) {
|
|
// set of watch.IDs that have been processed
|
|
// filters will only be processed if all their parents are processed
|
|
processedMap := make(map[FilterID]bool, len(filters))
|
|
|
|
// if this function is called from a schedule, add the schedule filter to the processed set
|
|
if scheduleID != nil {
|
|
processedMap[*scheduleID] = true
|
|
}
|
|
|
|
// print out the name of the watch and schedule filter that called this function
|
|
for _, filter := range filters {
|
|
if scheduleID != nil && filter.ID == *scheduleID {
|
|
log.Printf("Scheduled Watch for '%s', triggered by schedule '%s'", watch.Name, filter.Name)
|
|
}
|
|
}
|
|
|
|
// check if there are multiple 'cron' filters connected to a single filter
|
|
// this filter can't be run, only one filter is ever triggered,
|
|
// the other prevents it from running, allParentsProcessed is always false
|
|
// just warn the user when this happens and return
|
|
for i := range filters {
|
|
filter := &filters[i]
|
|
cronParentCount := 0
|
|
for _, parent := range filter.Parents {
|
|
if parent.Type == "cron" {
|
|
cronParentCount++
|
|
}
|
|
}
|
|
if cronParentCount > 1 {
|
|
filter.Log("Multiple schedules on the same filter is not supported!")
|
|
return
|
|
}
|
|
}
|
|
|
|
// create a queue with all filters
|
|
currentFilters := make([]*Filter, 0, len(filters))
|
|
for i := range filters {
|
|
filter := &filters[i]
|
|
currentFilters = append(currentFilters, filter)
|
|
}
|
|
|
|
// collect 'store' and 'notify' filters so we can process them separately at the end
|
|
storeFilters := make([]*Filter, 0, 5)
|
|
notifyFilters := make([]*Filter, 0, 5)
|
|
|
|
// loop through current filters until it's empty
|
|
for {
|
|
// empty queue that will replace 'currentFilters' every outer iteration
|
|
nextFilters := make([]*Filter, 0, len(currentFilters))
|
|
for i := range currentFilters {
|
|
filter := currentFilters[i]
|
|
|
|
// handle store filters at the end, so everything has a value
|
|
if filter.Type == "store" {
|
|
storeFilters = append(storeFilters, filter)
|
|
processedMap[filter.ID] = true
|
|
continue
|
|
}
|
|
|
|
// handle notify filters at the end, so everything has a value
|
|
if debug && filter.Type == "notify" {
|
|
notifyFilters = append(notifyFilters, filter)
|
|
}
|
|
|
|
// for schedule filters during editing, just check the schedule string and set to processed
|
|
if debug && filter.Type == "cron" {
|
|
if filter.Var2 == "no" {
|
|
filter.Log("Schedule is disabled")
|
|
}
|
|
processedMap[filter.ID] = true
|
|
getCronDebugResult(filter)
|
|
continue
|
|
}
|
|
|
|
// for testing, allows bootstrapping test cases
|
|
if filter.Type == "echo" {
|
|
getFilterResultEcho(filter)
|
|
processedMap[filter.ID] = true
|
|
continue
|
|
}
|
|
|
|
// no parents nothing to do, unless editing
|
|
if len(filter.Parents) == 0 && !debug {
|
|
continue
|
|
}
|
|
|
|
// loop through parent filters to see if they're all processed
|
|
var allParentsProcessed = true
|
|
for _, parent := range filter.Parents {
|
|
if _, contains := processedMap[parent.ID]; !contains {
|
|
allParentsProcessed = false
|
|
break
|
|
}
|
|
}
|
|
|
|
// if not all parents are processed, add to queue for next outer iteration
|
|
if !allParentsProcessed {
|
|
nextFilters = append(nextFilters, filter)
|
|
continue
|
|
}
|
|
|
|
// get the result of the filter
|
|
getFilterResult(filters, filter, watch, web, debug)
|
|
processedMap[filter.ID] = true
|
|
}
|
|
if len(nextFilters) == 0 {
|
|
break
|
|
}
|
|
currentFilters = nextFilters
|
|
}
|
|
|
|
// process the store filters last
|
|
for _, storeFilter := range storeFilters {
|
|
getFilterResult(filters, storeFilter, watch, web, debug)
|
|
}
|
|
|
|
// process the notify filters when editing, so it still logs and can put the message in results
|
|
if debug {
|
|
for _, nFilter := range notifyFilters {
|
|
if len(nFilter.Results) == 0 {
|
|
notifyFilter(filters, nFilter, watch, web, debug)
|
|
log.Println(nFilter.Results)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResult is just a big switch case handling passing the filter to the right filter function
|
|
func getFilterResult(filters []Filter, filter *Filter, watch *Watch, web *Web, debug bool) {
|
|
switch {
|
|
case filter.Type == "gurl":
|
|
{
|
|
getFilterResultURL(filter, web.urlCache, debug)
|
|
}
|
|
case filter.Type == "gurls":
|
|
{
|
|
getFilterResultURLs(filter, web.urlCache, debug)
|
|
}
|
|
case filter.Type == "xpath":
|
|
{
|
|
getFilterResultXPath(filter)
|
|
}
|
|
case filter.Type == "json":
|
|
{
|
|
getFilterResultJSON(filter)
|
|
}
|
|
case filter.Type == "css":
|
|
{
|
|
getFilterResultCSS(filter)
|
|
}
|
|
case filter.Type == "replace":
|
|
{
|
|
getFilterResultReplace(filter)
|
|
}
|
|
case filter.Type == "match":
|
|
{
|
|
getFilterResultMatch(filter)
|
|
}
|
|
case filter.Type == "substring":
|
|
{
|
|
getFilterResultSubstring(filter)
|
|
}
|
|
case filter.Type == "subset":
|
|
{
|
|
getFilterResultSubset(filter)
|
|
}
|
|
case filter.Type == "contains":
|
|
{
|
|
getFilterResultContains(filter)
|
|
}
|
|
case filter.Type == "unique":
|
|
{
|
|
getFilterResultUnique(filter)
|
|
}
|
|
case filter.Type == "math":
|
|
{
|
|
switch {
|
|
case filter.Var1 == "sum":
|
|
{
|
|
getFilterResultSum(filter)
|
|
}
|
|
case filter.Var1 == "min":
|
|
{
|
|
getFilterResultMin(filter)
|
|
}
|
|
case filter.Var1 == "max":
|
|
{
|
|
getFilterResultMax(filter)
|
|
}
|
|
case filter.Var1 == "average":
|
|
{
|
|
getFilterResultAverage(filter)
|
|
}
|
|
case filter.Var1 == "count":
|
|
{
|
|
getFilterResultCount(filter)
|
|
}
|
|
case filter.Var1 == "round":
|
|
{
|
|
getFilterResultRound(filter)
|
|
}
|
|
}
|
|
}
|
|
case filter.Type == "store":
|
|
{
|
|
storeFilterResult(filter, web.db, debug)
|
|
}
|
|
case filter.Type == "expect":
|
|
{
|
|
getFilterResultExpect(filter, web, debug)
|
|
}
|
|
case filter.Type == "disable":
|
|
{
|
|
getFilterResultDisableSchedules(filter, web, debug)
|
|
}
|
|
case filter.Type == "notify":
|
|
{
|
|
notifyFilter(filters, filter, watch, web, debug)
|
|
}
|
|
case filter.Type == "lua":
|
|
{
|
|
getFilterResultLua(filter)
|
|
}
|
|
case filter.Type == "cron":
|
|
{
|
|
|
|
}
|
|
case filter.Type == "condition":
|
|
{
|
|
switch filter.Var1 {
|
|
case "diff":
|
|
{
|
|
getFilterResultConditionDiff(filter, web.db)
|
|
}
|
|
case "lowerl":
|
|
{
|
|
getFilterResultConditionLowerLast(filter, web.db)
|
|
}
|
|
case "lowest":
|
|
{
|
|
getFilterResultConditionLowest(filter, web.db)
|
|
}
|
|
case "lowert":
|
|
{
|
|
getFilterResultConditionLowerThan(filter)
|
|
}
|
|
case "higherl":
|
|
{
|
|
getFilterResultConditionHigherLast(filter, web.db)
|
|
}
|
|
case "highest":
|
|
{
|
|
getFilterResultConditionHighest(filter, web.db)
|
|
}
|
|
case "highert":
|
|
{
|
|
getFilterResultConditionHigherThan(filter)
|
|
}
|
|
}
|
|
}
|
|
case filter.Type == "brow":
|
|
{
|
|
switch filter.Var1 {
|
|
case "gurl":
|
|
{
|
|
getFilterResultBrowserlessURL(filter, web.urlCache, debug)
|
|
}
|
|
case "gurls":
|
|
{
|
|
getFilterResultBrowserlessURLs(filter, web.urlCache, debug)
|
|
}
|
|
case "func":
|
|
{
|
|
getBrowserlessFunctionResult(filter)
|
|
}
|
|
case "funcs":
|
|
{
|
|
getBrowserlessFunctionResults(filter)
|
|
}
|
|
}
|
|
}
|
|
case filter.Type == "echo":
|
|
{
|
|
getFilterResultEcho(filter)
|
|
}
|
|
default:
|
|
filter.Log("getFilterResult called with filter.Type == '", filter.Type, "'")
|
|
}
|
|
}
|
|
|
|
// getFilterResultURL Fetches the given URL and outputs the HTTP response.
|
|
func getFilterResultURL(filter *Filter, urlCache map[string]string, debug bool) {
|
|
fetchURL := filter.Var1
|
|
val, exists := urlCache[fetchURL]
|
|
if debug && exists {
|
|
filter.Results = append(filter.Results, val)
|
|
return
|
|
}
|
|
str, err := getURLContent(filter, fetchURL)
|
|
if err != nil {
|
|
log.Println("Could not fetch url: ", fetchURL, " - ", err)
|
|
filter.Log("Could not fetch url: ", fetchURL, " - ", err)
|
|
return
|
|
}
|
|
filter.Results = append(filter.Results, str)
|
|
if debug {
|
|
urlCache[fetchURL] = str
|
|
}
|
|
}
|
|
|
|
// getFilterResultURLs Fetches every URL given by its parents and outputs every HTTP response.
|
|
func getFilterResultURLs(filter *Filter, urlCache map[string]string, debug bool) {
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
fetchURL := result
|
|
val, exists := urlCache[fetchURL]
|
|
if debug && exists {
|
|
filter.Results = append(filter.Results, val)
|
|
continue
|
|
}
|
|
|
|
str, err := getURLContent(filter, fetchURL)
|
|
if err != nil {
|
|
log.Println("Could not fetch url: ", fetchURL, " - ", err)
|
|
filter.Log("Could not fetch url: ", fetchURL, " - ", err)
|
|
continue
|
|
}
|
|
filter.Results = append(filter.Results, str)
|
|
if debug {
|
|
urlCache[fetchURL] = str
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getURLContent actually handles the HTTP GET for getFilterResultURL and getFilterResultURLs
|
|
func getURLContent(filter *Filter, fetchURL string) (string, error) {
|
|
var httpClient *http.Client
|
|
if viper.IsSet("proxy.url") {
|
|
proxyUrl, err := url.Parse(viper.GetString("proxy.url"))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
httpClient = &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyUrl)}}
|
|
} else {
|
|
httpClient = &http.Client{}
|
|
}
|
|
resp, err := httpClient.Get(fetchURL)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(body), nil
|
|
}
|
|
|
|
// getFilterResultBrowserlessURL Fetches the given URL and outputs the HTTP response through Browserless
|
|
func getFilterResultBrowserlessURL(filter *Filter, urlCache map[string]string, debug bool) {
|
|
fetchURL := filter.Var2
|
|
val, exists := urlCache["b"+fetchURL]
|
|
if debug && exists {
|
|
filter.Results = append(filter.Results, val)
|
|
return
|
|
}
|
|
str, err := getBrowserlessURLContent(filter, fetchURL)
|
|
if err != nil {
|
|
log.Println("Could not fetch url: ", fetchURL, " - ", err)
|
|
filter.Log("Could not fetch url: ", fetchURL, " - ", err)
|
|
return
|
|
}
|
|
filter.Results = append(filter.Results, str)
|
|
if debug {
|
|
urlCache["b"+fetchURL] = str
|
|
}
|
|
}
|
|
|
|
// getFilterResultBrowserlessURLs Fetches every URL given by its parents and outputs every HTTP response through Browserless.
|
|
func getFilterResultBrowserlessURLs(filter *Filter, urlCache map[string]string, debug bool) {
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
fetchURL := result
|
|
val, exists := urlCache["b"+fetchURL]
|
|
if debug && exists {
|
|
filter.Results = append(filter.Results, val)
|
|
continue
|
|
}
|
|
|
|
str, err := getBrowserlessURLContent(filter, fetchURL)
|
|
if err != nil {
|
|
log.Println("Could not fetch url: ", fetchURL, " - ", err)
|
|
filter.Log("Could not fetch url: ", fetchURL, " - ", err)
|
|
continue
|
|
}
|
|
filter.Results = append(filter.Results, str)
|
|
if debug {
|
|
urlCache["b"+fetchURL] = str
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getBrowserlessURLContent actually handles the request for getFilterResultBrowserlessURL and getFilterResultBrowserlessURLs
|
|
func getBrowserlessURLContent(filter *Filter, fetchURL string) (string, error) {
|
|
if !viper.IsSet("browserless.url") {
|
|
return "", errors.New("browserless.url not set")
|
|
}
|
|
browserlessURL := viper.GetString("browserless.url")
|
|
data := struct {
|
|
URL string `json:"url"`
|
|
}{
|
|
URL: fetchURL,
|
|
}
|
|
jsn, err := json.Marshal(data)
|
|
if err != nil {
|
|
log.Println("Could not marshal url:", err)
|
|
filter.Log("Could not marshal url:", err)
|
|
return "", err
|
|
}
|
|
browserlessURL = browserlessURL + "/content"
|
|
resp, err := http.Post(browserlessURL, "application/json", bytes.NewBuffer(jsn))
|
|
if err != nil {
|
|
log.Println("Could not get browserless response content:", err)
|
|
filter.Log("Could not get browserless response content:", err)
|
|
return "", err
|
|
}
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Println("Could not fetch url through browserless: ", fetchURL, " - ", err)
|
|
filter.Log("Could not fetch url through browserless: ", fetchURL, " - ", err)
|
|
return "", err
|
|
}
|
|
return string(body), nil
|
|
}
|
|
|
|
// getBrowserlessFunctionResult Executes the Puppeteer function through Browserless
|
|
func getBrowserlessFunctionResult(filter *Filter) {
|
|
result, err := getBrowserlessFunctionContent(filter, "")
|
|
if err != nil {
|
|
log.Println(err)
|
|
filter.Log(err)
|
|
return
|
|
}
|
|
filter.Results = append(filter.Results, result)
|
|
}
|
|
|
|
// getBrowserlessFunctionResult Executes the Puppeteer function through Browserless for every parents result
|
|
func getBrowserlessFunctionResults(filter *Filter) {
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
fetchURL := result
|
|
|
|
str, err := getBrowserlessFunctionContent(filter, fetchURL)
|
|
if err != nil {
|
|
log.Println(err)
|
|
filter.Log(err)
|
|
continue
|
|
}
|
|
filter.Results = append(filter.Results, str)
|
|
}
|
|
}
|
|
}
|
|
|
|
type BrowserlessContext struct {
|
|
Result string `json:"result"`
|
|
}
|
|
|
|
// getBrowserlessFunctionContent actually handles Browserless request for getBrowserlessFunctionResult and getBrowserlessFunctionResults
|
|
func getBrowserlessFunctionContent(filter *Filter, result string) (string, error) {
|
|
if !viper.IsSet("browserless.url") {
|
|
|
|
return "", errors.New("browserless.url not set")
|
|
}
|
|
browserlessURL := viper.GetString("browserless.url")
|
|
code := filter.Var2
|
|
data := struct {
|
|
Code string `json:"code"`
|
|
Context BrowserlessContext `json:"context"`
|
|
Detached bool `json:"detached"`
|
|
}{
|
|
Code: code,
|
|
Context: BrowserlessContext{Result: result},
|
|
Detached: false,
|
|
}
|
|
jsn, err := json.Marshal(data)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
browserlessURL = browserlessURL + "/function"
|
|
resp, err := http.Post(browserlessURL, "application/json", bytes.NewBuffer(jsn))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(body), nil
|
|
}
|
|
|
|
// getFilterResultXPath Filters the results of its parents based on an XPath query
|
|
func getFilterResultXPath(filter *Filter) {
|
|
selectType := filter.Var2
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
doc, err := htmlquery.Parse(strings.NewReader(result))
|
|
if err != nil {
|
|
filter.Log(err)
|
|
continue
|
|
}
|
|
nodes, _ := htmlquery.QueryAll(doc, filter.Var1)
|
|
for _, node := range nodes {
|
|
switch selectType {
|
|
case "inner":
|
|
{
|
|
if node.FirstChild == nil {
|
|
continue
|
|
}
|
|
|
|
var result bytes.Buffer
|
|
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
|
var b bytes.Buffer
|
|
html.Render(&b, child)
|
|
result.WriteString(b.String())
|
|
}
|
|
filter.Results = append(filter.Results, html.UnescapeString(result.String()))
|
|
break
|
|
}
|
|
case "attr":
|
|
{
|
|
for _, attr := range node.Attr {
|
|
result := fmt.Sprintf("%s=\"%s\"", attr.Key, attr.Val)
|
|
filter.Results = append(filter.Results, html.UnescapeString(result))
|
|
}
|
|
break
|
|
}
|
|
default:
|
|
{
|
|
var b bytes.Buffer
|
|
html.Render(&b, node)
|
|
filter.Results = append(filter.Results, html.UnescapeString(b.String()))
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultJSON Filters the results of its parents based on an JSON query
|
|
func getFilterResultJSON(filter *Filter) {
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
for _, match := range gjson.Get(result, filter.Var1).Array() {
|
|
filter.Results = append(filter.Results, match.String())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultCSS Filters the results of its parents based on an CSS query
|
|
func getFilterResultCSS(filter *Filter) {
|
|
selectType := filter.Var2
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
doc, err := html.Parse(strings.NewReader(result))
|
|
if err != nil {
|
|
filter.Log(err)
|
|
continue
|
|
}
|
|
sel, err := cascadia.Parse(filter.Var1)
|
|
if err != nil {
|
|
filter.Log(err)
|
|
continue
|
|
}
|
|
for _, node := range cascadia.QueryAll(doc, sel) {
|
|
switch selectType {
|
|
case "inner":
|
|
{
|
|
if node.FirstChild == nil {
|
|
continue
|
|
}
|
|
|
|
var result bytes.Buffer
|
|
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
|
var b bytes.Buffer
|
|
html.Render(&b, child)
|
|
result.WriteString(b.String())
|
|
}
|
|
filter.Results = append(filter.Results, html.UnescapeString(result.String()))
|
|
break
|
|
}
|
|
case "attr":
|
|
{
|
|
for _, attr := range node.Attr {
|
|
result := fmt.Sprintf("%s=\"%s\"", attr.Key, attr.Val)
|
|
filter.Results = append(filter.Results, html.UnescapeString(result))
|
|
}
|
|
break
|
|
}
|
|
default:
|
|
{
|
|
var b bytes.Buffer
|
|
html.Render(&b, node)
|
|
filter.Results = append(filter.Results, html.UnescapeString(b.String()))
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultReplace performs a regex replace on all the results of its parents
|
|
func getFilterResultReplace(filter *Filter) {
|
|
r, err := regexp.Compile(filter.Var1)
|
|
if err != nil {
|
|
filter.Log(err)
|
|
return
|
|
}
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
filter.Results = append(filter.Results, r.ReplaceAllString(result, filter.Var2))
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultReplace performs a regex match on all the results of its parents
|
|
func getFilterResultMatch(filter *Filter) {
|
|
r, err := regexp.Compile(filter.Var1)
|
|
if err != nil {
|
|
filter.Log("Could not compile regex: ", err)
|
|
return
|
|
}
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
filter.Results = append(filter.Results, r.FindAllString(result, -1)...)
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultSubstring performs a substring selection on all the results of its parents
|
|
func getFilterResultSubstring(filter *Filter) {
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
substrings := strings.Split(filter.Var1, ",")
|
|
var sb strings.Builder
|
|
asRunes := []rune(result)
|
|
|
|
for _, substring := range substrings {
|
|
if strings.Contains(substring, ":") {
|
|
from_to := strings.Split(substring, ":")
|
|
if len(from_to) != 2 {
|
|
filter.Log("Missing value in range: '", substring, "'")
|
|
return
|
|
}
|
|
fromStr := from_to[0]
|
|
var hasFrom bool = true
|
|
if fromStr == "" {
|
|
hasFrom = false
|
|
}
|
|
from64, err := strconv.ParseInt(fromStr, 10, 32)
|
|
var from = int(from64)
|
|
if hasFrom && err != nil {
|
|
filter.Log("Could not parse left side of: '", substring, "'")
|
|
return
|
|
}
|
|
if from < 0 {
|
|
from = len(asRunes) + from
|
|
}
|
|
if from < 0 || from > len(asRunes) {
|
|
filter.Log("Out of bounds:", from_to)
|
|
continue
|
|
}
|
|
|
|
toStr := from_to[1]
|
|
var hasTo bool = true
|
|
if toStr == "" {
|
|
hasTo = false
|
|
}
|
|
to64, err := strconv.ParseInt(toStr, 10, 32)
|
|
var to = int(to64)
|
|
if hasTo && err != nil {
|
|
filter.Log("Could not parse right side of: '", substring, "'")
|
|
return
|
|
}
|
|
if to < 0 {
|
|
to = len(asRunes) + to
|
|
}
|
|
if to < 0 || to > len(asRunes) {
|
|
filter.Log("Out of bounds:", from_to)
|
|
continue
|
|
}
|
|
|
|
if hasFrom && hasTo {
|
|
_, err := sb.WriteString(string(asRunes[from:to]))
|
|
if err != nil {
|
|
filter.Log("Could not substring: ", err)
|
|
}
|
|
} else if hasFrom {
|
|
sb.WriteString(string(asRunes[from:]))
|
|
} else if hasTo {
|
|
sb.WriteString(string(asRunes[:to]))
|
|
}
|
|
} else {
|
|
pos, err := strconv.ParseInt(substring, 10, 32)
|
|
if err != nil {
|
|
filter.Log("Could not parse: '", substring, "'")
|
|
return
|
|
}
|
|
if pos < 0 {
|
|
pos = int64(len(asRunes)) + pos
|
|
}
|
|
if pos < 0 || pos >= int64(len(asRunes)) {
|
|
filter.Log("Out of bounds:", pos)
|
|
continue
|
|
}
|
|
sb.WriteRune(asRunes[pos])
|
|
}
|
|
}
|
|
filter.Results = append(filter.Results, sb.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultSubset performs a subset selection on all the results of its parents
|
|
func getFilterResultSubset(filter *Filter) {
|
|
numResults := 0
|
|
for _, parent := range filter.Parents {
|
|
numResults += len(parent.Results)
|
|
}
|
|
|
|
results := make([]string, 0, numResults)
|
|
|
|
for _, parent := range filter.Parents {
|
|
results = append(results, parent.Results...)
|
|
}
|
|
|
|
substrings := strings.Split(filter.Var1, ",")
|
|
for _, substring := range substrings {
|
|
if strings.Contains(substring, ":") {
|
|
from_to := strings.Split(substring, ":")
|
|
if len(from_to) != 2 {
|
|
filter.Log("Missing value in range: '", substring, "'")
|
|
return
|
|
}
|
|
fromStr := from_to[0]
|
|
var hasFrom bool = true
|
|
if fromStr == "" {
|
|
hasFrom = false
|
|
}
|
|
from64, err := strconv.ParseInt(fromStr, 10, 32)
|
|
var from = int(from64)
|
|
if hasFrom && err != nil {
|
|
filter.Log("Could not parse left side of: '", substring, "'")
|
|
return
|
|
}
|
|
if from < 0 {
|
|
from = len(results) + from
|
|
}
|
|
if from < 0 || from > len(results) {
|
|
filter.Log("Out of bounds:", from_to)
|
|
continue
|
|
}
|
|
|
|
toStr := from_to[1]
|
|
var hasTo bool = true
|
|
if toStr == "" {
|
|
hasTo = false
|
|
}
|
|
to64, err := strconv.ParseInt(toStr, 10, 32)
|
|
var to = int(to64)
|
|
if hasTo && err != nil {
|
|
filter.Log("Could not parse right side of: '", substring, "'")
|
|
return
|
|
}
|
|
if to < 0 {
|
|
to = len(results) + to
|
|
}
|
|
if to < 0 || to > len(results) {
|
|
filter.Log("Out of bounds:", from_to)
|
|
continue
|
|
}
|
|
if hasFrom && hasTo {
|
|
filter.Results = append(filter.Results, results[from:to]...)
|
|
if err != nil {
|
|
filter.Log("Could not substring: ", err)
|
|
}
|
|
} else if hasFrom {
|
|
filter.Results = append(filter.Results, results[from:]...)
|
|
} else if hasTo {
|
|
filter.Results = append(filter.Results, results[:to]...)
|
|
}
|
|
} else {
|
|
pos, err := strconv.ParseInt(substring, 10, 32)
|
|
if err != nil {
|
|
filter.Log("Could not parse: '", substring, "'")
|
|
return
|
|
}
|
|
if pos < 0 {
|
|
pos = int64(len(results)) + pos
|
|
}
|
|
if pos < 0 || pos >= int64(numResults) {
|
|
filter.Log("Out of bounds:", pos)
|
|
continue
|
|
}
|
|
filter.Results = append(filter.Results, results[pos])
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultContains performs a regex contains on all the results of its parents
|
|
func getFilterResultContains(filter *Filter) {
|
|
r, err := regexp.Compile(filter.Var1)
|
|
if err != nil {
|
|
filter.Log("Could not parse:", filter.Var1)
|
|
}
|
|
invert, err := strconv.ParseBool(filter.Var2)
|
|
if err != nil {
|
|
invert = false
|
|
}
|
|
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
contains := r.MatchString(result)
|
|
if contains && !invert {
|
|
filter.Results = append(filter.Results, result)
|
|
} else if !contains && invert {
|
|
filter.Results = append(filter.Results, result)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultSum sums all the numerical values passed from its parents
|
|
func getFilterResultSum(filter *Filter) {
|
|
var sum float64 = 0.0
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
if number, err := strconv.ParseFloat(result, 64); err == nil {
|
|
sum += number
|
|
} else {
|
|
if len(result) > 50 {
|
|
filter.Log("Could not convert value, with length ", len(result), ", to number")
|
|
} else {
|
|
filter.Log("Could not convert value, '", result, "', to number")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
filter.Results = append(filter.Results, fmt.Sprintf("%f", sum))
|
|
}
|
|
|
|
// getFilterResultMin outputs the minimum value of all the numerical values passed from its parents
|
|
func getFilterResultMin(filter *Filter) {
|
|
var min = math.MaxFloat64
|
|
var setMin = false
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
if number, err := strconv.ParseFloat(result, 64); err == nil {
|
|
if number < min {
|
|
min = number
|
|
setMin = true
|
|
}
|
|
} else {
|
|
if len(result) > 50 {
|
|
filter.Log("Could not convert value, with length ", len(result), ", to number")
|
|
} else {
|
|
filter.Log("Could not convert value, '", result, "', to number")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if setMin {
|
|
filter.Results = append(filter.Results, fmt.Sprintf("%f", min))
|
|
}
|
|
}
|
|
|
|
// getFilterResultMax outputs the maximum value of all the numerical values passed from its parents
|
|
func getFilterResultMax(filter *Filter) {
|
|
var max = -math.MaxFloat64
|
|
var setMax = false
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
if number, err := strconv.ParseFloat(result, 64); err == nil {
|
|
if number > max {
|
|
max = number
|
|
setMax = true
|
|
}
|
|
} else {
|
|
if len(result) > 50 {
|
|
filter.Log("Could not convert value, with length ", len(result), ", to number")
|
|
} else {
|
|
filter.Log("Could not convert value, '", result, "', to number")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if setMax {
|
|
filter.Results = append(filter.Results, fmt.Sprintf("%f", max))
|
|
}
|
|
}
|
|
|
|
// getFilterResultAverage outputs the average value of all the numerical values passed from its parents
|
|
func getFilterResultAverage(filter *Filter) {
|
|
var sum float64 = 0.0
|
|
var count float64 = 0.0
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
if number, err := strconv.ParseFloat(result, 64); err == nil {
|
|
sum += number
|
|
count++
|
|
} else {
|
|
if len(result) > 50 {
|
|
filter.Log("Could not convert value, with length ", len(result), ", to number")
|
|
} else {
|
|
filter.Log("Could not convert value, '", result, "', to number")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if count > 0 {
|
|
filter.Results = append(filter.Results, fmt.Sprintf("%f", sum/count))
|
|
}
|
|
}
|
|
|
|
// getFilterResultCount outputs the count of the number of inputs it gets from its parents
|
|
func getFilterResultCount(filter *Filter) {
|
|
var count = 0
|
|
for _, parent := range filter.Parents {
|
|
count += len(parent.Results)
|
|
}
|
|
filter.Results = append(filter.Results, fmt.Sprintf("%d", count))
|
|
}
|
|
|
|
// https://gosamples.dev/round-float/
|
|
func roundFloat(val float64, precision uint) float64 {
|
|
if precision == 0 {
|
|
math.Round(val)
|
|
}
|
|
ratio := math.Pow(10, float64(precision))
|
|
rounded := math.Round(val*ratio) / ratio
|
|
return rounded
|
|
}
|
|
|
|
// getFilterResultRound outputs the rounded value of every numerical input
|
|
func getFilterResultRound(filter *Filter) {
|
|
var decimals int64 = 0
|
|
d, err := strconv.ParseInt(filter.Var2, 10, 32)
|
|
if err == nil {
|
|
decimals = d
|
|
}
|
|
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
if number, err := strconv.ParseFloat(result, 64); err == nil {
|
|
rounded := roundFloat(number, uint(decimals))
|
|
filter.Results = append(filter.Results, fmt.Sprintf("%f", rounded))
|
|
} else {
|
|
if len(result) > 50 {
|
|
filter.Log("Could not convert value, with length ", len(result), ", to number")
|
|
} else {
|
|
filter.Log("Could not convert value, '", result, "', to number")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// storeFilterResult stores the results of its parents in the database
|
|
func storeFilterResult(filter *Filter, db *gorm.DB, debug bool) {
|
|
if debug {
|
|
return
|
|
}
|
|
|
|
filterOutputs := make([]FilterOutput, 0, 1)
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
filterOutputs = append(filterOutputs, FilterOutput{
|
|
WatchID: filter.WatchID,
|
|
Name: filter.Name,
|
|
Time: time.Now(),
|
|
Value: result,
|
|
})
|
|
}
|
|
}
|
|
if len(filterOutputs) > 0 {
|
|
tx := db.Create(&filterOutputs)
|
|
if tx.Error != nil {
|
|
log.Println("Could not store value:", tx.Error)
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultConditionDiff outputs any input that is different from the last stored value of a specific store filter
|
|
func getFilterResultConditionDiff(filter *Filter, db *gorm.DB) {
|
|
var previousOutput FilterOutput
|
|
db.Model(&FilterOutput{}).Limit(1).Order("time desc").Where("watch_id = ? AND name = ?", filter.WatchID, filter.Var2).Find(&previousOutput)
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
if previousOutput.WatchID == 0 {
|
|
filter.Results = append(filter.Results, result)
|
|
} else if previousOutput.Value != result {
|
|
filter.Results = append(filter.Results, result)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultConditionLowerLast outputs any input that is lower from the last stored value of a specific store filter
|
|
func getFilterResultConditionLowerLast(filter *Filter, db *gorm.DB) {
|
|
var previousOutput FilterOutput
|
|
db.Model(&FilterOutput{}).Order("time desc").Where("watch_id = ? AND name = ?", filter.WatchID, filter.Var2).Limit(1).Find(&previousOutput)
|
|
lastValue, lastValueErr := strconv.ParseFloat(previousOutput.Value, 64)
|
|
if lastValueErr != nil {
|
|
filter.Log("Could not convert previous value to number all will pass: '", previousOutput.Value, "'")
|
|
}
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
if previousOutput.WatchID == 0 {
|
|
filter.Results = append(filter.Results, result)
|
|
} else {
|
|
number, err := strconv.ParseFloat(result, 64)
|
|
if err != nil {
|
|
if len(result) > 50 {
|
|
filter.Log("Could not convert value, with length ", len(result), ", to number")
|
|
} else {
|
|
filter.Log("Could not convert value, '", result, "', to number")
|
|
}
|
|
continue
|
|
}
|
|
if lastValueErr != nil || number < lastValue {
|
|
filter.Results = append(filter.Results, result)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultConditionLowest outputs any input that is lower than all values of a specific store filter
|
|
func getFilterResultConditionLowest(filter *Filter, db *gorm.DB) {
|
|
var previousOutputs []FilterOutput
|
|
db.Model(&FilterOutput{}).Where("watch_id = ? AND name = ?", filter.WatchID, filter.Name).Find(&previousOutputs)
|
|
lowest := math.MaxFloat64
|
|
for _, previousOutput := range previousOutputs {
|
|
number, err := strconv.ParseFloat(previousOutput.Value, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if number < lowest {
|
|
lowest = number
|
|
}
|
|
}
|
|
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
number, err := strconv.ParseFloat(result, 64)
|
|
if err != nil {
|
|
if len(result) > 50 {
|
|
filter.Log("Could not convert value, with length ", len(result), ", to number")
|
|
} else {
|
|
filter.Log("Could not convert value, '", result, "', to number")
|
|
}
|
|
continue
|
|
}
|
|
if number < lowest {
|
|
filter.Results = append(filter.Results, result)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultConditionLowerThan outputs any input that is lower than a given value
|
|
func getFilterResultConditionLowerThan(filter *Filter) {
|
|
threshold, err := strconv.ParseFloat(filter.Var2, 64)
|
|
if err != nil {
|
|
filter.Log("Could not convert convert threshold to number: '", filter.Var2, "'")
|
|
return
|
|
}
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
number, err := strconv.ParseFloat(result, 64)
|
|
if err != nil {
|
|
if len(result) > 50 {
|
|
filter.Log("Could not convert value, with length ", len(result), ", to number")
|
|
} else {
|
|
filter.Log("Could not convert value, '", result, "', to number")
|
|
}
|
|
continue
|
|
}
|
|
if number < threshold {
|
|
filter.Results = append(filter.Results, result)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultConditionHigherLast outputs any input that is higher from the last stored value of a specific store filter
|
|
func getFilterResultConditionHigherLast(filter *Filter, db *gorm.DB) {
|
|
var previousOutput FilterOutput
|
|
db.Model(&FilterOutput{}).Order("time desc").Where("watch_id = ? AND name = ?", filter.WatchID, filter.Var2).Limit(1).Find(&previousOutput)
|
|
lastValue, lastValueErr := strconv.ParseFloat(previousOutput.Value, 64)
|
|
if lastValueErr != nil {
|
|
filter.Log("Could not convert previous value to number all will pass: '", previousOutput.Value, "'")
|
|
}
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
if previousOutput.WatchID == 0 {
|
|
filter.Results = append(filter.Results, result)
|
|
} else {
|
|
number, err := strconv.ParseFloat(result, 64)
|
|
if err != nil {
|
|
if len(result) > 50 {
|
|
filter.Log("Could not convert value, with length ", len(result), ", to number")
|
|
} else {
|
|
filter.Log("Could not convert value, '", result, "', to number")
|
|
}
|
|
continue
|
|
}
|
|
if lastValueErr != nil || number > lastValue {
|
|
filter.Results = append(filter.Results, result)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultConditionHighest outputs any input that is higher than all values of a specific store filter
|
|
func getFilterResultConditionHighest(filter *Filter, db *gorm.DB) {
|
|
var previousOutputs []FilterOutput
|
|
db.Model(&FilterOutput{}).Where("watch_id = ? AND name = ?", filter.WatchID, filter.Var2).Find(&previousOutputs)
|
|
highest := -math.MaxFloat64
|
|
for _, previousOutput := range previousOutputs {
|
|
number, err := strconv.ParseFloat(previousOutput.Value, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if number > highest {
|
|
highest = number
|
|
}
|
|
}
|
|
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
number, err := strconv.ParseFloat(result, 64)
|
|
if err != nil {
|
|
if len(result) > 50 {
|
|
filter.Log("Could not convert value, with length ", len(result), ", to number")
|
|
} else {
|
|
filter.Log("Could not convert value, '", result, "', to number")
|
|
}
|
|
continue
|
|
}
|
|
if number > highest {
|
|
filter.Results = append(filter.Results, result)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultConditionHigherThan outputs any input that is higher than a given value
|
|
func getFilterResultConditionHigherThan(filter *Filter) {
|
|
threshold, err := strconv.ParseFloat(filter.Var2, 64)
|
|
if err != nil {
|
|
filter.Log("Could not convert convert threshold to number: '", filter.Var2, "'")
|
|
return
|
|
}
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
number, err := strconv.ParseFloat(result, 64)
|
|
if err != nil {
|
|
if len(result) > 50 {
|
|
filter.Log("Could not convert value, with length ", len(result), ", to number")
|
|
} else {
|
|
filter.Log("Could not convert value, '", result, "', to number")
|
|
}
|
|
continue
|
|
}
|
|
if number > threshold {
|
|
filter.Results = append(filter.Results, result)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getFilterResultUnique outputs the unique values
|
|
func getFilterResultUnique(filter *Filter) {
|
|
valueMap := make(map[string]bool)
|
|
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
valueMap[result] = true
|
|
}
|
|
}
|
|
|
|
for value := range valueMap {
|
|
filter.Results = append(filter.Results, value)
|
|
}
|
|
}
|
|
|
|
// notifyFilter creates a notification string, and sends it to web.notify
|
|
func notifyFilter(filters []Filter, filter *Filter, watch *Watch, web *Web, debug bool) {
|
|
haveResults := false
|
|
for _, parent := range filter.Parents {
|
|
if len(parent.Results) > 0 {
|
|
haveResults = true
|
|
}
|
|
}
|
|
if !debug && !haveResults {
|
|
return
|
|
}
|
|
tmpl, err := template.New("notify").Parse(filter.Var1)
|
|
if err != nil {
|
|
filter.Log("Could not parse template: ", err)
|
|
log.Println("Could not parse template: ", err)
|
|
return
|
|
}
|
|
|
|
// add convenience values for all filters
|
|
dataMap := make(map[string]any, 20)
|
|
for _, f := range filters {
|
|
dataMap[f.Name] = template.HTML(strings.Join(f.Results, ", "))
|
|
dataMap[f.Name+"_Type"] = f.Type
|
|
dataMap[f.Name+"_Var1"] = f.Var1
|
|
dataMap[f.Name+"_Var2"] = f.Var2
|
|
}
|
|
|
|
// add the watch name
|
|
dataMap["WatchName"] = template.HTML(watch.Name)
|
|
|
|
var buffer bytes.Buffer
|
|
tmpl.Execute(&buffer, dataMap)
|
|
if debug {
|
|
filter.Results = append(filter.Results, buffer.String())
|
|
} else {
|
|
notifier := filter.Var2
|
|
web.notify(notifier, buffer.String())
|
|
}
|
|
|
|
}
|
|
|
|
// TriggerSchedule gets called by the cronjob scheduler to start a watch execution
|
|
func TriggerSchedule(watchID WatchID, web *Web, scheduleID *FilterID) {
|
|
var watch *Watch
|
|
web.db.Model(&Watch{}).First(&watch, watchID)
|
|
|
|
var filters []Filter
|
|
web.db.Model(&Filter{}).Where("watch_id = ?", watch.ID).Find(&filters)
|
|
|
|
var connections []FilterConnection
|
|
web.db.Model(&FilterConnection{}).Where("watch_id = ?", watch.ID).Find(&connections)
|
|
|
|
buildFilterTree(filters, connections)
|
|
ProcessFilters(filters, web, watch, false, scheduleID)
|
|
}
|
|
|
|
// getCronDebugResult parses the cron schedule string and logs any errors
|
|
func getCronDebugResult(filter *Filter) {
|
|
scheduleSplit := strings.Split(filter.Var1, "+")
|
|
scheduleTrimmed := strings.TrimSpace(scheduleSplit[0])
|
|
_, err := cron.ParseStandard(scheduleTrimmed)
|
|
if err != nil {
|
|
filter.Log(err)
|
|
}
|
|
_, err = getJittersFromScheduleString(filter.Var1)
|
|
if err != nil {
|
|
filter.Log(err)
|
|
}
|
|
}
|
|
|
|
// getFilterResultLua executes a given Lua script
|
|
func getFilterResultLua(filter *Filter) {
|
|
L := lua.NewState()
|
|
defer L.Close()
|
|
|
|
lualibs.Preload(L)
|
|
|
|
// add all parent results to the 'inputs' table
|
|
inputs := L.CreateTable(10, 0)
|
|
for _, parent := range filter.Parents {
|
|
for _, result := range parent.Results {
|
|
inputs.Append(lua.LString(result))
|
|
}
|
|
}
|
|
L.SetGlobal("inputs", inputs)
|
|
|
|
// create the 'outputs' table
|
|
outputs := L.CreateTable(10, 0)
|
|
L.SetGlobal("outputs", outputs)
|
|
|
|
// create the 'logs' table
|
|
logs := L.CreateTable(10, 0)
|
|
L.SetGlobal("logs", logs)
|
|
|
|
err := L.DoString(filter.Var1)
|
|
if err != nil {
|
|
filter.Log(err)
|
|
return
|
|
}
|
|
|
|
// add all the values from the 'outputs' table to this filters results
|
|
outputs.ForEach(
|
|
func(key lua.LValue, value lua.LValue) {
|
|
filter.Results = append(filter.Results, value.String())
|
|
},
|
|
)
|
|
// add all the messages from the 'logs' table to this filters logs
|
|
logs.ForEach(
|
|
func(key lua.LValue, value lua.LValue) {
|
|
filter.Logs = append(filter.Logs, value.String())
|
|
},
|
|
)
|
|
}
|
|
|
|
// getFilterResultExpect outputs once if there is no results from its parents a set number of times
|
|
func getFilterResultExpect(filter *Filter, web *Web, debug bool) {
|
|
if len(filter.Parents) == 0 {
|
|
filter.Logs = append(filter.Logs, "Need Parents")
|
|
return
|
|
}
|
|
for i := range filter.Parents {
|
|
parent := filter.Parents[i]
|
|
if len(parent.Results) > 0 { // reset/delete expectFails
|
|
web.db.Delete(&ExpectFail{}, "watch_id = ? AND name = ?", filter.WatchID, filter.Name)
|
|
return
|
|
}
|
|
}
|
|
if debug {
|
|
filter.Results = append(filter.Results, "Expected")
|
|
return
|
|
}
|
|
expectThreshold, err := strconv.Atoi(filter.Var1)
|
|
if err != nil {
|
|
filter.Logs = append(filter.Logs, "Could not parse to int:", filter.Var1)
|
|
expectThreshold = 1
|
|
}
|
|
if expectThreshold <= 0 {
|
|
// 0 doesn't really make sense so just set to 1
|
|
expectThreshold = 1
|
|
}
|
|
|
|
var expectFails []ExpectFail
|
|
web.db.Model(&ExpectFail{}).Find(&expectFails, "watch_id = ? AND name = ?", filter.WatchID, filter.Name)
|
|
|
|
// +1 so this one is already counted
|
|
failCount := len(expectFails) + 1
|
|
|
|
if failCount > expectThreshold {
|
|
return
|
|
} else if expectThreshold == failCount {
|
|
filter.Results = append(filter.Results, "Expected")
|
|
}
|
|
|
|
expectFail := ExpectFail{
|
|
WatchID: filter.WatchID,
|
|
Name: filter.Name,
|
|
Time: time.Now(),
|
|
}
|
|
web.db.Create(&expectFail)
|
|
}
|
|
|
|
// getFilterResultDisableSchedules disables all schedules of a watch if it gets any inputs
|
|
func getFilterResultDisableSchedules(filter *Filter, web *Web, debug bool) {
|
|
if len(filter.Parents) == 0 {
|
|
filter.Logs = append(filter.Logs, "Need Parents")
|
|
return
|
|
}
|
|
anyParentWithResults := false
|
|
for i := range filter.Parents {
|
|
parent := filter.Parents[i]
|
|
if len(parent.Results) > 0 {
|
|
anyParentWithResults = true
|
|
break
|
|
}
|
|
}
|
|
if !anyParentWithResults {
|
|
return
|
|
}
|
|
|
|
if debug {
|
|
filter.Results = append(filter.Results, "Would have disabled Schedules")
|
|
return
|
|
}
|
|
|
|
web.db.Model(&Filter{}).Where("watch_id = ? AND type = 'cron'", filter.WatchID).Update("Var2", "no")
|
|
|
|
var cronFilters []Filter
|
|
web.db.Model(&Filter{}).Where("watch_id = ? AND type = 'cron'", filter.WatchID).Find(&cronFilters)
|
|
for _, cronFilter := range cronFilters {
|
|
entryID, exist := web.cronWatch[cronFilter.ID]
|
|
if !exist {
|
|
return
|
|
}
|
|
web.cron.Remove(entryID)
|
|
delete(web.cronWatch, filter.ID)
|
|
}
|
|
}
|
|
|
|
// getFilterResultEcho is a debug filter type, used to bootstrap some tests
|
|
func getFilterResultEcho(filter *Filter) {
|
|
filter.Results = append(filter.Results, filter.Var1)
|
|
}
|