Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ee893cc360 | |||
| d73907d357 | |||
| 0b50305f38 | |||
| ee3d719c3a | |||
| d76cdca4a5 | |||
| b34ed607b7 | |||
| 932e191510 | |||
| 3a6c407fe7 | |||
| 8c3afc31f4 | |||
| 2e4ba44952 | |||
| 4192ac719e | |||
| 539c94595f | |||
| de21e611a3 | |||
| dea362361e | |||
| 7b77519f49 | |||
| 94df7e1ec3 | |||
| babd8d3089 | |||
| 52ef28f091 | |||
| 80d72f8a1b | |||
| 2e8f4a0581 | |||
| 7fd2e2b050 | |||
| 8c65166a90 | |||
| 733b49c2c4 | |||
| 711b5c40b1 | |||
| f94e616d8d | |||
| fb7848f341 | |||
| 007857afd5 | |||
| e4bbe8c035 | |||
| cb5226f6e4 | |||
| c69770d1dd | |||
| e07a53046f | |||
| 19a0b8c2ac | |||
| 97f73703b1 | |||
| 4fcd11f497 | |||
| 7f1023fa9b | |||
| d49497da80 | |||
| d8c359bd8a | |||
| ad4b117f6e | |||
| 22d2f9847c | |||
| 1c7f299b98 | |||
| 602fdce0ee | |||
| 61fde6a2ca | |||
| d843bcc258 | |||
| f2856e0f26 | |||
| 58ef1aa311 | |||
| 6a6570b8e3 | |||
| 29a0860caa | |||
| fb760a9f6d | |||
| c9f13f4398 | |||
| dd03a8cf63 | |||
| 0a6ade4da9 | |||
| 5c8c11d78b | |||
| 00502cc565 | |||
| 0febe3fda5 | |||
| fcd4bb4561 | |||
| 89f763e65d | |||
| 075eb94fa2 | |||
| e9cf8a9180 | |||
| 64ad353628 | |||
| 5518865bc6 | |||
| 50321d897a | |||
| e18a7e9ce0 | |||
| 536b590080 | |||
| 098ce0673a | |||
| 2677796322 | |||
| 5cc7fb30ed | |||
| 222b8103d6 | |||
| 727d5b0ace | |||
| d7b45e5f01 | |||
| 578a262d90 | |||
| c6e11f88b4 | |||
| b795331efb |
@@ -101,7 +101,7 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '^1.18.3'
|
||||
go-version: '^1.20.1'
|
||||
- name: Build Binaries
|
||||
run: |
|
||||
make binary-clean binary-all
|
||||
@@ -111,4 +111,4 @@ jobs:
|
||||
name: binaries.zip
|
||||
path: |
|
||||
scrutiny-web-*
|
||||
scrutiny-collector-metrics-*
|
||||
scrutiny-collector-metrics-*
|
||||
|
||||
@@ -74,15 +74,6 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
- name: "Populate frontend version information"
|
||||
run: "cd webapp/frontend && ./git.version.sh"
|
||||
- name: "Generate frontend"
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
image: node:lts
|
||||
options: -v ${{ github.workspace }}:/work
|
||||
run: |
|
||||
cd /work
|
||||
make binary-frontend && echo "print contents of /work/dist" && ls -alt /work/dist
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
@@ -134,16 +125,6 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
- name: "Populate frontend version information"
|
||||
run: "cd webapp/frontend && ./git.version.sh"
|
||||
- name: "Generate frontend & version information"
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
image: node:lts
|
||||
options: -v ${{ github.workspace }}:/work
|
||||
run: |
|
||||
cd /work
|
||||
make binary-frontend && echo "print contents of /work/dist" && ls -alt /work/dist
|
||||
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
@@ -181,4 +162,4 @@ jobs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
# cache-from: type=gha
|
||||
# cache-to: type=gha,mode=max
|
||||
# cache-to: type=gha,mode=max
|
||||
|
||||
@@ -19,16 +19,6 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
- name: "Populate frontend version information"
|
||||
run: "cd webapp/frontend && ./git.version.sh"
|
||||
- name: "Generate frontend & version information"
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
image: node:lts
|
||||
options: -v ${{ github.workspace }}:/work
|
||||
run: |
|
||||
cd /work
|
||||
make binary-frontend && echo "print contents of /work/dist" && ls -alt /work/dist
|
||||
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
name: workspace
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18.3' # The Go version to download (if necessary) and use.
|
||||
go-version: '1.20.1' # The Go version to download (if necessary) and use.
|
||||
- name: Build Binaries
|
||||
run: |
|
||||
make binary-clean binary-all
|
||||
|
||||
+11
-11
@@ -5,18 +5,18 @@ The Scrutiny repository is a [monorepo](https://en.wikipedia.org/wiki/Monorepo)
|
||||
- Scrutiny Frontend Angular SPA
|
||||
- S.M.A.R.T Collector
|
||||
|
||||
Depending on the functionality you are adding, you may need to setup a development environment for 1 or more projects.
|
||||
Depending on the functionality you are adding, you may need to setup a development environment for 1 or more projects.
|
||||
|
||||
# Modifying the Scrutiny Backend Server (API)
|
||||
|
||||
1. install the [Go runtime](https://go.dev/doc/install) (v1.18+)
|
||||
1. install the [Go runtime](https://go.dev/doc/install) (v1.20+)
|
||||
2. download the `scrutiny-web-frontend.tar.gz` for
|
||||
the [latest release](https://github.com/AnalogJ/scrutiny/releases/latest). Extract to a folder named `dist`
|
||||
3. create a `scrutiny.yaml` config file
|
||||
```yaml
|
||||
# config file for local development. store as scrutiny.yaml
|
||||
version: 1
|
||||
|
||||
|
||||
web:
|
||||
listen:
|
||||
port: 8080
|
||||
@@ -29,13 +29,13 @@ Depending on the functionality you are adding, you may need to setup a developme
|
||||
path: ./dist
|
||||
influxdb:
|
||||
retention_policy: false
|
||||
|
||||
|
||||
log:
|
||||
file: 'web.log' #absolute or relative paths allowed, eg. web.log
|
||||
level: DEBUG
|
||||
|
||||
```
|
||||
4. start a InfluxDB docker container.
|
||||
4. start a InfluxDB docker container.
|
||||
```bash
|
||||
docker run -p 8086:8086 --rm influxdb:2.2
|
||||
```
|
||||
@@ -55,21 +55,21 @@ The frontend is written in Angular. If you're working on the frontend and can us
|
||||
```bash
|
||||
cd webapp/frontend
|
||||
npm install
|
||||
npm run start -- --deploy-url="/web/" --base-href="/web/" --port 4200
|
||||
npm run start -- --serve-path="/web/" --port 4200
|
||||
```
|
||||
3. open your browser and visit [http://localhost:4200/web](http://localhost:4200/web)
|
||||
|
||||
# Modifying both Scrutiny Backend and Frontend Applications
|
||||
If you're developing a feature that requires changes to the backend and the frontend, or a frontend feature that requires real data,
|
||||
If you're developing a feature that requires changes to the backend and the frontend, or a frontend feature that requires real data,
|
||||
you'll need to follow the steps below:
|
||||
|
||||
1. install the [Go runtime](https://go.dev/doc/install) (v1.18+)
|
||||
1. install the [Go runtime](https://go.dev/doc/install) (v1.20+)
|
||||
2. install [NodeJS](https://nodejs.org/en/download/)
|
||||
3. create a `scrutiny.yaml` config file
|
||||
```yaml
|
||||
# config file for local development. store as scrutiny.yaml
|
||||
version: 1
|
||||
|
||||
|
||||
web:
|
||||
listen:
|
||||
port: 8080
|
||||
@@ -82,7 +82,7 @@ you'll need to follow the steps below:
|
||||
path: ./dist
|
||||
influxdb:
|
||||
retention_policy: false
|
||||
|
||||
|
||||
log:
|
||||
file: 'web.log' #absolute or relative paths allowed, eg. web.log
|
||||
level: DEBUG
|
||||
@@ -185,4 +185,4 @@ docker run -p 8086:8086 -d --rm \
|
||||
influxdb:2.2
|
||||
go test ./...
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
@@ -100,7 +100,7 @@ binary-frontend: export NPM_CONFIG_LOGLEVEL = warn
|
||||
binary-frontend: export NG_CLI_ANALYTICS = false
|
||||
binary-frontend:
|
||||
cd webapp/frontend
|
||||
npm install -g @angular/cli@9.1.4
|
||||
npm install -g @angular/cli@v13-lts
|
||||
mkdir -p $(CURDIR)/dist
|
||||
npm ci
|
||||
npm run build:prod -- --output-path=$(CURDIR)/dist
|
||||
|
||||
@@ -46,7 +46,7 @@ Scrutiny is a simple but focused application, with a couple of core features:
|
||||
- Customized thresholds using real world failure rates
|
||||
- Temperature tracking
|
||||
- Provided as an all-in-one Docker image (but can be installed manually)
|
||||
- Future Configurable Alerting/Notifications via Webhooks
|
||||
- Configurable Alerting/Notifications via Webhooks
|
||||
- (Future) Hard Drive performance testing & tracking
|
||||
|
||||
# Getting Started
|
||||
@@ -69,7 +69,7 @@ See [docs/TROUBLESHOOTING_DEVICE_COLLECTOR.md](./docs/TROUBLESHOOTING_DEVICE_COL
|
||||
|
||||
If you're using Docker, getting started is as simple as running the following command:
|
||||
|
||||
> See [docker/example.omnibus.docker-compose.yml](./docker/example.omnibus.docker-compose.yml) for a docker-compose file.
|
||||
> See [docker/example.omnibus.docker-compose.yml](./docker/example.omnibus.docker-compose.yml) for a docker-compose file.
|
||||
|
||||
```bash
|
||||
docker run -it --rm -p 8080:8080 -p 8086:8086 \
|
||||
@@ -91,10 +91,14 @@ docker run -it --rm -p 8080:8080 -p 8086:8086 \
|
||||
|
||||
### Hub/Spoke Deployment
|
||||
|
||||
In addition to the Omnibus image (available under the `latest` tag) there are 2 other Docker images available:
|
||||
In addition to the Omnibus image (available under the `latest` tag) you can deploy in Hub/Spoke mode, which requires 3
|
||||
other Docker images:
|
||||
|
||||
- `ghcr.io/analogj/scrutiny:master-collector` - Contains the Scrutiny data collector, `smartctl` binary and cron-like scheduler. You can run one collector on each server.
|
||||
- `ghcr.io/analogj/scrutiny:master-web` - Contains the Web UI, API and Database. Only one container necessary
|
||||
- `ghcr.io/analogj/scrutiny:master-collector` - Contains the Scrutiny data collector, `smartctl` binary and cron-like
|
||||
scheduler. You can run one collector on each server.
|
||||
- `ghcr.io/analogj/scrutiny:master-web` - Contains the Web UI and API. Only one container necessary
|
||||
- `influxdb:2.2` - InfluxDB image, used by the Web container to persist SMART data. Only one container necessary
|
||||
See [docs/TROUBLESHOOTING_INFLUXDB.md](./docs/TROUBLESHOOTING_INFLUXDB.md)
|
||||
|
||||
> See [docker/example.hubspoke.docker-compose.yml](./docker/example.hubspoke.docker-compose.yml) for a docker-compose file.
|
||||
|
||||
@@ -153,7 +157,7 @@ Neither file is required, however if provided, it allows you to configure how Sc
|
||||
|
||||
## Cron Schedule
|
||||
Unfortunately the Cron schedule cannot be configured via the `collector.yaml` (as the collector binary needs to be trigged by a scheduler/cron).
|
||||
However, if you are using the official `ghcr.io/analogj/scrutiny:master-collector` or `ghcr.io/analogj/scrutiny:master-omnibus` docker images,
|
||||
However, if you are using the official `ghcr.io/analogj/scrutiny:master-collector` or `ghcr.io/analogj/scrutiny:master-omnibus` docker images,
|
||||
you can use the `COLLECTOR_CRON_SCHEDULE` environmental variable to override the default cron schedule (daily @ midnight - `0 0 * * *`).
|
||||
|
||||
`docker run -e COLLECTOR_CRON_SCHEDULE="0 0 * * *" ...`
|
||||
@@ -170,6 +174,7 @@ Scrutiny supports sending SMART device failure notifications via the following s
|
||||
- IFTTT
|
||||
- Join
|
||||
- Mattermost
|
||||
- ntfy
|
||||
- Pushbullet
|
||||
- Pushover
|
||||
- Slack
|
||||
@@ -239,7 +244,7 @@ scrutiny-collector-metrics run --debug --log-file /tmp/collector.log
|
||||
| linux-arm-6 | :white_check_mark: | |
|
||||
| linux-arm-7 | :white_check_mark: | web/collector only. see [#236](https://github.com/AnalogJ/scrutiny/issues/236) |
|
||||
| linux-arm64 | :white_check_mark: | :white_check_mark: |
|
||||
| freebsd-amd64 | :white_check_mark: | |
|
||||
| freebsd-amd64 | :white_check_mark: | |
|
||||
| macos-amd64 | :white_check_mark: | :white_check_mark: |
|
||||
| macos-arm64 | :white_check_mark: | :white_check_mark: |
|
||||
| windows-amd64 | :white_check_mark: | WIP, see [#15](https://github.com/AnalogJ/scrutiny/issues/15) |
|
||||
|
||||
@@ -30,8 +30,14 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
configFilePath := "/opt/scrutiny/config/collector.yaml"
|
||||
configFilePathAlternative := "/opt/scrutiny/config/collector.yml"
|
||||
if !utils.FileExists(configFilePath) && utils.FileExists(configFilePathAlternative) {
|
||||
configFilePath = configFilePathAlternative
|
||||
}
|
||||
|
||||
//we're going to load the config file manually, since we need to validate it.
|
||||
err = config.ReadConfig("/opt/scrutiny/config/collector.yaml") // Find and read the config file
|
||||
err = config.ReadConfig(configFilePath) // Find and read the config file
|
||||
if _, ok := err.(errors.ConfigFileMissingError); ok { // Handle errors reading the config file
|
||||
//ignore "could not find config file"
|
||||
} else if err != nil {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var httpClient = &http.Client{Timeout: 10 * time.Second}
|
||||
var httpClient = &http.Client{Timeout: 60 * time.Second}
|
||||
|
||||
type BaseCollector struct {
|
||||
logger *logrus.Entry
|
||||
|
||||
@@ -36,6 +36,25 @@ func TestConfiguration_GetScanOverrides_Simple(t *testing.T) {
|
||||
require.Equal(t, []models.ScanOverride{{Device: "/dev/sda", DeviceType: []string{"sat"}, Ignore: false}}, scanOverrides)
|
||||
}
|
||||
|
||||
// fixes #418
|
||||
func TestConfiguration_GetScanOverrides_DeviceTypeComma(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
//setup
|
||||
testConfig, _ := config.Create()
|
||||
|
||||
//test
|
||||
err := testConfig.ReadConfig(path.Join("testdata", "device_type_comma.yaml"))
|
||||
require.NoError(t, err, "should correctly load simple device config")
|
||||
scanOverrides := testConfig.GetDeviceOverrides()
|
||||
|
||||
//assert
|
||||
require.Equal(t, []models.ScanOverride{
|
||||
{Device: "/dev/sda", DeviceType: []string{"sat", "auto"}, Ignore: false},
|
||||
{Device: "/dev/sdb", DeviceType: []string{"sat,auto"}, Ignore: false},
|
||||
}, scanOverrides)
|
||||
}
|
||||
|
||||
func TestConfiguration_GetScanOverrides_Ignore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
version: 1
|
||||
devices:
|
||||
# the scrutiny config parser will detect `sat,auto` as two separate items in a list. If you want to use `-d sat,auto` you must
|
||||
# set 'sat,auto' in a list (see eg. /dev/sbd)
|
||||
- device: /dev/sda
|
||||
type: 'sat,auto'
|
||||
- device: /dev/sdb
|
||||
type:
|
||||
- sat,auto
|
||||
+29
-17
@@ -1,50 +1,62 @@
|
||||
# syntax=docker/dockerfile:1.4
|
||||
########################################################################################################################
|
||||
# Omnibus Image
|
||||
# NOTE: this image requires the `make binary-frontend` target to have been run before `docker build` The `dist` directory must exist.
|
||||
########################################################################################################################
|
||||
|
||||
######## Build the frontend
|
||||
FROM --platform=${BUILDPLATFORM} node AS frontendbuild
|
||||
WORKDIR /go/src/github.com/analogj/scrutiny
|
||||
COPY --link . /go/src/github.com/analogj/scrutiny
|
||||
|
||||
########
|
||||
FROM golang:1.18-bullseye as backendbuild
|
||||
RUN make binary-frontend
|
||||
|
||||
|
||||
######## Build the backend
|
||||
FROM golang:1.20-bullseye as backendbuild
|
||||
|
||||
WORKDIR /go/src/github.com/analogj/scrutiny
|
||||
COPY . /go/src/github.com/analogj/scrutiny
|
||||
COPY --link . /go/src/github.com/analogj/scrutiny
|
||||
RUN make binary-clean binary-all WEB_BINARY_NAME=scrutiny
|
||||
|
||||
|
||||
########
|
||||
######## Combine build artifacts in runtime image
|
||||
FROM debian:bullseye-slim as runtime
|
||||
ARG TARGETARCH
|
||||
EXPOSE 8080
|
||||
WORKDIR /opt/scrutiny
|
||||
ENV PATH="/opt/scrutiny/bin:${PATH}"
|
||||
ENV INFLUXD_CONFIG_PATH=/opt/scrutiny/influxdb
|
||||
ENV S6VER="1.21.8.0"
|
||||
ENV INFLUXVER="2.2.0"
|
||||
|
||||
RUN apt-get update && apt-get install -y cron smartmontools ca-certificates curl tzdata \
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
cron \
|
||||
curl \
|
||||
smartmontools \
|
||||
tzdata \
|
||||
&& update-ca-certificates \
|
||||
&& case ${TARGETARCH} in \
|
||||
"amd64") S6_ARCH=amd64 ;; \
|
||||
"arm64") S6_ARCH=aarch64 ;; \
|
||||
esac \
|
||||
&& curl https://github.com/just-containers/s6-overlay/releases/download/v1.21.8.0/s6-overlay-${S6_ARCH}.tar.gz -L -s --output /tmp/s6-overlay-${S6_ARCH}.tar.gz \
|
||||
&& curl https://github.com/just-containers/s6-overlay/releases/download/v${S6VER}/s6-overlay-${S6_ARCH}.tar.gz -L -s --output /tmp/s6-overlay-${S6_ARCH}.tar.gz \
|
||||
&& tar xzf /tmp/s6-overlay-${S6_ARCH}.tar.gz -C / \
|
||||
&& rm -rf /tmp/s6-overlay-${S6_ARCH}.tar.gz \
|
||||
&& curl -L https://dl.influxdata.com/influxdb/releases/influxdb2-2.2.0-${TARGETARCH}.deb --output /tmp/influxdb2-2.2.0-${TARGETARCH}.deb \
|
||||
&& dpkg -i --force-all /tmp/influxdb2-2.2.0-${TARGETARCH}.deb
|
||||
&& curl -L https://dl.influxdata.com/influxdb/releases/influxdb2-${INFLUXVER}-${TARGETARCH}.deb --output /tmp/influxdb2-${INFLUXVER}-${TARGETARCH}.deb \
|
||||
&& dpkg -i --force-all /tmp/influxdb2-${INFLUXVER}-${TARGETARCH}.deb \
|
||||
&& rm -rf /tmp/influxdb2-2.2.0-${TARGETARCH}.deb
|
||||
|
||||
COPY /rootfs /
|
||||
|
||||
COPY /rootfs/etc/cron.d/scrutiny /etc/cron.d/scrutiny
|
||||
COPY --from=backendbuild /go/src/github.com/analogj/scrutiny/scrutiny /opt/scrutiny/bin/
|
||||
COPY --from=backendbuild /go/src/github.com/analogj/scrutiny/scrutiny-collector-metrics /opt/scrutiny/bin/
|
||||
COPY dist /opt/scrutiny/web
|
||||
RUN chmod +x /opt/scrutiny/bin/scrutiny && \
|
||||
chmod +x /opt/scrutiny/bin/scrutiny-collector-metrics && \
|
||||
chmod 0644 /etc/cron.d/scrutiny && \
|
||||
COPY --link --from=backendbuild --chmod=755 /go/src/github.com/analogj/scrutiny/scrutiny /opt/scrutiny/bin/
|
||||
COPY --link --from=backendbuild --chmod=755 /go/src/github.com/analogj/scrutiny/scrutiny-collector-metrics /opt/scrutiny/bin/
|
||||
COPY --link --from=frontendbuild --chmod=644 /go/src/github.com/analogj/scrutiny/dist /opt/scrutiny/web
|
||||
RUN chmod 0644 /etc/cron.d/scrutiny && \
|
||||
rm -f /etc/cron.daily/* && \
|
||||
mkdir -p /opt/scrutiny/web && \
|
||||
mkdir -p /opt/scrutiny/config && \
|
||||
chmod -R ugo+rwx /opt/scrutiny/config
|
||||
|
||||
|
||||
CMD ["/init"]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
|
||||
########
|
||||
FROM golang:1.18-bullseye as backendbuild
|
||||
FROM golang:1.20-bullseye as backendbuild
|
||||
|
||||
WORKDIR /go/src/github.com/analogj/scrutiny
|
||||
|
||||
|
||||
+14
-10
@@ -1,20 +1,25 @@
|
||||
# syntax=docker/dockerfile:1.4
|
||||
########################################################################################################################
|
||||
# Web Image
|
||||
# NOTE: this image requires the `make binary-frontend` target to have been run before `docker build` The `dist` directory must exist.
|
||||
########################################################################################################################
|
||||
|
||||
######## Build the frontend
|
||||
FROM --platform=${BUILDPLATFORM} node AS frontendbuild
|
||||
WORKDIR /go/src/github.com/analogj/scrutiny
|
||||
COPY --link . /go/src/github.com/analogj/scrutiny
|
||||
|
||||
########
|
||||
FROM golang:1.18-bullseye as backendbuild
|
||||
RUN make binary-frontend
|
||||
|
||||
######## Build the backend
|
||||
FROM golang:1.20-bullseye as backendbuild
|
||||
|
||||
WORKDIR /go/src/github.com/analogj/scrutiny
|
||||
|
||||
COPY . /go/src/github.com/analogj/scrutiny
|
||||
COPY --link . /go/src/github.com/analogj/scrutiny
|
||||
|
||||
RUN make binary-clean binary-all WEB_BINARY_NAME=scrutiny
|
||||
|
||||
|
||||
########
|
||||
######## Combine build artifacts in runtime image
|
||||
FROM debian:bullseye-slim as runtime
|
||||
EXPOSE 8080
|
||||
WORKDIR /opt/scrutiny
|
||||
@@ -22,10 +27,9 @@ ENV PATH="/opt/scrutiny/bin:${PATH}"
|
||||
|
||||
RUN apt-get update && apt-get install -y ca-certificates curl tzdata && update-ca-certificates
|
||||
|
||||
COPY --from=backendbuild /go/src/github.com/analogj/scrutiny/scrutiny /opt/scrutiny/bin/
|
||||
COPY dist /opt/scrutiny/web
|
||||
RUN chmod +x /opt/scrutiny/bin/scrutiny && \
|
||||
mkdir -p /opt/scrutiny/web && \
|
||||
COPY --link --from=backendbuild --chmod=755 /go/src/github.com/analogj/scrutiny/scrutiny /opt/scrutiny/bin/
|
||||
COPY --link --from=frontendbuild --chmod=644 /go/src/github.com/analogj/scrutiny/dist /opt/scrutiny/web
|
||||
RUN mkdir -p /opt/scrutiny/web && \
|
||||
mkdir -p /opt/scrutiny/config && \
|
||||
chmod -R ugo+rwx /opt/scrutiny/config
|
||||
CMD ["/opt/scrutiny/bin/scrutiny", "start"]
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
# adding ability to customize the cron schedule.
|
||||
COLLECTOR_CRON_SCHEDULE=${COLLECTOR_CRON_SCHEDULE:-"0 0 * * *"}
|
||||
COLLECTOR_RUN_STARTUP=${COLLECTOR_RUN_STARTUP:-"false"}
|
||||
COLLECTOR_RUN_STARTUP_SLEEP=${COLLECTOR_RUN_STARTUP_SLEEP:-"1"}
|
||||
|
||||
# if the cron schedule has been overridden via env variable (eg docker-compose) we should make sure to strip quotes
|
||||
[[ "${COLLECTOR_CRON_SCHEDULE}" == \"*\" || "${COLLECTOR_CRON_SCHEDULE}" == \'*\' ]] && COLLECTOR_CRON_SCHEDULE="${COLLECTOR_CRON_SCHEDULE:1:-1}"
|
||||
@@ -14,6 +16,13 @@ COLLECTOR_CRON_SCHEDULE=${COLLECTOR_CRON_SCHEDULE:-"0 0 * * *"}
|
||||
# replace placeholder with correct value
|
||||
sed -i 's|{COLLECTOR_CRON_SCHEDULE}|'"${COLLECTOR_CRON_SCHEDULE}"'|g' /etc/cron.d/scrutiny
|
||||
|
||||
if [[ "${COLLECTOR_RUN_STARTUP}" == "true" ]]; then
|
||||
sleep ${COLLECTOR_RUN_STARTUP_SLEEP}
|
||||
echo "starting scrutiny collector (run-once mode. subsequent calls will be triggered via cron service)"
|
||||
/opt/scrutiny/bin/scrutiny-collector-metrics run
|
||||
fi
|
||||
|
||||
|
||||
# now that we have the env start cron in the foreground
|
||||
echo "starting cron"
|
||||
su -c "cron -f -L 15" root
|
||||
|
||||
+181
-1
@@ -1 +1,181 @@
|
||||
> See [docker/example.hubspoke.docker-compose.yml](https://github.com/AnalogJ/scrutiny/blob/master/docker/example.hubspoke.docker-compose.yml) for a docker-compose file.
|
||||
>
|
||||
See [docker/example.hubspoke.docker-compose.yml](https://github.com/AnalogJ/scrutiny/blob/master/docker/example.hubspoke.docker-compose.yml)
|
||||
for a docker-compose file.
|
||||
|
||||
> The following guide was contributed by @TinJoy59 in #417
|
||||
> It describes how to deploy the Scrutiny in Hub/Spoke mode, where the Hub is running in Docker, and the Spokes (
|
||||
> collectors) are running as binaries.
|
||||
> He's using Proxmox & Synology in his guide, however this should be applicable for almost anyone
|
||||
|
||||
# S.M.A.R.T. Monitoring with Scrutiny across machines
|
||||
|
||||

|
||||
|
||||
### 🤔 The problem:
|
||||
|
||||
Scrutiny offers a nice Docker package called "Omnibus" that can monitor HDDs attached to a Docker host with relative
|
||||
ease. Scrutiny can also be installed in a Hub-Spoke layout where Web interface, Database and Collector come in 3
|
||||
separate packages. The official documentation assumes that the spokes in the "Hub-Spokes layout" run Docker, which is
|
||||
not always the case. The third approach is to install Scrutiny manually, entirely outside of Docker.
|
||||
|
||||
### 💡 The solution:
|
||||
|
||||
This tutorial provides a hybrid configuration where the Hub lives in a Docker instance while the spokes have only
|
||||
Scrutiny Collector installed manually. The Collector periodically send data to the Hub. It's not mind-boggling hard to
|
||||
understand but someone might struggle with the setup. This is for them.
|
||||
|
||||
### 🖥️ My setup:
|
||||
|
||||
I have a Proxmox cluster where one VM runs Docker and all monitoring services - Grafana, Prometheus, various exporters,
|
||||
InfluxDB and so forth. Another VM runs the NAS - OpenMediaVault v6, where all hard drives reside. The Scrutiny Collector
|
||||
is triggered every 30min to collect data on the drives. The data is sent to the Docker VM, running InfluxDB.
|
||||
|
||||
## Setting up the Hub
|
||||
|
||||

|
||||
|
||||
The Hub consists of Scrutiny Web - a web interface for viewing the SMART data. And InfluxDB, where the smartmon data is
|
||||
stored.
|
||||
|
||||
[🔗This is the official Hub-Spoke layout in docker-compose.](https://github.com/AnalogJ/scrutiny/blob/master/docker/example.hubspoke.docker-compose.yml)
|
||||
We are going to reuse parts of it. The ENV variables provide the necessary configuration for the initial setup, both for
|
||||
InfluxDB and Scrutiny.
|
||||
|
||||
If you are working with and existing InfluxDB instance, you can forgo all the `INIT` variables as they already exist.
|
||||
|
||||
The official Scrutiny documentation has a
|
||||
sample [scrutiny.yaml ](https://github.com/AnalogJ/scrutiny/blob/master/example.scrutiny.yaml)file that normally
|
||||
contains the connection and notification details but I always find it easier to configure as much as possible in the
|
||||
docker-compose.
|
||||
|
||||
```yaml
|
||||
version: "3.4"
|
||||
|
||||
networks:
|
||||
monitoring: # A common network for all monitoring services to communicate into
|
||||
external: true
|
||||
notifications: # To Gotify or another Notification service
|
||||
external: true
|
||||
|
||||
services:
|
||||
influxdb:
|
||||
container_name: influxdb
|
||||
image: influxdb:2.1-alpine
|
||||
ports:
|
||||
- 8086:8086
|
||||
volumes:
|
||||
- ${DIR_CONFIG}/influxdb2/db:/var/lib/influxdb2
|
||||
- ${DIR_CONFIG}/influxdb2/config:/etc/influxdb2
|
||||
environment:
|
||||
- DOCKER_INFLUXDB_INIT_MODE=setup
|
||||
- DOCKER_INFLUXDB_INIT_USERNAME=Admin
|
||||
- DOCKER_INFLUXDB_INIT_PASSWORD=${PASSWORD}
|
||||
- DOCKER_INFLUXDB_INIT_ORG=homelab
|
||||
- DOCKER_INFLUXDB_INIT_BUCKET=scrutiny
|
||||
- DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=your-very-secret-token
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- monitoring
|
||||
|
||||
scrutiny:
|
||||
container_name: scrutiny
|
||||
image: ghcr.io/analogj/scrutiny:master-web
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- ${DIR_CONFIG}/scrutiny/config:/opt/scrutiny/config
|
||||
environment:
|
||||
- SCRUTINY_WEB_INFLUXDB_HOST=influxdb
|
||||
- SCRUTINY_WEB_INFLUXDB_PORT=8086
|
||||
- SCRUTINY_WEB_INFLUXDB_TOKEN=your-very-secret-token
|
||||
- SCRUTINY_WEB_INFLUXDB_ORG=homelab
|
||||
- SCRUTINY_WEB_INFLUXDB_BUCKET=scrutiny
|
||||
# Optional but highly recommended to notify you in case of a problem
|
||||
- SCRUTINY_WEB_NOTIFY_URLS=["http://gotify:80/message?token=a-gotify-token"]
|
||||
depends_on:
|
||||
- influxdb
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- notifications
|
||||
- monitoring
|
||||
```
|
||||
|
||||
A freshly initialized Scrutiny instance can be accessed on port 8080, eg. `192.168.0.100:8080`. The interface will be
|
||||
empty because no metrics have been collected yet.
|
||||
|
||||
## Setting up a Spoke ***without*** Docker
|
||||
|
||||

|
||||
|
||||
A spoke consists of the Scrutiny Collector binary that is run on a set interval via crontab and sends the data to the
|
||||
Hub. The official
|
||||
documentation [describes the manual setup of the Collector](https://github.com/AnalogJ/scrutiny/blob/master/docs/INSTALL_MANUAL.md#collector)
|
||||
- dependencies and step by step commands. I have a shortened version that does the same thing but in one line of code.
|
||||
|
||||
```bash
|
||||
# Installing dependencies
|
||||
apt install smartmontools -y
|
||||
|
||||
# 1. Create directory for the binary
|
||||
# 2. Download the binary into that directory
|
||||
# 3. Make it exacutable
|
||||
# 4. List the contents of the library for confirmation
|
||||
mkdir -p /opt/scrutiny/bin && \
|
||||
curl -L https://github.com/AnalogJ/scrutiny/releases/download/v0.5.0/scrutiny-collector-metrics-linux-amd64 > /opt/scrutiny/bin/scrutiny-collector-metrics-linux-amd64 && \
|
||||
chmod +x /opt/scrutiny/bin/scrutiny-collector-metrics-linux-amd64 && \
|
||||
ls -lha /opt/scrutiny/bin
|
||||
```
|
||||
|
||||
<p class="callout warning">When downloading Github Release Assests, make sure that you have the correct version. The provided example is with Release v0.5.0. [The release list can be found here.](https://github.com/analogj/scrutiny/releases) </p>
|
||||
|
||||
Once the Collector is installed, you can run it with the following command. Make sure to add the correct address and
|
||||
port of your Hub as `--api-endpoint`.
|
||||
|
||||
```bash
|
||||
/opt/scrutiny/bin/scrutiny-collector-metrics-linux-amd64 run --api-endpoint "http://192.168.0.100:8080"
|
||||
```
|
||||
|
||||
This will run the Collector once and populate the Web interface of your Scrutiny instance. In order to collect metrics
|
||||
for a time series, you need to run the command repeatedly. Here is an example for crontab, running the Collector every
|
||||
15min.
|
||||
|
||||
```bash
|
||||
# open crontab
|
||||
crontab -e
|
||||
|
||||
# add a line for Scrutiny
|
||||
*/15 * * * * /opt/scrutiny/bin/scrutiny-collector-metrics-linux-amd64 run --api-endpoint "http://192.168.0.100:8080"
|
||||
```
|
||||
|
||||
The Collector has its own independent config file that lives in `/opt/scrutiny/config/collector.yaml` but I did not find
|
||||
a need to modify
|
||||
it. [A default collector.yaml can be found in the official documentation.](https://github.com/AnalogJ/scrutiny/blob/master/example.collector.yaml)
|
||||
|
||||
## Setting up a Spoke ***with*** Docker
|
||||
|
||||

|
||||
|
||||
Setting up a remote Spoke in Docker requires you to split
|
||||
the [official Hub-Spoke layout docker-compose.yml](https://github.com/AnalogJ/scrutiny/blob/master/docker/example.hubspoke.docker-compose.yml)
|
||||
. In the following docker-compose you need to provide the `${API_ENDPOINT}`, in my case `http://192.168.0.100:8080`.
|
||||
Also all drives that you wish to monitor need to be presented to the container under `devices`.
|
||||
|
||||
The image handles the periodic scanning of the drives.
|
||||
|
||||
```yaml
|
||||
version: "3.4"
|
||||
|
||||
services:
|
||||
|
||||
collector:
|
||||
image: 'ghcr.io/analogj/scrutiny:master-collector'
|
||||
cap_add:
|
||||
- SYS_RAWIO
|
||||
volumes:
|
||||
- '/run/udev:/run/udev:ro'
|
||||
environment:
|
||||
COLLECTOR_API_ENDPOINT: ${API_ENDPOINT}
|
||||
devices:
|
||||
- "/dev/sda"
|
||||
- "/dev/sdb"
|
||||
```
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# Manual Windows Install
|
||||
|
||||
This guide is specifically for people who are on a Windows machine using [WSL](https://learn.microsoft.com/en-us/windows/wsl/about) with Docker.
|
||||
|
||||
Scrutiny is made up of three components: an influxdb Database, a collector and a webapp/api. Docker will be used for
|
||||
the influxdb and webapp/API, the collector component will be facilitated by [Windows Task Scheduler](https://learn.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-start-page).
|
||||
|
||||
> **NOTE:** If you are **NOT** using WSL with docker, then the easiest way to get started with [Scrutiny is the omnibus Docker image](https://github.com/AnalogJ/scrutiny#docker).
|
||||
|
||||
## InfluxDB and Webapp/API (Docker)
|
||||
|
||||
1. Copy the [example.hubspoke.docker-compose.yml](https://github.com/AnalogJ/scrutiny/blob/master/docker/example.hubspoke.docker-compose.yml)
|
||||
file and delete the collector section near the bottom of the file.
|
||||
2. Run `docker-compose up -d` to verify that the DB and webapp are working correctly and once its completed, your webapp
|
||||
should be up and running but the dashboard will be empty (default location is `localhost:8080`)
|
||||
|
||||
## Collector (Windows Task Scheduler)
|
||||
|
||||
1. Download the latest `scrutiny-collector-metrics-windows-amd64.exe` from the [releases page](https://github.com/AnalogJ/scrutiny/releases) (under assets)
|
||||
2. On your windows host, open [Windows Task Scheduler](https://www.wikihow.com/Open-Task-Scheduler-in-Windows-10) as **Administrator**
|
||||
1. In the **Start Menu** (Windows key), type `Task Scheduler` and then right click `Run as Administrator` to open
|
||||
3. On the status bar (under the `action` tab), click `Create Task...`
|
||||
4. A new window should open with the `General` Tab open, enter relevant information into the `Name` and `Description` fields
|
||||
1. Under **Security Options** check:
|
||||
1. **Run whether user is logged on or not**
|
||||
2. **Run with highest privileges**
|
||||
5. Next, click the `Triggers` tab and then click `New...` (bottom left-hand side of the window)
|
||||
6. Here you can set how often you want this task to run, example settings are the following:
|
||||
1. **Settings:**
|
||||
1. `Daily`, start at `TODAYS_DATE` `12:00:00 AM`, Recur every `1` days,
|
||||
2. **Advanced Settings:**
|
||||
1. Repeat Task every: `1 hour` for a duration of `Indefinitely`
|
||||
2. Stop task if it runs longer than: `30 minutes`
|
||||
3. Click Ok when satisfied with your schedule
|
||||
> **NOTE:** The above settings will trigger the task **every day at midnight** and then **run every hour after that** (modify as needed)
|
||||
7. Next, click the `Actions` tab and then click `New...` (bottom left-hand side of the window)
|
||||
1. **Action Settings:**
|
||||
1. In the **Program/Script** field, put: `scrutiny-collector-metrics-windows-amd64.exe`
|
||||
2. In the **Add arguments (optional)** field, put: `run --api-endpoint "http://localhost:8080" --config collector.yaml`
|
||||
> **NOTE:**
|
||||
> * Make sure that you put the correct port number (as specified in the docker-compose file) for the webapp (default is `8080`)
|
||||
> * The `--config` param is optional and is not needed if you just want to use the default collector config, see
|
||||
[example.collector.yaml](https://github.com/AnalogJ/scrutiny/blob/master/example.collector.yaml) for more info on the collector config.
|
||||
3. In the **Start in (optional)** field, put: FOLDER_PATH_TO_YOUR `scrutiny-collector-metrics-windows-amd64.exe` file
|
||||
> **NOTE:** Must be exact and do not include `scrutiny-collector-metrics-windows-amd64.exe` in the path
|
||||
4. Click Ok when finished
|
||||
8. Next, click the `Conditions` tab and make sure that everything is unchecked (unless you want to specify otherwise)
|
||||
9. Next, click the `Settings` tab and check everything except for the last checkbox
|
||||
1. **Examples for the following settings:**
|
||||
1. If the task fails, restart every: `5 minutes`
|
||||
2. Attempt restart up to: `3` times
|
||||
3. Stop the task if it runs longer than `1 hour`
|
||||
10. Next, once satisfied with everything, click Ok
|
||||
11. Then, find your newly created task (by its name) in the scheduler task list and then manually run it (right click it and then click `Run`)
|
||||
12. Finally, refresh your dashboard after a minute or two and your drive information should have populated the webapp dashboard.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,12 +7,15 @@ in `docs/guides/` or elsewhere) it will be linked here.
|
||||
- [x] [unraid](./INSTALL_UNRAID.md)
|
||||
- [ ] ESXI
|
||||
- [ ] Proxmox
|
||||
- [x] [Synology](./INSTALL_SYNOLOGY_COLLECTOR.md)
|
||||
- [x] Synology
|
||||
- [Hub/Spoke Deployment - Collector](./INSTALL_SYNOLOGY_COLLECTOR.md)
|
||||
- [Omnibus Deployment](https://drfrankenstein.co.uk/2022/07/28/scrutiny-in-docker-on-a-synology-nas)
|
||||
- [ ] OMV
|
||||
- [ ] Amahi
|
||||
- [ ] Running in a LXC container
|
||||
- [x] [PFSense](./INSTALL_UNRAID.md)
|
||||
- [x] [PFSense](./INSTALL_PFSENSE.md)
|
||||
- [x] QNAP
|
||||
- [x] [RockStor](https://rockstor.com/docs/interface/docker-based-rock-ons/scrutiny.html)
|
||||
- [ ] Solaris/OmniOS CE Support
|
||||
- [ ] Kubernetes
|
||||
- [x] [Windows](./INSTALL_MANUAL_WINDOWS.md)
|
||||
|
||||
@@ -19,6 +19,25 @@ Scrutiny stores and references the devices by their `WWN` which is globally uniq
|
||||
As such, passing devices to the Scrutiny collector container using `/dev/disk/by-id/`, `/dev/disk/by-label/`, `/dev/disk/by-path/` and `/dev/disk/by-uuid/`
|
||||
paths are unnecessary, unless you'd like to ensure the docker run command never needs to change.
|
||||
|
||||
#### Force /dev/disk/by-id paths
|
||||
|
||||
Since Scrutiny uses WWN under the hood, it really doesn't care about `/dev/sd*` vs `/dev/disk/by-id/`. The problem is the interaction between docker and smartmontools when using `--device /dev/disk/by-id` paths.
|
||||
|
||||
Basically Scrutiny offloads all device detection to smartmontools, which doesn't seem to detect devices that have been passed into the docker container using `/dev/disk/by-id` paths.
|
||||
|
||||
If you must use "static" device references, you can map the host device id/uuid/wwn references to device names within the container:
|
||||
|
||||
```
|
||||
# --device=<Host Device>:<Container Device Mapping>
|
||||
|
||||
docker run ....
|
||||
--device=/dev/disk/by-id/wwn-0x5000xxxxx:/dev/sda
|
||||
--device=/dev/disk/by-id/wwn-0x5001xxxxx:/dev/sdb
|
||||
--device=/dev/disk/by-id/wwn-0x5003xxxxx:/dev/sdc
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Device Detection By Smartctl
|
||||
|
||||
@@ -61,7 +80,8 @@ using a collector config file. See [example.collector.yaml](/example.collector.y
|
||||
|
||||
> NOTE: If you use docker, you **must** pass though the RAID virtual disk to the container using `--device` (see below)
|
||||
>
|
||||
> This device may be in `/dev/*` or `/dev/bus/*`.
|
||||
> This device may be in `/dev/*` or `/dev/bus/*`.
|
||||
> If you do not see a virtual device file `/dev/bus/*` you may need to use the `--privileged` flag. See [#366 for more info](https://github.com/AnalogJ/scrutiny/issues/366#issuecomment-1253196407)
|
||||
>
|
||||
> If you're unsure, run `smartctl --scan` on your host, and pass all listed devices to the container.
|
||||
|
||||
@@ -92,7 +112,7 @@ devices:
|
||||
type:
|
||||
- aacraid,0,0,0
|
||||
- aacraid,0,0,1
|
||||
|
||||
|
||||
# HPE Smart Array example: https://github.com/AnalogJ/scrutiny/issues/213
|
||||
- device: /dev/sda
|
||||
type:
|
||||
@@ -100,8 +120,11 @@ devices:
|
||||
- 'cciss,1'
|
||||
```
|
||||
|
||||
>
|
||||
|
||||
### NVMe Drives
|
||||
As mentioned in the [README.md](/README.md), NVMe devices require both `--cap-add SYS_RAWIO` and `--cap-add SYS_ADMIN`
|
||||
|
||||
As mentioned in the [README.md](/README.md), NVMe devices require both `--cap-add SYS_RAWIO` and `--cap-add SYS_ADMIN`
|
||||
to allow smartctl permission to query your NVMe device SMART data [#26](https://github.com/AnalogJ/scrutiny/issues/26)
|
||||
|
||||
When attaching NVMe devices using `--device=/dev/nvme..`, make sure to provide the device controller (`/dev/nvme0`)
|
||||
@@ -252,10 +275,12 @@ to disable Scrutiny analysis for them. Both are non-critical, and have low-corre
|
||||
If this is effecting your drives, you'll need to do the following:
|
||||
|
||||
1. Upgrade to v0.4.13+
|
||||
2. Reset your drive status using the SQLite script in [#device-failed-but-smart--scrutiny-passed](https://github.com/AnalogJ/scrutiny/blob/master/docs/TROUBLESHOOTING_DEVICE_COLLECTOR.md#device-failed-but-smart--scrutiny-passed)
|
||||
2. Reset your drive status using the SQLite script
|
||||
in [#device-failed-but-smart--scrutiny-passed](https://github.com/AnalogJ/scrutiny/blob/master/docs/TROUBLESHOOTING_DEVICE_COLLECTOR.md#device-failed-but-smart--scrutiny-passed)
|
||||
3. Wait for (or manually start) the collector.
|
||||
|
||||
If you'd like to learn more about how the Seagate Ironwolf SMART attributes work under the hood, and how they differ from
|
||||
If you'd like to learn more about how the Seagate Ironwolf SMART attributes work under the hood, and how they differ
|
||||
from
|
||||
other drives, please read the following:
|
||||
|
||||
- http://www.users.on.net/~fzabkar/HDD/Seagate_SER_RRER_HEC.html
|
||||
@@ -263,10 +288,23 @@ other drives, please read the following:
|
||||
|
||||
## Hub & Spoke model, with multiple Hosts.
|
||||
|
||||
When deploying Scrutiny in a hub & spoke model, it can be difficult to determine exactly which node a set of devices are associated with.
|
||||
Thankfully the collector has a special `--host-id` flag (or `COLLECTOR_HOST_ID` env variable) that can be used to associate devices with a friendly host name.
|
||||

|
||||
|
||||
See the [docs/INSTALL_HUB_SPOKE.md](/docs/INSTALL_HUB_SPOKE.md) guide for more information.
|
||||
When deploying Scrutiny in a hub & spoke model, it can be difficult to determine exactly which node a set of devices are
|
||||
associated with.
|
||||
Thankfully the collector has a special `--host-id` flag (or `COLLECTOR_HOST_ID` env variable) that can be used to
|
||||
associate devices with a friendly host name.
|
||||
|
||||
The host-id is passed from the collector to the web-api when SMART device data is uploaded. There's 3 ways you can set
|
||||
the host-id:
|
||||
|
||||
- using the collector config
|
||||
file: [master/example.collector.yaml#L19-L22](https://github.com/AnalogJ/scrutiny/blob/master/example.collector.yaml?rgh-link-date=2022-05-25T15%3A08%3A56Z#L19-L22)
|
||||
- using the `--host-id` collector CLI
|
||||
argument: [master/collector/cmd/collector-metrics/collector-metrics.go#L180-L185](https://github.com/AnalogJ/scrutiny/blob/master/collector/cmd/collector-metrics/collector-metrics.go?rgh-link-date=2022-05-25T15%3A08%3A56Z#L180-L185)
|
||||
- using the `COLLECTOR_HOST_ID` environmental variable.
|
||||
|
||||
See the [docs/INSTALL_HUB_SPOKE.md](/docs/INSTALL_HUB_SPOKE.md) guide for more information.
|
||||
|
||||
## Collector DEBUG mode
|
||||
|
||||
@@ -282,3 +320,20 @@ Or if you're not using docker, you can pass CLI arguments to the collector durin
|
||||
```bash
|
||||
scrutiny-collector-metrics run --debug --log-file /tmp/collector.log
|
||||
```
|
||||
|
||||
## Collector trigger on startup
|
||||
|
||||
When the `omnibus` docker image starts up, it will automatically trigger the collector, which will populate the Scrutiny
|
||||
Webui with your disks.
|
||||
This is not the case when running the collector docker image in **hub/spoke** mode, as the collector and webui are
|
||||
running in different containers (and potentially different host machines), so
|
||||
the web container may not be ready for incoming connections. By default the container will only run the collector at the
|
||||
time specified in the cron schedule.
|
||||
|
||||
You can force the collector to run on startup using the following env variables:
|
||||
|
||||
- `-e COLLECTOR_RUN_STARTUP=true` - forces the collector to run on startup (cron will be started after the collector
|
||||
completes)
|
||||
- `-e COLLECTOR_RUN_STARTUP_SLEEP=10` - if `COLLECTOR_RUN_STARTUP` is enabled, you can use this env variable to
|
||||
configure the delay before the collector is run (default: `1` second). Used to ensure the web container has started
|
||||
successfully.
|
||||
|
||||
@@ -82,6 +82,7 @@ this usually related to either:
|
||||
variables
|
||||
- remove the `SCRUTINY_WEB=true` and `SCRUTINY_COLLECTOR=true` environmental variables. They were used by the LSIO
|
||||
image, but are unnecessary and cause issues with the official Scrutiny image.
|
||||
- Change your volume mappings to `/opt/scrutiny` from `/scrutiny`
|
||||
- Updated versions of the [LSIO Scrutiny images are broken](https://github.com/linuxserver/docker-scrutiny/issues/22),
|
||||
as they have not installed InfluxDB which is a required dependency of Scrutiny v0.4.x
|
||||
- You can revert to an earlier version of the LSIO image (`lscr.io/linuxserver/scrutiny:060ac7b8-ls34`), or just
|
||||
@@ -394,3 +395,32 @@ After running the Curl command above, you'll see a JSON response that looks like
|
||||
You must copy the token field from the JSON response, and save it in your `scrutiny.yaml` config file. After that's
|
||||
done, you can start the Scrutiny server
|
||||
|
||||
## Customize InfluxDB Admin Username & Password
|
||||
|
||||
The full set of InfluxDB configuration options are available
|
||||
in [code](https://github.com/AnalogJ/scrutiny/blob/master/webapp/backend/pkg/config/config.go?rgh-link-date=2023-01-19T16%3A23%3A40Z#L49-L51)
|
||||
.
|
||||
|
||||
During first startup Scrutiny will connect to the unprotected InfluxDB server, start the setup process (via API) using a
|
||||
username and password of `admin`:`password12345` and then create an API token of `scrutiny-default-admin-token`.
|
||||
|
||||
After that's complete, it will use the api token for all subsequent communication with InfluxDB.
|
||||
|
||||
You can configure the values for the Admin username, password and token using the config file, or env variables:
|
||||
|
||||
#### Config File Example
|
||||
|
||||
```yaml
|
||||
web:
|
||||
influxdb:
|
||||
token: 'my-custom-token'
|
||||
init_username: 'my-custom-username'
|
||||
init_password: 'my-custom-password'
|
||||
```
|
||||
|
||||
#### Environmental Variables Example
|
||||
|
||||
`SCRUTINY_WEB_INFLUXDB_TOKEN` , `SCRUTINY_WEB_INFLUXDB_INIT_USERNAME` and `SCRUTINY_WEB_INFLUXDB_INIT_PASSWORD`
|
||||
|
||||
It's safe to change the InfluxDB Admin username/password after setup has completed, only the API token is used for
|
||||
subsequent communication with InfluxDB.
|
||||
|
||||
@@ -24,3 +24,8 @@ SCRUTINY_MESSAGE - eg. "Scrutiny SMART error notification for device: %s\nFailur
|
||||
SCRUTINY_HOST_ID - (optional) eg. "my-custom-host-id"
|
||||
```
|
||||
|
||||
# Testing Notifications
|
||||
You can test that your notifications are configured correctly by posting an empty payload to the notifications health check API.
|
||||
```
|
||||
curl -X POST http://localhost:8080/api/health/notify
|
||||
```
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
@@ -31,6 +31,10 @@ devices:
|
||||
# - device: /dev/sda
|
||||
# type: 'sat'
|
||||
#
|
||||
# # example for using `-d sat,auto`, notice the square brackets (workaround for #418)
|
||||
# - device: /dev/sda
|
||||
# type: ['sat,auto']
|
||||
#
|
||||
# # example to show how to ignore a specific disk/device.
|
||||
# - device: /dev/sda
|
||||
# ignore: true
|
||||
|
||||
@@ -47,6 +47,11 @@ web:
|
||||
# org: 'my-org'
|
||||
# bucket: 'bucket'
|
||||
retention_policy: true
|
||||
# if you wish to disable TLS certificate verification,
|
||||
# when using self-signed certificates for example,
|
||||
# then uncomment the lines below and set `insecure_skip_verify: true`
|
||||
# tls:
|
||||
# insecure_skip_verify: false
|
||||
|
||||
log:
|
||||
file: '' #absolute or relative paths allowed, eg. web.log
|
||||
@@ -68,6 +73,7 @@ log:
|
||||
# - "pushbullet://api-token[/device/#channel/email]"
|
||||
# - "ifttt://key/?events=event1[,event2,...]&value1=value1&value2=value2&value3=value3"
|
||||
# - "mattermost://[username@]mattermost-host/token[/channel]"
|
||||
# - "ntfy://username:password@host:port/topic"
|
||||
# - "hangouts://chat.googleapis.com/v1/spaces/FOO/messages?key=bar&token=baz"
|
||||
# - "zulip://bot-mail:bot-key@zulip-domain/?stream=name-or-id&topic=name"
|
||||
# - "join://shoutrrr:api-key@join/?devices=device1[,device2, ...][&icon=icon][&title=title]"
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
module github.com/analogj/scrutiny
|
||||
|
||||
go 1.18
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/analogj/go-util v0.0.0-20190301173314-5295e364eb14
|
||||
github.com/containrrr/shoutrrr v0.4.4
|
||||
github.com/fatih/color v1.10.0
|
||||
github.com/containrrr/shoutrrr v0.7.1
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/gin-gonic/gin v1.6.3
|
||||
github.com/glebarez/sqlite v1.4.5
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.0.0
|
||||
github.com/golang/mock v1.4.3
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.9.0
|
||||
github.com/jaypipes/ghw v0.6.1
|
||||
github.com/mitchellh/mapstructure v1.2.2
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/samber/lo v1.25.0
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spf13/viper v1.7.0
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/spf13/viper v1.14.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/urfave/cli/v2 v2.2.0
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||
golang.org/x/sync v0.1.0
|
||||
gorm.io/gorm v1.23.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deepmap/oapi-codegen v1.8.2 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.17.2 // indirect
|
||||
@@ -35,55 +35,48 @@ require (
|
||||
github.com/go-playground/locales v0.13.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.17.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.2.0 // indirect
|
||||
github.com/golang/protobuf v1.4.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
|
||||
github.com/jaypipes/pcidb v0.5.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.4 // indirect
|
||||
github.com/json-iterator/go v1.1.9 // indirect
|
||||
github.com/klauspost/compress v1.11.7 // indirect
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
|
||||
github.com/kvz/logstreamer v0.0.0-20201023134116-02d20f4338f5 // indirect
|
||||
github.com/leodido/go-urn v1.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/nxadm/tail v1.4.6 // indirect
|
||||
github.com/onsi/ginkgo v1.14.2 // indirect
|
||||
github.com/pelletier/go-toml v1.7.0 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/spf13/afero v1.2.2 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/afero v1.9.2 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/ugorji/go/codec v1.1.7 // indirect
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
|
||||
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64 // indirect
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
||||
golang.org/x/text v0.3.5 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/protobuf v1.23.0 // indirect
|
||||
gopkg.in/ini.v1 v1.55.0 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
gosrc.io/xmpp v0.5.1 // indirect
|
||||
golang.org/x/net v0.1.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/term v0.1.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect
|
||||
modernc.org/libc v1.16.8 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.1.1 // indirect
|
||||
modernc.org/sqlite v1.17.2 // indirect
|
||||
nhooyr.io/websocket v1.8.6 // indirect
|
||||
)
|
||||
|
||||
@@ -29,11 +29,18 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
configFilePath := "/opt/scrutiny/config/scrutiny.yaml"
|
||||
configFilePathAlternative := "/opt/scrutiny/config/scrutiny.yml"
|
||||
if !utils.FileExists(configFilePath) && utils.FileExists(configFilePathAlternative) {
|
||||
configFilePath = configFilePathAlternative
|
||||
}
|
||||
|
||||
//we're going to load the config file manually, since we need to validate it.
|
||||
err = config.ReadConfig("/opt/scrutiny/config/scrutiny.yaml") // Find and read the config file
|
||||
err = config.ReadConfig(configFilePath) // Find and read the config file
|
||||
if _, ok := err.(errors.ConfigFileMissingError); ok { // Handle errors reading the config file
|
||||
//ignore "could not find config file"
|
||||
} else if err != nil {
|
||||
log.Print(color.HiRedString("CONFIG ERROR: %v", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ func (c *configuration) Init() error {
|
||||
c.SetDefault("web.influxdb.init_username", "admin")
|
||||
c.SetDefault("web.influxdb.init_password", "password12345")
|
||||
c.SetDefault("web.influxdb.token", "scrutiny-default-admin-token")
|
||||
c.SetDefault("web.influxdb.tls.insecure_skip_verify", false)
|
||||
c.SetDefault("web.influxdb.retention_policy", true)
|
||||
|
||||
//c.SetDefault("disks.include", []string{})
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
type DeviceRepo interface {
|
||||
Close() error
|
||||
HealthCheck(ctx context.Context) error
|
||||
|
||||
RegisterDevice(ctx context.Context, dev models.Device) error
|
||||
GetDevices(ctx context.Context) ([]models.Device, error)
|
||||
|
||||
@@ -2,6 +2,7 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/config"
|
||||
@@ -95,11 +96,20 @@ func NewScrutinyRepository(appConfig config.Interface, globalLogger logrus.Field
|
||||
influxdbUrl := fmt.Sprintf("%s://%s:%s", appConfig.GetString("web.influxdb.scheme"), appConfig.GetString("web.influxdb.host"), appConfig.GetString("web.influxdb.port"))
|
||||
globalLogger.Debugf("InfluxDB url: %s", influxdbUrl)
|
||||
|
||||
client := influxdb2.NewClient(influxdbUrl, appConfig.GetString("web.influxdb.token"))
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: appConfig.GetBool("web.influxdb.tls.insecure_skip_verify"),
|
||||
}
|
||||
globalLogger.Infof("InfluxDB certificate verification: %t\n", !tlsConfig.InsecureSkipVerify)
|
||||
|
||||
client := influxdb2.NewClientWithOptions(
|
||||
influxdbUrl,
|
||||
appConfig.GetString("web.influxdb.token"),
|
||||
influxdb2.DefaultOptions().SetTLSConfig(tlsConfig),
|
||||
)
|
||||
|
||||
//if !appConfig.IsSet("web.influxdb.token") {
|
||||
globalLogger.Debugf("Determine Influxdb setup status...")
|
||||
influxSetupComplete, err := InfluxSetupComplete(influxdbUrl)
|
||||
influxSetupComplete, err := InfluxSetupComplete(influxdbUrl, tlsConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check influxdb setup status - %w", err)
|
||||
}
|
||||
@@ -195,7 +205,30 @@ func (sr *scrutinyRepository) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func InfluxSetupComplete(influxEndpoint string) (bool, error) {
|
||||
func (sr *scrutinyRepository) HealthCheck(ctx context.Context) error {
|
||||
//check influxdb
|
||||
status, err := sr.influxClient.Health(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("influxdb healthcheck failed: %w", err)
|
||||
}
|
||||
if status.Status != "pass" {
|
||||
return fmt.Errorf("influxdb healthcheckf failed: status=%s", status.Status)
|
||||
}
|
||||
|
||||
//check sqlite db.
|
||||
database, err := sr.gormClient.DB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("sqlite healthcheck failed: %w", err)
|
||||
}
|
||||
err = database.Ping()
|
||||
if err != nil {
|
||||
return fmt.Errorf("sqlite healthcheck failed during ping: %w", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func InfluxSetupComplete(influxEndpoint string, tlsConfig *tls.Config) (bool, error) {
|
||||
influxUri, err := url.Parse(influxEndpoint)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -205,7 +238,8 @@ func InfluxSetupComplete(influxEndpoint string) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
res, err := http.Get(influxUri.String())
|
||||
client := &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}}
|
||||
res, err := client.Get(influxUri.String())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -325,6 +325,12 @@ func (sr *scrutinyRepository) Migrate(ctx context.Context) error {
|
||||
SettingDataType: "bool",
|
||||
SettingValueBool: false,
|
||||
},
|
||||
{
|
||||
SettingKeyName: "line_stroke",
|
||||
SettingKeyDescription: "Temperature chart line stroke ('smooth' | 'straight' | 'stepline')",
|
||||
SettingDataType: "string",
|
||||
SettingValueString: "smooth",
|
||||
},
|
||||
|
||||
{
|
||||
SettingKeyName: "metrics.notify_level",
|
||||
@@ -348,6 +354,21 @@ func (sr *scrutinyRepository) Migrate(ctx context.Context) error {
|
||||
return tx.Create(&defaultSettings).Error
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "m20221115214900", // add line_stroke setting.
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
//add line_stroke setting default.
|
||||
var defaultSettings = []m20220716214900.Setting{
|
||||
{
|
||||
SettingKeyName: "line_stroke",
|
||||
SettingKeyDescription: "Temperature chart line stroke ('smooth' | 'straight' | 'stepline')",
|
||||
SettingDataType: "string",
|
||||
SettingValueString: "smooth",
|
||||
},
|
||||
}
|
||||
return tx.Create(&defaultSettings).Error
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if err := m.Migrate(); err != nil {
|
||||
|
||||
@@ -17,6 +17,10 @@ func (sr *scrutinyRepository) SaveSmartTemperature(ctx context.Context, wwn stri
|
||||
if len(collectorSmartData.AtaSctTemperatureHistory.Table) > 0 {
|
||||
|
||||
for ndx, temp := range collectorSmartData.AtaSctTemperatureHistory.Table {
|
||||
//temp value may be null, we must skip/ignore them. See #393
|
||||
if temp == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
minutesOffset := collectorSmartData.AtaSctTemperatureHistory.LoggingIntervalMinutes * int64(ndx) * 60
|
||||
smartTemp := measurements.SmartTemperature{
|
||||
|
||||
@@ -14,6 +14,7 @@ type Settings struct {
|
||||
DashboardSort string `json:"dashboard_sort" mapstructure:"dashboard_sort"`
|
||||
TemperatureUnit string `json:"temperature_unit" mapstructure:"temperature_unit"`
|
||||
FileSizeSIUnits bool `json:"file_size_si_units" mapstructure:"file_size_si_units"`
|
||||
LineStroke string `json:"line_stroke" mapstructure:"line_stroke"`
|
||||
|
||||
Metrics struct {
|
||||
NotifyLevel int `json:"notify_level" mapstructure:"notify_level"`
|
||||
|
||||
@@ -5,6 +5,13 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/analogj/go-util/utils"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/config"
|
||||
@@ -15,12 +22,6 @@ import (
|
||||
shoutrrrTypes "github.com/containrrr/shoutrrr/pkg/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const NotifyFailureTypeEmailTest = "EmailTest"
|
||||
@@ -386,6 +387,9 @@ func (n *Notify) GenShoutrrrNotificationParams(shoutrrrUrl string) (string, *sho
|
||||
case "join":
|
||||
(*params)["title"] = subject
|
||||
(*params)["icon"] = logoUrl
|
||||
case "ntfy":
|
||||
(*params)["title"] = subject
|
||||
(*params)["icon"] = logoUrl
|
||||
case "opsgenie":
|
||||
(*params)["title"] = subject
|
||||
case "pushbullet":
|
||||
|
||||
@@ -2,4 +2,4 @@ package version
|
||||
|
||||
// VERSION is the app-global version string, which will be replaced with a
|
||||
// new value during packaging
|
||||
const VERSION = "0.5.0"
|
||||
const VERSION = "0.7.1"
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func HealthCheck(c *gin.Context) {
|
||||
logger := c.MustGet("LOGGER").(*logrus.Entry)
|
||||
deviceRepo := c.MustGet("DEVICE_REPOSITORY").(database.DeviceRepo)
|
||||
logger.Infof("Checking Influxdb & Sqlite health")
|
||||
|
||||
//check sqlite and influxdb health
|
||||
err := deviceRepo.HealthCheck(c)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred during healthcheck", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
//TODO:
|
||||
// check if the /web folder is populated.
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
})
|
||||
}
|
||||
@@ -34,15 +34,7 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
|
||||
{
|
||||
api := base.Group("/api")
|
||||
{
|
||||
api.GET("/health", func(c *gin.Context) {
|
||||
//TODO:
|
||||
// check if the /web folder is populated.
|
||||
// check if access to influxdb
|
||||
// check if access to sqlitedb.
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
})
|
||||
})
|
||||
api.GET("/health", handler.HealthCheck)
|
||||
api.POST("/health/notify", handler.SendTestNotification) //check if notifications are configured correctly
|
||||
|
||||
api.POST("/devices/register", handler.RegisterDevices) //used by Collector to register new devices and retrieve filtered list
|
||||
|
||||
@@ -103,6 +103,7 @@ func (suite *ServerTestSuite) TestHealthRoute() {
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.token").Return("my-super-secret-auth-token").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.tls.insecure_skip_verify").Return(false).AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes()
|
||||
if _, isGithubActions := os.LookupEnv("GITHUB_ACTIONS"); isGithubActions {
|
||||
// when running test suite in github actions, we run an influxdb service as a sidecar.
|
||||
@@ -145,6 +146,7 @@ func (suite *ServerTestSuite) TestRegisterDevicesRoute() {
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.token").Return("my-super-secret-auth-token").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.tls.insecure_skip_verify").Return(false).AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes()
|
||||
if _, isGithubActions := os.LookupEnv("GITHUB_ACTIONS"); isGithubActions {
|
||||
// when running test suite in github actions, we run an influxdb service as a sidecar.
|
||||
@@ -187,6 +189,7 @@ func (suite *ServerTestSuite) TestUploadDeviceMetricsRoute() {
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.token").Return("my-super-secret-auth-token").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.tls.insecure_skip_verify").Return(false).AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes()
|
||||
if _, isGithubActions := os.LookupEnv("GITHUB_ACTIONS"); isGithubActions {
|
||||
// when running test suite in github actions, we run an influxdb service as a sidecar.
|
||||
@@ -244,6 +247,7 @@ func (suite *ServerTestSuite) TestPopulateMultiple() {
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.token").Return("my-super-secret-auth-token").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.tls.insecure_skip_verify").Return(false).AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes()
|
||||
if _, isGithubActions := os.LookupEnv("GITHUB_ACTIONS"); isGithubActions {
|
||||
// when running test suite in github actions, we run an influxdb service as a sidecar.
|
||||
@@ -342,6 +346,7 @@ func (suite *ServerTestSuite) TestSendTestNotificationRoute_WebhookFailure() {
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.token").Return("my-super-secret-auth-token").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.tls.insecure_skip_verify").Return(false).AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes()
|
||||
fakeConfig.EXPECT().GetStringSlice("notify.urls").AnyTimes().Return([]string{"https://unroutable.domain.example.asdfghj"})
|
||||
fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.notify_level", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsNotifyLevelFail))
|
||||
@@ -387,6 +392,7 @@ func (suite *ServerTestSuite) TestSendTestNotificationRoute_ScriptFailure() {
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.token").Return("my-super-secret-auth-token").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.tls.insecure_skip_verify").Return(false).AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes()
|
||||
fakeConfig.EXPECT().GetStringSlice("notify.urls").AnyTimes().Return([]string{"script:///missing/path/on/disk"})
|
||||
fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.notify_level", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsNotifyLevelFail))
|
||||
@@ -432,6 +438,7 @@ func (suite *ServerTestSuite) TestSendTestNotificationRoute_ScriptSuccess() {
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.token").Return("my-super-secret-auth-token").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.tls.insecure_skip_verify").Return(false).AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes()
|
||||
fakeConfig.EXPECT().GetStringSlice("notify.urls").AnyTimes().Return([]string{"script:///usr/bin/env"})
|
||||
fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.notify_level", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsNotifyLevelFail))
|
||||
@@ -477,6 +484,7 @@ func (suite *ServerTestSuite) TestSendTestNotificationRoute_ShoutrrrFailure() {
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.token").Return("my-super-secret-auth-token").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.tls.insecure_skip_verify").Return(false).AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes()
|
||||
fakeConfig.EXPECT().GetStringSlice("notify.urls").AnyTimes().Return([]string{"discord://invalidtoken@channel"})
|
||||
fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.notify_level", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsNotifyLevelFail))
|
||||
@@ -521,6 +529,7 @@ func (suite *ServerTestSuite) TestGetDevicesSummaryRoute_Nvme() {
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.token").Return("my-super-secret-auth-token").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.tls.insecure_skip_verify").Return(false).AnyTimes()
|
||||
fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes()
|
||||
fakeConfig.EXPECT().GetStringSlice("notify.urls").AnyTimes().Return([]string{})
|
||||
fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.notify_level", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsNotifyLevelFail))
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
@@ -91,7 +90,6 @@
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"defaultConfiguration": "production",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
|
||||
@@ -10,10 +10,10 @@ module.exports = function (config)
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client : {
|
||||
client: {
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
@@ -21,13 +21,13 @@ module.exports = function (config)
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
reporters : ['progress', 'kjhtml'],
|
||||
port : 9876,
|
||||
colors : true,
|
||||
logLevel : config.LOG_INFO,
|
||||
autoWatch : true,
|
||||
browsers : ['Chrome'],
|
||||
singleRun : false,
|
||||
restartOnFileChange : true
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
||||
|
||||
Generated
+8291
-27143
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@
|
||||
"start": "ng serve --open",
|
||||
"start:mem": "node --max_old_space_size=6144 ./node_modules/@angular/cli/bin/ng serve --open",
|
||||
"build": "ng build",
|
||||
"build:prod": "ng build --prod",
|
||||
"build:prod": "ng build --configuration production",
|
||||
"build:prod:mem": "node --max_old_space_size=6144 ./node_modules/@angular/cli/bin/ng build --prod",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
@@ -20,66 +20,55 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "9.1.4",
|
||||
"@angular/cdk": "9.2.2",
|
||||
"@angular/common": "9.1.4",
|
||||
"@angular/compiler": "9.1.4",
|
||||
"@angular/core": "9.1.4",
|
||||
"@angular/forms": "9.1.4",
|
||||
"@angular/material": "9.2.2",
|
||||
"@angular/material-moment-adapter": "9.2.2",
|
||||
"@angular/platform-browser": "9.1.4",
|
||||
"@angular/platform-browser-dynamic": "9.1.4",
|
||||
"@angular/router": "9.1.4",
|
||||
"@fullcalendar/angular": "4.4.5-beta",
|
||||
"@fullcalendar/core": "4.4.0",
|
||||
"@fullcalendar/daygrid": "4.4.0",
|
||||
"@fullcalendar/interaction": "4.4.0",
|
||||
"@fullcalendar/list": "4.4.0",
|
||||
"@fullcalendar/moment": "4.4.0",
|
||||
"@fullcalendar/rrule": "4.4.0",
|
||||
"@fullcalendar/timegrid": "4.4.0",
|
||||
"@types/humanize-duration": "^3.18.1",
|
||||
"apexcharts": "3.19.2",
|
||||
"crypto-js": "3.3.0",
|
||||
"highlight.js": "10.0.1",
|
||||
"humanize-duration": "^3.24.0",
|
||||
"lodash": "4.17.15",
|
||||
"moment": "2.24.0",
|
||||
"ng-apexcharts": "1.5.12",
|
||||
"ngx-markdown": "9.0.0",
|
||||
"ngx-quill": "9.1.0",
|
||||
"perfect-scrollbar": "1.5.0",
|
||||
"quill": "1.3.7",
|
||||
"rrule": "2.6.4",
|
||||
"rxjs": "6.5.5",
|
||||
"tslib": "1.11.1",
|
||||
"web-animations-js": "2.3.2",
|
||||
"zone.js": "0.10.3"
|
||||
"@angular/animations": "v13-lts",
|
||||
"@angular/cdk": "v13-lts",
|
||||
"@angular/common": "v13-lts",
|
||||
"@angular/compiler": "v13-lts",
|
||||
"@angular/core": "v13-lts",
|
||||
"@angular/forms": "v13-lts",
|
||||
"@angular/material": "v13-lts",
|
||||
"@angular/material-moment-adapter": "v13-lts",
|
||||
"@angular/platform-browser": "v13-lts",
|
||||
"@angular/platform-browser-dynamic": "v13-lts",
|
||||
"@angular/router": "v13-lts",
|
||||
"@types/humanize-duration": "^3.27.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
"highlight.js": "^11.6.0",
|
||||
"humanize-duration": "^3.27.3",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"ng-apexcharts": "^1.7.4",
|
||||
"ngx-markdown": "^13.1.0",
|
||||
"perfect-scrollbar": "^1.5.5",
|
||||
"quill": "^1.3.7",
|
||||
"rrule": "^2.7.1",
|
||||
"rxjs": "^7.5.7",
|
||||
"tslib": "^2.4.1",
|
||||
"web-animations-js": "^2.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "0.901.4",
|
||||
"@angular/cli": "9.1.4",
|
||||
"@angular/compiler-cli": "9.1.4",
|
||||
"@angular/language-service": "9.1.4",
|
||||
"@types/crypto-js": "3.1.45",
|
||||
"@types/highlight.js": "9.12.3",
|
||||
"@types/jasmine": "3.5.10",
|
||||
"@types/jasminewd2": "2.0.8",
|
||||
"@types/lodash": "4.14.150",
|
||||
"@types/node": "12.12.37",
|
||||
"codelyzer": "5.2.2",
|
||||
"jasmine-core": "3.5.0",
|
||||
"jasmine-spec-reporter": "4.2.1",
|
||||
"karma": "5.0.4",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-coverage-istanbul-reporter": "2.1.1",
|
||||
"karma-jasmine": "3.0.3",
|
||||
"karma-jasmine-html-reporter": "1.5.3",
|
||||
"protractor": "5.4.4",
|
||||
"tailwindcss": "1.4.4",
|
||||
"ts-node": "8.3.0",
|
||||
"tslint": "6.1.2",
|
||||
"typescript": "3.8.3"
|
||||
"@angular-devkit/build-angular": "v13-lts",
|
||||
"@angular/cli": "v13-lts",
|
||||
"@angular/compiler-cli": "v13-lts",
|
||||
"@angular/language-service": "v13-lts",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/highlight.js": "^10.1.0",
|
||||
"@types/jasmine": "^4.3.0",
|
||||
"@types/jasminewd2": "^2.0.10",
|
||||
"@types/lodash": "^4.14.188",
|
||||
"@types/node": "^18.11.9",
|
||||
"codelyzer": "^6.0.2",
|
||||
"jasmine-core": "^4.5.0",
|
||||
"jasmine-spec-reporter": "^7.0.0",
|
||||
"karma": "^6.4.1",
|
||||
"karma-chrome-launcher": "^3.1.1",
|
||||
"karma-coverage": "^2.2.0",
|
||||
"karma-jasmine": "^5.1.0",
|
||||
"karma-jasmine-html-reporter": "^2.0.0",
|
||||
"protractor": "^7.0.0",
|
||||
"tailwindcss": "^3.2.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"tslint": "^6.1.3",
|
||||
"typescript": "^4.6.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export class TreoDateRangeComponent implements ControlValueAccessor, OnInit, OnD
|
||||
private _timeFormat: string;
|
||||
private _timeRange: boolean;
|
||||
private readonly _timeRegExp: RegExp;
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
@@ -31,7 +31,7 @@ export class TreoMessageComponent implements OnInit, OnDestroy
|
||||
private _dismissed: null | boolean;
|
||||
private _showIcon: boolean;
|
||||
private _type: TreoMessageType;
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ export class TreoHorizontalNavigationBasicItemComponent implements OnInit, OnDes
|
||||
|
||||
// Private
|
||||
private _treoHorizontalNavigationComponent: TreoHorizontalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ export class TreoHorizontalNavigationBranchItemComponent implements OnInit, OnDe
|
||||
|
||||
// Private
|
||||
private _treoHorizontalNavigationComponent: TreoHorizontalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ export class TreoHorizontalNavigationDividerItemComponent implements OnInit, OnD
|
||||
|
||||
// Private
|
||||
private _treoHorizontalNavigationComponent: TreoHorizontalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ export class TreoHorizontalNavigationSpacerItemComponent implements OnInit, OnDe
|
||||
|
||||
// Private
|
||||
private _treoHorizontalNavigationComponent: TreoHorizontalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
@@ -23,7 +23,7 @@ export class TreoHorizontalNavigationComponent implements OnInit, OnDestroy
|
||||
|
||||
// Private
|
||||
private _navigation: TreoNavigationItem[];
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ export class TreoVerticalNavigationAsideItemComponent implements OnInit, OnDestr
|
||||
|
||||
// Private
|
||||
private _treoVerticalNavigationComponent: TreoVerticalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ export class TreoVerticalNavigationBasicItemComponent implements OnInit, OnDestr
|
||||
|
||||
// Private
|
||||
private _treoVerticalNavigationComponent: TreoVerticalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ export class TreoVerticalNavigationCollapsableItemComponent implements OnInit, O
|
||||
|
||||
// Private
|
||||
private _treoVerticalNavigationComponent: TreoVerticalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ export class TreoVerticalNavigationDividerItemComponent implements OnInit, OnDes
|
||||
|
||||
// Private
|
||||
private _treoVerticalNavigationComponent: TreoVerticalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ export class TreoVerticalNavigationGroupItemComponent implements OnInit, OnDestr
|
||||
|
||||
// Private
|
||||
private _treoVerticalNavigationComponent: TreoVerticalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ export class TreoVerticalNavigationSpacerItemComponent implements OnInit, OnDest
|
||||
|
||||
// Private
|
||||
private _treoVerticalNavigationComponent: TreoVerticalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
@@ -65,7 +65,7 @@ export class TreoVerticalNavigationComponent implements OnInit, AfterViewInit, O
|
||||
private _position: TreoVerticalNavigationPosition;
|
||||
private _scrollStrategy: ScrollStrategy;
|
||||
private _transparentOverlay: boolean | '';
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
@HostBinding('class.treo-vertical-navigation-animations-enabled')
|
||||
private _animationsEnabled: boolean;
|
||||
|
||||
@@ -12,7 +12,7 @@ export class TreoAutogrowDirective implements OnInit, OnDestroy
|
||||
|
||||
// Private
|
||||
private _padding: number;
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
@@ -24,7 +24,7 @@ export class TreoScrollbarDirective implements OnInit, OnDestroy
|
||||
private _animation: number | null;
|
||||
private _enabled: boolean;
|
||||
private _options: any;
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
@@ -20,7 +20,7 @@ export class TreoMockApiModule
|
||||
*
|
||||
* @param mockDataServices
|
||||
*/
|
||||
static forRoot(mockDataServices: any[]): ModuleWithProviders
|
||||
static forRoot(mockDataServices: any[]): ModuleWithProviders<TreoMockApiModule>
|
||||
{
|
||||
return {
|
||||
ngModule : TreoMockApiModule,
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
|
||||
// 6. Overrides
|
||||
@import 'overrides/angular-material';
|
||||
@import 'overrides/fullcalendar';
|
||||
@import 'overrides/highlightjs';
|
||||
@import 'overrides/perfect-scrollbar';
|
||||
@import 'overrides/quill';
|
||||
|
||||
@@ -1,878 +0,0 @@
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ FullCalendar overrides
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
.fc {
|
||||
|
||||
.fc-view-container {
|
||||
|
||||
// Day Grid - Month view
|
||||
.fc-view.fc-dayGridMonth-view {
|
||||
|
||||
.fc-head {
|
||||
|
||||
> tr > .fc-head-container {
|
||||
border: none;
|
||||
|
||||
.fc-row {
|
||||
|
||||
.fc-day-header {
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-body {
|
||||
|
||||
> tr > .fc-widget-content {
|
||||
border: none;
|
||||
|
||||
.fc-day-grid {
|
||||
|
||||
.fc-week {
|
||||
|
||||
.fc-content-skeleton {
|
||||
|
||||
.fc-day-top {
|
||||
text-align: center;
|
||||
|
||||
&.fc-other-month {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.fc-day-number {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 21px;
|
||||
margin: 4px 0;
|
||||
font-size: 12px;
|
||||
border-radius: 50%;
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fc-event-container {
|
||||
|
||||
.fc-day-grid-event {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
min-height: 22px;
|
||||
max-height: 22px;
|
||||
margin: 0 6px 4px 6px;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
@include treo-breakpoint('xs') {
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-more {
|
||||
padding: 0 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
|
||||
@include treo-breakpoint('xs') {
|
||||
padding: 0 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-highlight-skeleton {
|
||||
|
||||
.fc-highlight {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-popover {
|
||||
|
||||
&.fc-more-popover {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
@include treo-elevation('2xl');
|
||||
|
||||
.fc-header {
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
max-height: 32px;
|
||||
padding: 0 8px;
|
||||
|
||||
.fc-title {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.fc-body {
|
||||
max-height: 160px;
|
||||
overflow: hidden auto;
|
||||
|
||||
.fc-event-container {
|
||||
padding: 8px;
|
||||
|
||||
.fc-day-grid-event {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
min-height: 22px;
|
||||
max-height: 22px;
|
||||
margin: 0 0 6px 0;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Time Grid - Week view
|
||||
.fc-view.fc-timeGridWeek-view {
|
||||
|
||||
.fc-head {
|
||||
|
||||
> tr > .fc-head-container {
|
||||
border: none;
|
||||
|
||||
.fc-row {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
}
|
||||
|
||||
.fc-day-header {
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
&.fc-weekday {
|
||||
padding-top: 16px;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.055em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&.fc-date {
|
||||
padding-bottom: 12px;
|
||||
font-size: 26px;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-body {
|
||||
|
||||
> tr > .fc-widget-content {
|
||||
border: none;
|
||||
|
||||
.fc-day-grid {
|
||||
|
||||
.fc-row {
|
||||
min-height: 0;
|
||||
|
||||
.fc-bg {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.fc-content-skeleton {
|
||||
padding-bottom: 0;
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
}
|
||||
|
||||
.fc-event-container {
|
||||
|
||||
.fc-day-grid-event {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
min-height: 22px;
|
||||
max-height: 22px;
|
||||
margin: 0 6px 6px 6px;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-divider {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.fc-time-grid {
|
||||
|
||||
.fc-bg {
|
||||
|
||||
.fc-axis {
|
||||
border: none;
|
||||
width: 48px !important;
|
||||
|
||||
+ .fc-day {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-slats {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
height: 48px;
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
width: 48px;
|
||||
min-width: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-content-skeleton {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
}
|
||||
|
||||
.fc-event-container {
|
||||
margin: 0 12px 0 0;
|
||||
|
||||
.fc-time-grid-event {
|
||||
display: flex;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
.fc-time,
|
||||
.fc-title {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Time Grid - Day view
|
||||
.fc-view.fc-timeGridDay-view {
|
||||
|
||||
.fc-head {
|
||||
|
||||
> tr > .fc-head-container {
|
||||
border: none;
|
||||
|
||||
.fc-row {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
}
|
||||
|
||||
.fc-day-header {
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
&.fc-weekday {
|
||||
padding-top: 16px;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.055em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&.fc-date {
|
||||
padding-bottom: 12px;
|
||||
font-size: 26px;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-body {
|
||||
|
||||
> tr > .fc-widget-content {
|
||||
border: none;
|
||||
|
||||
.fc-day-grid {
|
||||
|
||||
.fc-row {
|
||||
min-height: 0;
|
||||
|
||||
.fc-bg {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.fc-content-skeleton {
|
||||
padding-bottom: 0;
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
}
|
||||
|
||||
.fc-event-container {
|
||||
|
||||
.fc-day-grid-event {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
min-height: 22px;
|
||||
max-height: 22px;
|
||||
margin: 0 6px 6px 6px;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-divider {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.fc-time-grid {
|
||||
|
||||
.fc-bg {
|
||||
|
||||
.fc-axis {
|
||||
border: none;
|
||||
width: 48px !important;
|
||||
|
||||
+ .fc-day {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-slats {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
height: 48px;
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
width: 48px;
|
||||
min-width: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-content-skeleton {
|
||||
|
||||
.fc-axis {
|
||||
width: 48px !important;
|
||||
}
|
||||
|
||||
.fc-event-container {
|
||||
margin: 0 12px 0 0;
|
||||
|
||||
.fc-time-grid-event {
|
||||
display: flex;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
.fc-time,
|
||||
.fc-title {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List - Year view
|
||||
.fc-view.fc-listYear-view {
|
||||
border: none;
|
||||
|
||||
.fc-list-table {
|
||||
|
||||
.fc-list-heading {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fc-list-item {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
|
||||
td {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
height: 48px;
|
||||
min-height: 48px;
|
||||
padding: 0 8px;
|
||||
border-width: 0 0 1px 0;
|
||||
|
||||
&.fc-list-item-date {
|
||||
order: 1;
|
||||
padding-left: 16px;
|
||||
width: 120px;
|
||||
min-width: 120px;
|
||||
max-width: 120px;
|
||||
|
||||
@include treo-breakpoint('xs') {
|
||||
width: 100px;
|
||||
min-width: 100px;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
span {
|
||||
|
||||
&:first-child {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-right: 8px;
|
||||
width: 32px;
|
||||
min-width: 32px;
|
||||
max-width: 32px;
|
||||
font-size: 18px;
|
||||
|
||||
@include treo-breakpoint('xs') {
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
+ span {
|
||||
display: flex;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.055em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.fc-list-item-time {
|
||||
flex: 0 0 auto;
|
||||
order: 3;
|
||||
width: 160px;
|
||||
min-width: 160px;
|
||||
max-width: 160px;
|
||||
|
||||
@include treo-breakpoint('xs') {
|
||||
width: 120px;
|
||||
min-width: 120px;
|
||||
max-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
&.fc-list-item-marker {
|
||||
flex: 0 0 auto;
|
||||
order: 2;
|
||||
|
||||
.fc-event-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&.fc-list-item-title {
|
||||
flex: 1 1 auto;
|
||||
order: 4;
|
||||
padding-right: 24px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Day grid event - Dragging
|
||||
.fc-day-grid-event {
|
||||
|
||||
&.fc-dragging,
|
||||
&.fc-resizing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
min-height: 22px;
|
||||
max-height: 22px;
|
||||
margin: 0 6px 4px 6px;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Theming
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
@include treo-theme {
|
||||
|
||||
$background: map-get($theme, background);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$primary: map-get($theme, primary);
|
||||
|
||||
.fc {
|
||||
|
||||
.fc-view-container {
|
||||
|
||||
// Day Grid - Month view
|
||||
.fc-view.fc-dayGridMonth-view {
|
||||
|
||||
.fc-head {
|
||||
|
||||
> tr > .fc-head-container {
|
||||
|
||||
.fc-row {
|
||||
|
||||
.fc-day-header {
|
||||
border-color: map-get($foreground, divider);
|
||||
|
||||
span {
|
||||
color: map-get($foreground, secondary-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-body {
|
||||
|
||||
> tr > .fc-widget-content {
|
||||
|
||||
.fc-day-grid {
|
||||
|
||||
.fc-week {
|
||||
|
||||
.fc-bg {
|
||||
|
||||
.fc-day {
|
||||
border-color: map-get($foreground, divider);
|
||||
|
||||
&.fc-today {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-content-skeleton {
|
||||
|
||||
.fc-day-top {
|
||||
|
||||
&.fc-other-month {
|
||||
|
||||
.fc-day-number {
|
||||
color: map-get($foreground, hint-text);
|
||||
}
|
||||
}
|
||||
|
||||
&.fc-today {
|
||||
|
||||
.fc-day-number {
|
||||
background: map-get($primary, default);
|
||||
color: map-get($primary, default-contrast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-more {
|
||||
color: map-get($foreground, secondary-text);
|
||||
}
|
||||
}
|
||||
|
||||
.fc-highlight-skeleton {
|
||||
|
||||
.fc-highlight {
|
||||
background: treo-color('cool-gray', 100);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-popover {
|
||||
background: map-get($background, card);
|
||||
|
||||
&.fc-more-popover {
|
||||
|
||||
.fc-header {
|
||||
background: map-get($background, hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Time Grid - Week view
|
||||
.fc-view.fc-timeGridWeek-view {
|
||||
|
||||
.fc-head {
|
||||
|
||||
> tr > .fc-head-container {
|
||||
|
||||
.fc-row {
|
||||
|
||||
.fc-axis {
|
||||
border-color: map-get($foreground, divider);
|
||||
}
|
||||
|
||||
.fc-day-header {
|
||||
border-color: map-get($foreground, divider);
|
||||
|
||||
span {
|
||||
color: map-get($foreground, secondary-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-body {
|
||||
|
||||
> tr > .fc-widget-content {
|
||||
border: none;
|
||||
|
||||
.fc-day-grid {
|
||||
|
||||
.fc-bg {
|
||||
|
||||
.fc-axis {
|
||||
border-color: map-get($foreground, divider);
|
||||
}
|
||||
|
||||
.fc-day {
|
||||
border-color: map-get($foreground, divider);
|
||||
|
||||
&.fc-today {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-divider {
|
||||
background: map-get($foreground, divider);
|
||||
}
|
||||
|
||||
.fc-time-grid {
|
||||
|
||||
.fc-bg {
|
||||
|
||||
.fc-day {
|
||||
border-color: map-get($foreground, divider);
|
||||
|
||||
&.fc-today {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-slats {
|
||||
|
||||
.fc-time {
|
||||
border-color: map-get($foreground, divider);
|
||||
}
|
||||
|
||||
.fc-widget-content {
|
||||
border-color: map-get($foreground, divider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Time Grid - Day view
|
||||
.fc-view.fc-timeGridDay-view {
|
||||
|
||||
.fc-head {
|
||||
|
||||
> tr > .fc-head-container {
|
||||
|
||||
.fc-row {
|
||||
|
||||
.fc-axis {
|
||||
border-color: map-get($foreground, divider);
|
||||
}
|
||||
|
||||
.fc-day-header {
|
||||
border-color: map-get($foreground, divider);
|
||||
|
||||
span {
|
||||
color: map-get($foreground, secondary-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-body {
|
||||
|
||||
> tr > .fc-widget-content {
|
||||
border: none;
|
||||
|
||||
.fc-day-grid {
|
||||
|
||||
.fc-bg {
|
||||
|
||||
.fc-axis {
|
||||
border-color: map-get($foreground, divider);
|
||||
}
|
||||
|
||||
.fc-day {
|
||||
border-color: map-get($foreground, divider);
|
||||
|
||||
&.fc-today {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-divider {
|
||||
background: map-get($foreground, divider);
|
||||
}
|
||||
|
||||
.fc-time-grid {
|
||||
|
||||
.fc-bg {
|
||||
|
||||
.fc-day {
|
||||
border-color: map-get($foreground, divider);
|
||||
|
||||
&.fc-today {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-slats {
|
||||
|
||||
.fc-time {
|
||||
border-color: map-get($foreground, divider);
|
||||
}
|
||||
|
||||
.fc-widget-content {
|
||||
border-color: map-get($foreground, divider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List - Year view
|
||||
.fc-view.fc-listYear-view {
|
||||
|
||||
.fc-list-table {
|
||||
|
||||
.fc-list-item {
|
||||
|
||||
&:hover {
|
||||
|
||||
td {
|
||||
background-color: map-get($background, hover);
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
border-color: map-get($foreground, divider);
|
||||
|
||||
&.fc-list-item-date {
|
||||
|
||||
> span {
|
||||
|
||||
span {
|
||||
|
||||
&:first-child {
|
||||
|
||||
+ span {
|
||||
color: map-get($foreground, secondary-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ export type DashboardSort = 'status' | 'title' | 'age'
|
||||
|
||||
export type TemperatureUnit = 'celsius' | 'fahrenheit'
|
||||
|
||||
export type LineStroke = 'smooth' | 'straight' | 'stepline'
|
||||
|
||||
|
||||
export enum MetricsNotifyLevel {
|
||||
Warn = 1,
|
||||
@@ -45,6 +47,8 @@ export interface AppConfig {
|
||||
|
||||
file_size_si_units?: boolean;
|
||||
|
||||
line_stroke?: LineStroke;
|
||||
|
||||
// Settings from Scrutiny API
|
||||
|
||||
metrics?: {
|
||||
@@ -73,6 +77,8 @@ export const appConfig: AppConfig = {
|
||||
temperature_unit: 'celsius',
|
||||
file_size_si_units: false,
|
||||
|
||||
line_stroke: 'smooth',
|
||||
|
||||
metrics: {
|
||||
notify_level: MetricsNotifyLevel.Fail,
|
||||
status_filter_attributes: MetricsStatusFilterAttributes.All,
|
||||
|
||||
@@ -19,7 +19,7 @@ export class ScrutinyConfigModule {
|
||||
*
|
||||
* @param config
|
||||
*/
|
||||
static forRoot(config: any): ModuleWithProviders {
|
||||
static forRoot(config: any): ModuleWithProviders<ScrutinyConfigModule> {
|
||||
return {
|
||||
ngModule: ScrutinyConfigModule,
|
||||
providers: [
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ export class DashboardDeviceComponent implements OnInit {
|
||||
|
||||
config: AppConfig;
|
||||
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
readonly humanizeDuration = humanizeDuration;
|
||||
|
||||
|
||||
+11
@@ -53,6 +53,17 @@
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mt-5 gt-md:flex-row">
|
||||
<mat-form-field class="flex-auto gt-xs:pr-3 gt-md:pr-3">
|
||||
<mat-label>Line stroke</mat-label>
|
||||
<mat-select [(ngModel)]="lineStroke">
|
||||
<mat-option value="smooth">Smooth</mat-option>
|
||||
<mat-option value="straight">Straight</mat-option>
|
||||
<mat-option value="stepline">Stepline</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mt-5 gt-md:flex-row">
|
||||
<mat-form-field class="flex-auto gt-xs:pr-3 gt-md:pr-3">
|
||||
<mat-label>Device Status - Thresholds</mat-label>
|
||||
|
||||
+5
-1
@@ -6,6 +6,7 @@ import {
|
||||
MetricsStatusFilterAttributes,
|
||||
MetricsStatusThreshold,
|
||||
TemperatureUnit,
|
||||
LineStroke,
|
||||
Theme
|
||||
} from 'app/core/config/app.config';
|
||||
import {ScrutinyConfigService} from 'app/core/config/scrutiny-config.service';
|
||||
@@ -23,12 +24,13 @@ export class DashboardSettingsComponent implements OnInit {
|
||||
dashboardSort: string;
|
||||
temperatureUnit: string;
|
||||
fileSizeSIUnits: boolean;
|
||||
lineStroke: string;
|
||||
theme: string;
|
||||
statusThreshold: number;
|
||||
statusFilterAttributes: number;
|
||||
|
||||
// Private
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
constructor(
|
||||
private _configService: ScrutinyConfigService,
|
||||
@@ -48,6 +50,7 @@ export class DashboardSettingsComponent implements OnInit {
|
||||
this.dashboardSort = config.dashboard_sort;
|
||||
this.temperatureUnit = config.temperature_unit;
|
||||
this.fileSizeSIUnits = config.file_size_si_units;
|
||||
this.lineStroke = config.line_stroke;
|
||||
this.theme = config.theme;
|
||||
|
||||
this.statusFilterAttributes = config.metrics.status_filter_attributes;
|
||||
@@ -63,6 +66,7 @@ export class DashboardSettingsComponent implements OnInit {
|
||||
dashboard_sort: this.dashboardSort as DashboardSort,
|
||||
temperature_unit: this.temperatureUnit as TemperatureUnit,
|
||||
file_size_si_units: this.fileSizeSIUnits,
|
||||
line_stroke: this.lineStroke as LineStroke,
|
||||
theme: this.theme as Theme,
|
||||
metrics: {
|
||||
status_filter_attributes: this.statusFilterAttributes as MetricsStatusFilterAttributes,
|
||||
|
||||
@@ -35,7 +35,7 @@ export class SearchComponent implements OnInit, OnDestroy
|
||||
// Private
|
||||
private _appearance: 'basic' | 'bar';
|
||||
private _opened: boolean;
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
@@ -21,7 +21,7 @@ export class LayoutComponent implements OnInit, OnDestroy {
|
||||
theme: Theme;
|
||||
|
||||
// Private
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
private systemPrefersDark: boolean;
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Subject } from 'rxjs';
|
||||
export class EmptyLayoutComponent implements OnInit, OnDestroy
|
||||
{
|
||||
// Private
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
@@ -25,7 +25,7 @@ export class MaterialLayoutComponent implements OnInit, OnDestroy
|
||||
fixedFooter: boolean;
|
||||
|
||||
// Private
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
@@ -36,7 +36,7 @@ export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
config: AppConfig;
|
||||
|
||||
// Private
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
@ViewChild('tempChart', { static: false }) tempChart: ChartComponent;
|
||||
|
||||
/**
|
||||
@@ -193,15 +193,15 @@ export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
colors : ['#A3BFFA', '#667EEA'],
|
||||
colors : ['#667eea', '#9066ea', '#66c0ea', '#66ead2', '#d266ea', '#66ea90'],
|
||||
fill : {
|
||||
colors : ['#CED9FB', '#AECDFD'],
|
||||
colors : ['#b2bef4', '#c7b2f4', '#b2dff4', '#b2f4e8', '#e8b2f4', '#b2f4c7'],
|
||||
opacity: 0.5,
|
||||
type : 'solid'
|
||||
type : 'gradient'
|
||||
},
|
||||
series : this._deviceDataTemperatureSeries(),
|
||||
stroke : {
|
||||
curve: 'straight',
|
||||
curve: this.config.line_stroke,
|
||||
width: 2
|
||||
},
|
||||
tooltip: {
|
||||
|
||||
@@ -85,7 +85,7 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
smartAttributeTableMatSort: MatSort;
|
||||
|
||||
// Private
|
||||
private _unsubscribeAll: Subject<any>;
|
||||
private _unsubscribeAll: Subject<void>;
|
||||
private systemPrefersDark: boolean;
|
||||
|
||||
readonly humanizeDuration = humanizeDuration;
|
||||
|
||||
@@ -7,12 +7,6 @@
|
||||
// that Treo doesn't support out-of-the-box visually compatible with your application.
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
// FullCalendar
|
||||
@import '~@fullcalendar/core/main.css';
|
||||
@import '~@fullcalendar/daygrid/main.css';
|
||||
@import '~@fullcalendar/timegrid/main.css';
|
||||
@import '~@fullcalendar/list/main.css';
|
||||
|
||||
// Perfect scrollbar
|
||||
@import '~perfect-scrollbar/css/perfect-scrollbar.css';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user