├── .all-contributorsrc ├── .codeclimate.yml ├── .deepsource.toml ├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── Bug.md │ ├── Feature.md │ └── Support.md ├── PULL_REQUEST_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE │ ├── Improvement.md │ └── Other.md ├── dependabot.yml ├── stale.yml └── workflows │ ├── codeql-analysis.yml │ ├── golangci-lint.yml │ ├── goreleaser.yml │ ├── pr-checks.yml │ └── staticcheck.yml ├── .gitignore ├── .gitmodules ├── .golangci.yml ├── .goreleaser.yml ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.build ├── LICENSE.md ├── Makefile ├── README.md ├── SECURITY.md ├── _sample_configs ├── bargraph_config.png ├── bargraph_config.yml ├── dynamic_sizing.yml ├── kubernetes_config.png ├── kubernetes_config.yml ├── sample_config.png ├── sample_config.yml ├── small_config.yml ├── uniconfig.png └── uniconfig.yml ├── app ├── app_manager.go ├── display.go ├── exit_message.go ├── exit_message_test.go ├── focus_tracker.go ├── module_validator.go ├── module_validator_test.go ├── scheduler.go ├── scheduler_test.go ├── widget_maker.go ├── widget_maker_test.go └── wtf_app.go ├── cfg ├── common_settings.go ├── common_settings_test.go ├── config_files.go ├── copy.go ├── default_color_theme.go ├── default_color_theme_test.go ├── default_config_file.go ├── error_messages.go ├── parsers.go ├── parsers_test.go ├── position_settings.go ├── position_validation.go ├── position_validation_test.go ├── secrets.go ├── validatable.go ├── validations.go └── validations_test.go ├── checklist ├── checklist.go ├── checklist_item.go ├── checklist_item_test.go └── checklist_test.go ├── flags └── flags.go ├── generator ├── settings.tpl ├── textwidget.go └── textwidget.tpl ├── go.mod ├── go.sum ├── help └── help.go ├── images ├── dude_wtf.png ├── logo_transparent.png ├── screenshot.jpg └── sponsors │ ├── airbrake.png │ ├── robusta.png │ ├── warp.png │ ├── warp2.png │ └── warp3.png ├── logger └── log.go ├── main.go ├── modules ├── airbrake │ ├── client.go │ ├── group_info_table.go │ ├── keyboard.go │ ├── result_table.go │ ├── settings.go │ ├── util.go │ └── widget.go ├── asana │ ├── client.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── azuredevops │ ├── client.go │ ├── example-conf.yml │ ├── settings.go │ └── widget.go ├── bamboohr │ ├── calendar.go │ ├── client.go │ ├── employee.go │ ├── item.go │ ├── request.go │ ├── settings.go │ └── widget.go ├── bargraph │ ├── settings.go │ └── widget.go ├── buildkite │ ├── client.go │ ├── keyboard.go │ ├── pipelines_display_data.go │ ├── settings.go │ └── widget.go ├── cds │ ├── favorites │ │ ├── display.go │ │ ├── keyboard.go │ │ ├── settings.go │ │ └── widget.go │ ├── queue │ │ ├── display.go │ │ ├── keyboard.go │ │ ├── settings.go │ │ └── widget.go │ └── status │ │ ├── display.go │ │ ├── keyboard.go │ │ ├── settings.go │ │ └── widget.go ├── circleci │ ├── build.go │ ├── client.go │ ├── settings.go │ └── widget.go ├── clocks │ ├── clock.go │ ├── clock_collection.go │ ├── display.go │ ├── settings.go │ └── widget.go ├── cmdrunner │ ├── settings.go │ └── widget.go ├── covid │ ├── cases.go │ ├── cases_test.go │ ├── client.go │ ├── settings.go │ └── widget.go ├── cryptocurrency │ ├── bittrex │ │ ├── bittrex.go │ │ ├── display.go │ │ ├── settings.go │ │ └── widget.go │ ├── blockfolio │ │ ├── settings.go │ │ └── widget.go │ ├── cryptolive │ │ ├── price │ │ │ ├── price.go │ │ │ ├── settings.go │ │ │ └── widget.go │ │ ├── settings.go │ │ ├── toplist │ │ │ ├── display.go │ │ │ ├── settings.go │ │ │ ├── toplist.go │ │ │ └── widget.go │ │ └── widget.go │ └── mempool │ │ ├── settings.go │ │ └── widget.go ├── datadog │ ├── client.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── devto │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── digitalclock │ ├── clocks.go │ ├── display.go │ ├── fonts.go │ ├── settings.go │ └── widget.go ├── digitalocean │ ├── display.go │ ├── droplet.go │ ├── droplet_properties_table.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── docker │ ├── client.go │ ├── example-conf.yml │ ├── settings.go │ ├── utils.go │ └── widget.go ├── feedreader │ ├── keyboard.go │ ├── settings.go │ ├── widget.go │ └── widget_test.go ├── football │ ├── client.go │ ├── settings.go │ ├── types.go │ ├── util.go │ └── widget.go ├── gcal │ ├── cal_event.go │ ├── client.go │ ├── display.go │ ├── display_test.go │ ├── settings.go │ └── widget.go ├── gerrit │ ├── display.go │ ├── gerrit_repo.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── git │ ├── display.go │ ├── git_repo.go │ ├── keyboard.go │ ├── settings.go │ ├── variables.go │ ├── variables_win.go │ └── widget.go ├── github │ ├── display.go │ ├── github_repo.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── gitlab │ ├── display.go │ ├── gitlab_project.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── gitlabtodo │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── gitter │ ├── client.go │ ├── gitter.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── googleanalytics │ ├── client.go │ ├── display.go │ ├── settings.go │ └── widget.go ├── grafana │ ├── client.go │ ├── display.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── gspreadsheets │ ├── client.go │ ├── settings.go │ └── widget.go ├── hackernews │ ├── client.go │ ├── keyboard.go │ ├── settings.go │ ├── story.go │ ├── story_test.go │ └── widget.go ├── healthchecks │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── hibp │ ├── client.go │ ├── hibp_breach.go │ ├── hibp_status.go │ ├── settings.go │ └── widget.go ├── ipaddresses │ ├── ipapi │ │ ├── settings.go │ │ └── widget.go │ └── ipinfo │ │ ├── settings.go │ │ └── widget.go ├── jenkins │ ├── client.go │ ├── job.go │ ├── keyboard.go │ ├── settings.go │ ├── view.go │ └── widget.go ├── jira │ ├── client.go │ ├── issues.go │ ├── keyboard.go │ ├── search_result.go │ ├── settings.go │ └── widget.go ├── krisinformation │ ├── client.go │ ├── settings.go │ └── widget.go ├── kubernetes │ ├── client.go │ ├── settings.go │ ├── widget.go │ └── widget_test.go ├── logger │ ├── settings.go │ └── widget.go ├── lunarphase │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── mercurial │ ├── display.go │ ├── hg_repo.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── nbascore │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── newrelic │ ├── client.go │ ├── client │ │ ├── README.md │ │ ├── alert_conditions.go │ │ ├── alert_events.go │ │ ├── application_deployments.go │ │ ├── application_host_metrics.go │ │ ├── application_hosts.go │ │ ├── application_instance_metrics.go │ │ ├── application_instances.go │ │ ├── application_metrics.go │ │ ├── applications.go │ │ ├── array.go │ │ ├── browser_applications.go │ │ ├── component_metrics.go │ │ ├── http_helper.go │ │ ├── key_transactions.go │ │ ├── legacy_alert_policies.go │ │ ├── main.go │ │ ├── metrics.go │ │ ├── mobile_application_metrics.go │ │ ├── mobile_applications.go │ │ ├── notification_channels.go │ │ ├── server_metrics.go │ │ ├── servers.go │ │ └── usages.go │ ├── display.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── nextbus │ ├── settings.go │ └── widget.go ├── opsgenie │ ├── client.go │ ├── settings.go │ └── widget.go ├── pagerduty │ ├── client.go │ ├── settings.go │ ├── sort.go │ └── widget.go ├── pihole │ ├── client.go │ ├── keyboard.go │ ├── settings.go │ ├── view.go │ └── widget.go ├── pivotal │ ├── client.go │ ├── display.go │ ├── keyboard.go │ ├── settings.go │ ├── structs.go │ ├── view.go │ └── widget.go ├── pocket │ ├── client.go │ ├── item_service.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── power │ ├── battery.go │ ├── battery_freebsd.go │ ├── battery_linux.go │ ├── managed_device_test.go │ ├── managed_devices.go │ ├── settings.go │ ├── source.go │ ├── source_freebsd.go │ ├── source_linux.go │ └── widget.go ├── progress │ ├── settings.go │ └── widget.go ├── resourceusage │ ├── settings.go │ └── widget.go ├── rollbar │ ├── client.go │ ├── keyboard.go │ ├── rollbar.go │ ├── settings.go │ └── widget.go ├── security │ ├── dns.go │ ├── firewall.go │ ├── security_data.go │ ├── settings.go │ ├── users.go │ ├── widget.go │ └── wifi.go ├── spacex │ ├── client.go │ ├── settings.go │ └── widget.go ├── spotify │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── spotifyweb │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── status │ ├── settings.go │ └── widget.go ├── steam │ ├── client.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── stocks │ ├── finnhub │ │ ├── client.go │ │ ├── quote.go │ │ ├── settings.go │ │ └── widget.go │ └── yfinance │ │ ├── settings.go │ │ ├── widget.go │ │ └── yquote.go ├── subreddit │ ├── api.go │ ├── keyboard.go │ ├── link.go │ ├── settings.go │ └── widget.go ├── system │ ├── settings.go │ ├── system_info.go │ ├── system_info_windows.go │ └── widget.go ├── textfile │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── todo │ ├── display.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── todo_plus │ ├── backend │ │ ├── backend.go │ │ ├── project.go │ │ ├── todoist.go │ │ └── trello.go │ ├── display.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── transmission │ ├── display.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── travisci │ ├── client.go │ ├── keyboard.go │ ├── settings.go │ ├── travis.go │ └── widget.go ├── twitch │ ├── client.go │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── twitter │ ├── client.go │ ├── keyboard.go │ ├── request.go │ ├── settings.go │ ├── tweet.go │ ├── user.go │ └── widget.go ├── twitterstats │ ├── client.go │ ├── settings.go │ └── widget.go ├── unknown │ ├── settings.go │ └── widget.go ├── updown │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── uptimerobot │ ├── keyboard.go │ ├── settings.go │ └── widget.go ├── urlcheck │ ├── client.go │ ├── client_test.go │ ├── settings.go │ ├── urlResult.go │ ├── urlResult_test.go │ ├── view.go │ └── widget.go ├── victorops │ ├── client.go │ ├── oncallresponse.go │ ├── oncallteam.go │ ├── settings.go │ └── widget.go ├── weatherservices │ ├── arpansagovau │ │ ├── client.go │ │ ├── settings.go │ │ └── widget.go │ ├── prettyweather │ │ ├── settings.go │ │ └── widget.go │ └── weather │ │ ├── display.go │ │ ├── emoji.go │ │ ├── keyboard.go │ │ ├── settings.go │ │ └── widget.go └── zendesk │ ├── client.go │ ├── keyboard.go │ ├── settings.go │ ├── tickets.go │ └── widget.go ├── package.json ├── scripts └── check-uncommitted-vendor-files.sh ├── support └── github.go ├── utils ├── colors.go ├── colors_test.go ├── conversions.go ├── conversions_test.go ├── email_addresses.go ├── email_addresses_test.go ├── help_parser.go ├── homedir.go ├── homedir_test.go ├── init.go ├── init_test.go ├── reflective.go ├── sums.go ├── sums_test.go ├── text.go ├── text_test.go ├── utils.go └── utils_test.go ├── view ├── bargraph.go ├── bargraph_test.go ├── base.go ├── base_test.go ├── billboard_modal.go ├── info_table.go ├── info_table_test.go ├── keyboard_widget.go ├── keyboard_widget_test.go ├── multisource_widget.go ├── scrollable_widget.go ├── text_widget.go └── text_widget_test.go └── wtf ├── colors.go ├── colors_test.go ├── datetime.go ├── datetime_test.go ├── enablable.go ├── numbers.go ├── numbers_test.go ├── schedulable.go ├── stoppable.go ├── terminal.go └── wtfable.go /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | checks: 3 | similar-code: 4 | config: 5 | threshold: 3 6 | -------------------------------------------------------------------------------- /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "shell" 5 | enabled = true 6 | 7 | [[analyzers]] 8 | name = "go" 9 | enabled = true 10 | 11 | [analyzers.meta] 12 | import_root = "github.com/wtfutil/wtf" 13 | 14 | [[analyzers]] 15 | name = "test-coverage" 16 | enabled = true -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | max_line_length=120 7 | 8 | [*.go] 9 | indent_style = tab 10 | indent_size = 4 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | 14 | [*.html] 15 | indent_style = tab 16 | indent_size = 4 17 | charset = utf-8 18 | trim_trailing_whitespace = false 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | _site/* linguist-vendored 2 | docs/* linguist-vendored 3 | vendor/* linguist-vendored 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: senorprogrammer 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐞 Report a Bug 3 | about: Tell us what's broken 4 | --- 5 | 6 | ## What's broken? 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ⚡️ Request a Feature 3 | about: Tell us what it should do 4 | --- 5 | 6 | ## What should it do? 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Support.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ❓Ask a Question 3 | about: Tell us how we can help 4 | --- 5 | 6 | ## How can we help? 7 | 8 | 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | Thanks for submitting a pull request. Please provide enough information so that others can review your pull request. 3 | 4 | 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/Improvement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Improvement 3 | about: You have some improvement to make wtf better? 4 | --- 5 | 6 | Thanks for submitting a pull request. Please provide enough information so that others can review your pull request. 7 | 8 | 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/Other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other 3 | about: You have some other ideas you want to introduce? 4 | --- 5 | 6 | Thanks for submitting a pull request. Please provide enough information so that others can review your pull request. 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | assignees: 9 | - FelicianoTech 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: daily 14 | open-pull-requests-limit: 10 15 | assignees: 16 | - FelicianoTech 17 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning - action" 2 | 3 | on: 4 | pull_request: 5 | schedule: 6 | - cron: '0 3 * * 3,6' 7 | 8 | jobs: 9 | CodeQL-Build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v4.2.2 16 | 17 | # Initializes the CodeQL tools for scanning. 18 | - name: Initialize CodeQL 19 | uses: github/codeql-action/init@v3 20 | # Override language selection by uncommenting this and choosing your languages 21 | # with: 22 | # languages: go, javascript, csharp, python, cpp, java 23 | 24 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 25 | # If this step fails, then you should remove it and run the build manually (see below) 26 | - name: Autobuild 27 | uses: github/codeql-action/autobuild@v3 28 | 29 | # ℹ️ Command-line programs to run using the OS shell. 30 | # 📚 https://git.io/JvXDl 31 | 32 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 33 | # and modify them (or add more) to build your code if your project 34 | # uses a compiled language 35 | 36 | #- run: | 37 | # make bootstrap 38 | # make release 39 | 40 | - name: Perform CodeQL Analysis 41 | uses: github/codeql-action/analyze@v3 42 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | pull_request: 9 | jobs: 10 | golangci: 11 | name: lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4.2.2 15 | - name: golangci-lint 16 | uses: golangci/golangci-lint-action@v8 17 | with: 18 | # Required: the version of golangci-lint is required and must be 19 | # specified without patch version: we always use the latest patch version. 20 | # https://github.com/golangci/golangci-lint/releases 21 | version: latest 22 | args: ./... --timeout=10m 23 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-24.04 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4.2.2 14 | with: 15 | fetch-depth: 0 16 | - name: Set up Go 17 | uses: actions/setup-go@v5.5 18 | with: 19 | go-version-file: 'go.mod' 20 | - name: Run GoReleaser 21 | uses: goreleaser/goreleaser-action@v6.3.0 22 | with: 23 | version: 2.10.2 24 | args: release --clean 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GH_PAT }} 27 | -------------------------------------------------------------------------------- /.github/workflows/pr-checks.yml: -------------------------------------------------------------------------------- 1 | name: "PR Checks" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-24.04 11 | steps: 12 | - name: "Checkout code" 13 | uses: actions/checkout@v4.2.2 14 | with: 15 | fetch-depth: 0 16 | - name: "Set up Go" 17 | uses: actions/setup-go@v5.5.0 18 | with: 19 | go-version-file: 'go.mod' 20 | - name: Run GoReleaser 21 | uses: goreleaser/goreleaser-action@v6.3.0 22 | with: 23 | version: 2.10.2 24 | args: release --snapshot 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Go ### 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | ftw* 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Misc 17 | .DS_Store 18 | gcal/client_secret.json 19 | gspreadsheets/client_secret.json 20 | profile.pdf 21 | report.* 22 | .vscode 23 | 24 | # All things node 25 | node_modules/ 26 | package-lock.json 27 | 28 | #intellij idea 29 | .idea/ 30 | 31 | dist/* 32 | bin/ 33 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfutil/wtf/930a94a305d19077dbf26402eb7c6b6de2169adc/.gitmodules -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | run: 4 | timeout: 3m 5 | 6 | linters: 7 | enable: 8 | - govet 9 | - errcheck 10 | - staticcheck 11 | - unconvert 12 | exclusions: 13 | rules: 14 | - linters: 15 | - errcheck 16 | source: "^\\s*defer\\s+" 17 | 18 | formatters: 19 | enable: 20 | - gofmt 21 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | env: 4 | - GO111MODULE=on 5 | - GOPROXY="https://proxy.golang.org,direct" 6 | 7 | archives: 8 | - id: default 9 | wrap_in_directory: true 10 | 11 | builds: 12 | - binary: wtfutil 13 | goos: 14 | - darwin 15 | - linux 16 | goarch: 17 | - amd64 18 | - arm 19 | - arm64 20 | 21 | before: 22 | hooks: 23 | - make install 24 | 25 | homebrew_casks: 26 | - name: wtfutil 27 | conflicts: 28 | - formula: wtfutil 29 | homepage: 'https://wtfutil.com' 30 | description: 'The personal information dashboard for your terminal.' 31 | repository: 32 | owner: wtfutil 33 | name: homebrew-wtfutil 34 | hooks: 35 | post: 36 | # This hook is needed until this binary is signed and notarized 37 | install: | 38 | if system_command("/usr/bin/xattr", args: ["-h"]).exit_status == 0 39 | system_command "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "#{staged_path}/wtfutil"] 40 | end 41 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.19.x" 5 | 6 | before_install: 7 | # Make sure travis builds work for forks 8 | - mkdir -p $TRAVIS_BUILD_DIR $GOPATH/src/github.com/wtfutil 9 | - test ! -d $GOPATH/src/github.com/wtfutil/wtf && mv $TRAVIS_BUILD_DIR $GOPATH/src/github.com/wtfutil/wtf || true 10 | - export TRAVIS_BUILD_DIR=$HOME/gopath/src/github.com/wtfutil/wtf 11 | - cd $HOME/gopath/src/github.com/wtfutil/wtf 12 | - export GOPROXY="https://proxy.golang.org,direct" 13 | 14 | script: go get ./... && ./scripts/check-uncommitted-vendor-files.sh && go test -v github.com/wtfutil/wtf/... 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24-alpine as build 2 | 3 | ARG version=master 4 | 5 | RUN apk add git make ncurses && \ 6 | git clone https://github.com/wtfutil/wtf.git $GOPATH/src/github.com/wtfutil/wtf && \ 7 | cd $GOPATH/src/github.com/wtfutil/wtf && \ 8 | git checkout $version 9 | 10 | ENV GOPROXY=https://proxy.golang.org,direct 11 | ENV GO111MODULE=on 12 | ENV GOSUMDB=off 13 | 14 | WORKDIR $GOPATH/src/github.com/wtfutil/wtf 15 | 16 | ENV PATH=$PATH:./bin 17 | 18 | RUN make build 19 | 20 | FROM alpine 21 | 22 | COPY --from=build /go/src/github.com/wtfutil/wtf/bin/wtfutil /usr/local/bin/ 23 | RUN adduser -h /config -DG users -u 20000 wtf 24 | 25 | USER wtf 26 | ENTRYPOINT ["wtfutil"] 27 | -------------------------------------------------------------------------------- /Dockerfile.build: -------------------------------------------------------------------------------- 1 | FROM golang:1.24 as build 2 | 3 | ARG version=master 4 | 5 | RUN git clone https://github.com/wtfutil/wtf.git $GOPATH/src/github.com/wtfutil/wtf && \ 6 | cd $GOPATH/src/github.com/wtfutil/wtf && \ 7 | git checkout $version 8 | 9 | ENV GOPROXY=https://proxy.golang.org,direct 10 | ENV GO111MODULE=on 11 | ENV GOSUMDB=off 12 | 13 | WORKDIR $GOPATH/src/github.com/wtfutil/wtf 14 | 15 | ENV PATH=$PATH:./bin 16 | 17 | RUN make build && \ 18 | cp bin/wtfutil /usr/local/bin/ 19 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | To file a security issue, open a new Issue in the Issues tab. 4 | -------------------------------------------------------------------------------- /_sample_configs/bargraph_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfutil/wtf/930a94a305d19077dbf26402eb7c6b6de2169adc/_sample_configs/bargraph_config.png -------------------------------------------------------------------------------- /_sample_configs/bargraph_config.yml: -------------------------------------------------------------------------------- 1 | wtf: 2 | colors: 3 | border: 4 | focusable: darkslateblue 5 | focused: orange 6 | normal: gray 7 | grid: 8 | columns: [40, 40] 9 | rows: [13, 13, 4] 10 | refreshInterval: 1 11 | mods: 12 | bargraph: 13 | enabled: true 14 | graphIcon: "💀" 15 | graphStars: 25 16 | position: 17 | top: 1 18 | left: 0 19 | height: 2 20 | width: 2 21 | refreshInterval: 30 -------------------------------------------------------------------------------- /_sample_configs/dynamic_sizing.yml: -------------------------------------------------------------------------------- 1 | wtf: 2 | mods: 3 | battery: 4 | type: power 5 | title: "⚡️" 6 | enabled: true 7 | position: 8 | top: 0 9 | left: 0 10 | height: 1 11 | width: 1 12 | refreshInterval: 15 13 | security_info: 14 | type: security 15 | enabled: true 16 | position: 17 | top: 0 18 | left: 1 19 | height: 1 20 | width: 1 21 | refreshInterval: 3600 -------------------------------------------------------------------------------- /_sample_configs/kubernetes_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfutil/wtf/930a94a305d19077dbf26402eb7c6b6de2169adc/_sample_configs/kubernetes_config.png -------------------------------------------------------------------------------- /_sample_configs/kubernetes_config.yml: -------------------------------------------------------------------------------- 1 | wtf: 2 | colors: 3 | border: 4 | focusable: darkslateblue 5 | focused: orange 6 | normal: gray 7 | grid: 8 | columns: [32, 32, 32, 32, 32, 32] 9 | rows: [10, 10, 10, 10, 10, 10] 10 | refreshInterval: 2 11 | mods: 12 | kubernetes: 13 | enabled: true 14 | kubeconfig: /Users/testuser/.kube/config 15 | namespaces: ["demo", "kube-system"] 16 | objects: ["nodes","deployments", "pods"] 17 | position: 18 | top: 0 19 | left: 0 20 | height: 6 21 | width: 3 22 | -------------------------------------------------------------------------------- /_sample_configs/sample_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfutil/wtf/930a94a305d19077dbf26402eb7c6b6de2169adc/_sample_configs/sample_config.png -------------------------------------------------------------------------------- /_sample_configs/small_config.yml: -------------------------------------------------------------------------------- 1 | wtf: 2 | grid: 3 | columns: [20, 20] 4 | rows: [3, 3] 5 | refreshInterval: 1 6 | mods: 7 | uptime: 8 | type: cmdrunner 9 | args: [] 10 | cmd: "uptime" 11 | enabled: true 12 | position: 13 | top: 0 14 | left: 0 15 | height: 1 16 | width: 1 17 | refreshInterval: 30 18 | -------------------------------------------------------------------------------- /_sample_configs/uniconfig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfutil/wtf/930a94a305d19077dbf26402eb7c6b6de2169adc/_sample_configs/uniconfig.png -------------------------------------------------------------------------------- /_sample_configs/uniconfig.yml: -------------------------------------------------------------------------------- 1 | wtf: 2 | colors: 3 | background: black 4 | border: 5 | focusable: darkslateblue 6 | grid: 7 | columns: [40, 40] 8 | rows: [16] 9 | refreshInterval: 1 10 | mods: 11 | americas_time: 12 | title: "Americas" 13 | type: clocks 14 | enabled: true 15 | locations: 16 | UTC: "Etc/UTC" 17 | Vancouver: "America/Vancouver" 18 | New_York: "America/New_York" 19 | Sao_Paolo: "America/Sao_Paulo" 20 | Denver: "America/Denver" 21 | Iqaluit: "America/Iqaluit" 22 | Bahamas: "America/Nassau" 23 | Chicago: "America/Chicago" 24 | position: 25 | top: 0 26 | left: 0 27 | height: 1 28 | width: 1 29 | refreshInterval: 15 30 | sort: "chronological" 31 | textfile: 32 | enabled: true 33 | filePaths: 34 | - "~/.config/wtf/config.yml" 35 | format: true 36 | formatStyle: "vim" 37 | position: 38 | top: 0 39 | left: 1 40 | height: 1 41 | width: 1 42 | refreshInterval: 15 43 | -------------------------------------------------------------------------------- /app/scheduler.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/wtfutil/wtf/wtf" 7 | ) 8 | 9 | // Schedule kicks off the first refresh of a module's data and then queues the rest of the 10 | // data refreshes on a timer 11 | func Schedule(widget wtf.Wtfable) { 12 | widget.Refresh() 13 | 14 | interval := widget.CommonSettings().RefreshInterval 15 | 16 | if interval <= 0 { 17 | return 18 | } 19 | 20 | timer := time.NewTicker(interval) 21 | 22 | for { 23 | select { 24 | case <-timer.C: 25 | if widget.Enabled() { 26 | widget.Refresh() 27 | } else { 28 | timer.Stop() 29 | return 30 | } 31 | case quit := <-widget.QuitChan(): 32 | if quit { 33 | timer.Stop() 34 | return 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /cfg/default_color_theme_test.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_NewDefaultColorTheme(t *testing.T) { 10 | theme := NewDefaultColorTheme() 11 | 12 | assert.Equal(t, "orange", theme.Focused) 13 | assert.Equal(t, "red", theme.Subheading) 14 | assert.Equal(t, "transparent", theme.Background) 15 | } 16 | 17 | func Test_NewDefaultColorConfig(t *testing.T) { 18 | cfg, err := NewDefaultColorConfig() 19 | 20 | assert.Nil(t, err) 21 | 22 | assert.Equal(t, "orange", cfg.UString("bordertheme.focused")) 23 | assert.Equal(t, "red", cfg.UString("texttheme.subheading")) 24 | assert.Equal(t, "transparent", cfg.UString("widgettheme.background")) 25 | assert.Equal(t, "", cfg.UString("widgettheme.missing")) 26 | } 27 | -------------------------------------------------------------------------------- /cfg/parsers.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/olebedev/config" 8 | ) 9 | 10 | // ParseAsMapOrList takes a configuration key and attempts to parse it first as a map 11 | // and then as a list. Map entries are concatenated as "key/value" 12 | func ParseAsMapOrList(ymlConfig *config.Config, configKey string) []string { 13 | result := []string{} 14 | 15 | mapItems, err := ymlConfig.Map(configKey) 16 | if err == nil { 17 | for key, value := range mapItems { 18 | result = append(result, fmt.Sprintf("%s/%s", value, key)) 19 | } 20 | return result 21 | } 22 | 23 | listItems := ymlConfig.UList(configKey) 24 | for _, listItem := range listItems { 25 | result = append(result, listItem.(string)) 26 | } 27 | 28 | return result 29 | } 30 | 31 | // ParseTimeString takes a configuration key and attempts to parse it first as an int 32 | // and then as a duration (int + time unit) 33 | func ParseTimeString(cfg *config.Config, configKey string, defaultValue string) time.Duration { 34 | i, err := cfg.Int(configKey) 35 | if err == nil { 36 | return time.Duration(i) * time.Second 37 | } 38 | 39 | str := cfg.UString(configKey, defaultValue) 40 | d, err := time.ParseDuration(str) 41 | if err == nil { 42 | return d 43 | } 44 | 45 | return time.Second 46 | } 47 | -------------------------------------------------------------------------------- /cfg/position_validation.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/logrusorgru/aurora/v4" 7 | ) 8 | 9 | // Common examples of invalid position configuration are: 10 | // 11 | // position: 12 | // top: -3 13 | // left: 2 14 | // width: 0 15 | // height: 1 16 | // 17 | // position: 18 | // top: 3 19 | // width: 2 20 | // height: 1 21 | // 22 | // position: 23 | // top: 3 24 | // # left: 2 25 | // width: 2 26 | // height: 1 27 | // 28 | // position: 29 | // top: 3 30 | // left: 2 31 | // width: 2 32 | // height: 1 33 | type positionValidation struct { 34 | err error 35 | name string 36 | intVal int 37 | } 38 | 39 | func (posVal *positionValidation) Error() error { 40 | return posVal.err 41 | } 42 | 43 | func (posVal *positionValidation) HasError() bool { 44 | return posVal.err != nil 45 | } 46 | 47 | func (posVal *positionValidation) IntValue() int { 48 | return posVal.intVal 49 | } 50 | 51 | // String returns the Stringer representation of the positionValidation 52 | func (posVal *positionValidation) String() string { 53 | return fmt.Sprintf("Invalid value for %s:\t%d", aurora.Yellow(posVal.name), posVal.intVal) 54 | } 55 | 56 | /* -------------------- Unexported Functions -------------------- */ 57 | 58 | func newPositionValidation(name string, intVal int, err error) *positionValidation { 59 | posVal := &positionValidation{ 60 | err: err, 61 | name: name, 62 | intVal: intVal, 63 | } 64 | 65 | return posVal 66 | } 67 | -------------------------------------------------------------------------------- /cfg/position_validation_test.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | var ( 11 | posVal = &positionValidation{ 12 | err: errors.New("Busted"), 13 | name: "top", 14 | intVal: -3, 15 | } 16 | ) 17 | 18 | func Test_Attributes(t *testing.T) { 19 | assert.EqualError(t, posVal.Error(), "Busted") 20 | assert.Equal(t, true, posVal.HasError()) 21 | assert.Equal(t, -3, posVal.IntValue()) 22 | 23 | assert.Contains(t, posVal.String(), "Invalid") 24 | assert.Contains(t, posVal.String(), "top") 25 | assert.Contains(t, posVal.String(), "-3") 26 | } 27 | -------------------------------------------------------------------------------- /cfg/validatable.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | // Validatable is implemented by any value that validates a configuration setting 4 | type Validatable interface { 5 | Error() error 6 | HasError() bool 7 | String() string 8 | IntValue() int 9 | } 10 | -------------------------------------------------------------------------------- /cfg/validations.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | // Validations represent a collection of config setting validations 4 | type Validations struct { 5 | validations map[string]Validatable 6 | } 7 | 8 | // NewValidations creates and returns an instance of Validations 9 | func NewValidations() *Validations { 10 | vals := &Validations{ 11 | validations: make(map[string]Validatable), 12 | } 13 | 14 | return vals 15 | } 16 | 17 | func (vals *Validations) append(key string, posVal Validatable) { 18 | vals.validations[key] = posVal 19 | } 20 | 21 | func (vals *Validations) intValueFor(key string) int { 22 | val := vals.validations[key] 23 | if val != nil { 24 | return val.IntValue() 25 | } 26 | 27 | return 0 28 | } 29 | -------------------------------------------------------------------------------- /cfg/validations_test.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | var ( 10 | vals = NewValidations() 11 | ) 12 | 13 | func Test_intValueFor(t *testing.T) { 14 | vals.append("left", newPositionValidation("left", 3, nil)) 15 | 16 | tests := []struct { 17 | name string 18 | key string 19 | expected int 20 | }{ 21 | { 22 | name: "with valid key", 23 | key: "left", 24 | expected: 3, 25 | }, 26 | { 27 | name: "with invalid key", 28 | key: "cat", 29 | expected: 0, 30 | }, 31 | } 32 | 33 | for _, tt := range tests { 34 | t.Run(tt.name, func(t *testing.T) { 35 | assert.Equal(t, tt.expected, vals.intValueFor(tt.key)) 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /checklist/checklist_item_test.go: -------------------------------------------------------------------------------- 1 | package checklist 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func testChecklistItem() *ChecklistItem { 10 | item := NewChecklistItem( 11 | false, 12 | nil, 13 | make([]string, 0), 14 | "test", 15 | "", 16 | "", 17 | ) 18 | return item 19 | } 20 | 21 | func Test_CheckMark(t *testing.T) { 22 | item := testChecklistItem() 23 | assert.Equal(t, " ", item.CheckMark()) 24 | 25 | item.Toggle() 26 | assert.Equal(t, "x", item.CheckMark()) 27 | } 28 | 29 | func Test_Toggle(t *testing.T) { 30 | item := testChecklistItem() 31 | assert.Equal(t, false, item.Checked) 32 | 33 | item.Toggle() 34 | assert.Equal(t, true, item.Checked) 35 | 36 | item.Toggle() 37 | assert.Equal(t, false, item.Checked) 38 | } 39 | -------------------------------------------------------------------------------- /generator/settings.tpl: -------------------------------------------------------------------------------- 1 | package {{(Lower .Name)}} 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "{{(.Name)}}" 11 | ) 12 | 13 | // Settings defines the configuration properties for this module 14 | type Settings struct { 15 | common *cfg.Common 16 | 17 | // Define your settings attributes here 18 | } 19 | 20 | // NewSettingsFromYAML creates a new settings instance from a YAML config block 21 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 22 | settings := Settings{ 23 | common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 24 | 25 | // Configure your settings attributes here. See http://github.com/olebedev/config for type details 26 | } 27 | 28 | return &settings 29 | } -------------------------------------------------------------------------------- /generator/textwidget.tpl: -------------------------------------------------------------------------------- 1 | package {{(Lower .Name)}} 2 | 3 | import ( 4 | "github.com/rivo/tview" 5 | "github.com/wtfutil/wtf/view" 6 | ) 7 | 8 | // Widget is the container for your module's data 9 | type Widget struct { 10 | view.TextWidget 11 | 12 | settings *Settings 13 | } 14 | 15 | // NewWidget creates and returns an instance of Widget 16 | func NewWidget(tviewApp *tview.Application, redrawChan chan bool, pages *tview.Pages, settings *Settings) *Widget { 17 | widget := Widget{ 18 | TextWidget: view.NewTextWidget(tviewApp, redrawChan, pages, settings.common), 19 | 20 | settings: settings, 21 | } 22 | 23 | return &widget 24 | } 25 | 26 | /* -------------------- Exported Functions -------------------- */ 27 | 28 | // Refresh updates the onscreen contents of the widget 29 | func (widget *Widget) Refresh() { 30 | 31 | // The last call should always be to the display function 32 | widget.display() 33 | } 34 | 35 | /* -------------------- Unexported Functions -------------------- */ 36 | 37 | func (widget *Widget) content() string { 38 | return "This is my widget" 39 | } 40 | 41 | func (widget *Widget) display() { 42 | widget.Redraw(func() (string, string, bool) { 43 | return widget.CommonSettings().Title, widget.content(), false 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /help/help.go: -------------------------------------------------------------------------------- 1 | package help 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/app" 8 | "github.com/wtfutil/wtf/utils" 9 | ) 10 | 11 | // Display displays the output of the --help argument 12 | func Display(moduleName string, cfg *config.Config) { 13 | if moduleName == "" { 14 | fmt.Println("\n --module takes a module name as an argument, i.e: '--module=github'") 15 | } else { 16 | fmt.Printf("%s\n", helpFor(moduleName, cfg)) 17 | } 18 | } 19 | 20 | func helpFor(moduleName string, cfg *config.Config) string { 21 | err := cfg.Set("wtf.mods."+moduleName+".enabled", true) 22 | if err != nil { 23 | return "" 24 | } 25 | 26 | widget := app.MakeWidget(nil, nil, moduleName, cfg, nil) 27 | 28 | // Since we are forcing enabled config, if no module 29 | // exists, we will get the unknown one 30 | if widget.CommonSettings().Title == "Unknown" { 31 | return "Unable to find module " + moduleName 32 | } 33 | 34 | result := "" 35 | result += utils.StripColorTags(widget.HelpText()) 36 | result += "\n" 37 | result += "Configuration Attributes" 38 | result += widget.ConfigText() 39 | return result 40 | } 41 | -------------------------------------------------------------------------------- /images/dude_wtf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfutil/wtf/930a94a305d19077dbf26402eb7c6b6de2169adc/images/dude_wtf.png -------------------------------------------------------------------------------- /images/logo_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfutil/wtf/930a94a305d19077dbf26402eb7c6b6de2169adc/images/logo_transparent.png -------------------------------------------------------------------------------- /images/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfutil/wtf/930a94a305d19077dbf26402eb7c6b6de2169adc/images/screenshot.jpg -------------------------------------------------------------------------------- /images/sponsors/airbrake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfutil/wtf/930a94a305d19077dbf26402eb7c6b6de2169adc/images/sponsors/airbrake.png -------------------------------------------------------------------------------- /images/sponsors/robusta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfutil/wtf/930a94a305d19077dbf26402eb7c6b6de2169adc/images/sponsors/robusta.png -------------------------------------------------------------------------------- /images/sponsors/warp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfutil/wtf/930a94a305d19077dbf26402eb7c6b6de2169adc/images/sponsors/warp.png -------------------------------------------------------------------------------- /images/sponsors/warp2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfutil/wtf/930a94a305d19077dbf26402eb7c6b6de2169adc/images/sponsors/warp2.png -------------------------------------------------------------------------------- /images/sponsors/warp3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfutil/wtf/930a94a305d19077dbf26402eb7c6b6de2169adc/images/sponsors/warp3.png -------------------------------------------------------------------------------- /logger/log.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | /* -------------------- Exported Functions -------------------- */ 10 | 11 | func Log(msg string) { 12 | if LogFileMissing() { 13 | return 14 | } 15 | 16 | f, err := os.OpenFile(LogFilePath(), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) 17 | if err != nil { 18 | log.Fatalf("error opening file: %v", err) 19 | } 20 | defer func() { _ = f.Close() }() 21 | 22 | log.SetOutput(f) 23 | log.Println(msg) 24 | } 25 | 26 | func LogFileMissing() bool { 27 | return LogFilePath() == "" 28 | } 29 | 30 | func LogFilePath() string { 31 | dir, err := os.UserHomeDir() 32 | if err != nil { 33 | return "" 34 | } 35 | 36 | return filepath.Join(dir, ".config", "wtf", "log.txt") 37 | } 38 | -------------------------------------------------------------------------------- /modules/airbrake/group_info_table.go: -------------------------------------------------------------------------------- 1 | package airbrake 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/wtfutil/wtf/view" 8 | ) 9 | 10 | type groupInfoTable struct { 11 | group *Group 12 | propertyMap map[string]string 13 | 14 | colWidth0 int 15 | colWidth1 int 16 | tableHeight int 17 | } 18 | 19 | func newGroupInfoTable(g *Group) *groupInfoTable { 20 | propTable := &groupInfoTable{ 21 | group: g, 22 | 23 | colWidth0: 20, 24 | colWidth1: 51, 25 | tableHeight: 15, 26 | } 27 | 28 | propTable.propertyMap = propTable.buildPropertyMap() 29 | 30 | return propTable 31 | } 32 | 33 | func (propTable *groupInfoTable) buildPropertyMap() map[string]string { 34 | propMap := map[string]string{} 35 | 36 | g := propTable.group 37 | if g == nil { 38 | return propMap 39 | } 40 | propMap["1. First Seen"] = g.CreatedAt 41 | propMap["2. Last Seen"] = g.LastNoticeAt 42 | propMap["3. Occurrences"] = strconv.Itoa(int(g.NoticeCount)) 43 | propMap["4. Environment"] = g.Context.Environment 44 | propMap["5. Severity"] = g.Context.Severity 45 | propMap["6. Muted"] = fmt.Sprintf("%v", g.Muted) 46 | propMap["7. File"] = g.File() 47 | 48 | return propMap 49 | } 50 | 51 | func (propTable *groupInfoTable) render() string { 52 | tbl := view.NewInfoTable( 53 | []string{"Property", "Value"}, 54 | propTable.propertyMap, 55 | propTable.colWidth0, 56 | propTable.colWidth1, 57 | propTable.tableHeight, 58 | ) 59 | 60 | return tbl.Render() 61 | } 62 | -------------------------------------------------------------------------------- /modules/airbrake/keyboard.go: -------------------------------------------------------------------------------- 1 | package airbrake 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("o", widget.openGroup, "Open group in browser") 10 | widget.SetKeyboardChar("s", widget.resolveGroup, "Resolve group") 11 | widget.SetKeyboardChar("m", widget.muteGroup, "Mute group") 12 | widget.SetKeyboardChar("u", widget.unmuteGroup, "Unmute group") 13 | widget.SetKeyboardChar("t", widget.toggleDisplayText, "Toggle between title and compare views") 14 | 15 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 16 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 17 | 18 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 19 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 20 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 21 | widget.SetKeyboardKey(tcell.KeyEnter, widget.viewGroup, "View group") 22 | } 23 | -------------------------------------------------------------------------------- /modules/airbrake/result_table.go: -------------------------------------------------------------------------------- 1 | package airbrake 2 | 3 | import ( 4 | "github.com/wtfutil/wtf/utils" 5 | "github.com/wtfutil/wtf/view" 6 | ) 7 | 8 | type resultTable struct { 9 | propertyMap map[string]string 10 | 11 | colWidth0 int 12 | colWidth1 int 13 | tableHeight int 14 | } 15 | 16 | func newResultTable(result, message string) *resultTable { 17 | propTable := &resultTable{ 18 | colWidth0: 20, 19 | colWidth1: 51, 20 | tableHeight: 15, 21 | } 22 | propTable.propertyMap = map[string]string{result: message} 23 | 24 | return propTable 25 | } 26 | 27 | func (propTable *resultTable) render() string { 28 | tbl := view.NewInfoTable( 29 | []string{"Result", "Message"}, 30 | propTable.propertyMap, 31 | propTable.colWidth0, 32 | propTable.colWidth1, 33 | propTable.tableHeight, 34 | ) 35 | 36 | return tbl.Render() + utils.CenterText("Esc to close", 80) 37 | } 38 | -------------------------------------------------------------------------------- /modules/airbrake/settings.go: -------------------------------------------------------------------------------- 1 | package airbrake 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | 7 | "github.com/olebedev/config" 8 | "github.com/wtfutil/wtf/cfg" 9 | ) 10 | 11 | const ( 12 | defaultFocusable = true 13 | defaultTitle = "Airbrake" 14 | ) 15 | 16 | type Settings struct { 17 | *cfg.Common 18 | 19 | projectID int `help:"The id of your Airbrake project."` 20 | authToken string `help:"The token that allows accessing Airbrake API"` 21 | } 22 | 23 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 24 | settings := Settings{ 25 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, 26 | defaultFocusable, ymlConfig, globalConfig), 27 | projectID: ymlConfig.UInt("projectID", getProjectID()), 28 | authToken: ymlConfig.UString("authToken", os.Getenv("AIRBRAKE_USER_KEY")), 29 | } 30 | 31 | cfg.ModuleSecret(name, globalConfig, &settings.authToken).Load() 32 | 33 | return &settings 34 | } 35 | 36 | func getProjectID() int { 37 | projectID, err := strconv.ParseInt(os.Getenv("AIRBRAKE_PROJECT_ID"), 10, 32) 38 | if err != nil { 39 | return 0 40 | } 41 | 42 | return int(projectID) 43 | } 44 | -------------------------------------------------------------------------------- /modules/airbrake/util.go: -------------------------------------------------------------------------------- 1 | package airbrake 2 | 3 | func reverseString(s string) string { 4 | r := []rune(s) 5 | for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { 6 | r[i], r[j] = r[j], r[i] 7 | } 8 | return string(r) 9 | } 10 | -------------------------------------------------------------------------------- /modules/asana/keyboard.go: -------------------------------------------------------------------------------- 1 | package asana 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("j", widget.Next, "Select next task") 10 | widget.SetKeyboardChar("k", widget.Prev, "Select previous task") 11 | widget.SetKeyboardChar("q", widget.Unselect, "Unselect task") 12 | widget.SetKeyboardChar("o", widget.openTask, "Open task in browser") 13 | widget.SetKeyboardChar("x", widget.toggleTaskCompletion, "Toggles the task's completion state") 14 | widget.SetKeyboardChar("?", widget.ShowHelp, "Shows help") 15 | 16 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next task") 17 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous task") 18 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Unselect task") 19 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openTask, "Open task in browser") 20 | } 21 | -------------------------------------------------------------------------------- /modules/azuredevops/example-conf.yml: -------------------------------------------------------------------------------- 1 | wtf: 2 | colors: 3 | # background: black 4 | # foreground: blue 5 | border: 6 | focusable: darkslateblue 7 | focused: orange 8 | normal: gray 9 | checked: yellow 10 | highlight: 11 | fore: black 12 | back: gray 13 | rows: 14 | even: yellow 15 | odd: white 16 | grid: 17 | # How _wide_ the columns are, in terminal characters. In this case we have 18 | # four columns, each of which are 35 characters wide. 19 | # columns: [50, ] 20 | # How _high_ the rows are, in terminal lines. In this case we have four rows 21 | # that support ten line of text and one of four. 22 | # rows: [50] 23 | refreshInterval: 1 24 | openFileUtil: "open" 25 | mods: 26 | azuredevops: 27 | type: azuredevops 28 | title: "💻" 29 | enabled: true 30 | position: 31 | top: 0 32 | left: 0 33 | height: 3 34 | width: 3 35 | refreshInterval: 1 36 | labelColor: lightblue # title label color (optional / default: white) 37 | apiToken: "mysecret api token" # api key (required) 38 | orgUrl: "https://dev.azure.com/myawesomecompany/" # url to your azure devops project (required) 39 | prjectName: "the awesome project" # name of your project (required) 40 | maxRows: 3 #max rows to show (optional / default 3) 41 | 42 | -------------------------------------------------------------------------------- /modules/azuredevops/settings.go: -------------------------------------------------------------------------------- 1 | package azuredevops 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocus = false 12 | defaultTitle = "azuredevops" 13 | ) 14 | 15 | // Settings defines the configuration options for this module 16 | type Settings struct { 17 | *cfg.Common 18 | 19 | apiToken string `help:"Your Azure DevOps Access Token."` 20 | labelColor string 21 | maxRows int 22 | orgURL string `help:"Your Azure DevOps organization URL."` 23 | projectName string 24 | } 25 | 26 | // NewSettingsFromYAML creates and returns an instance of Settings with configuration options populated 27 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 28 | settings := Settings{ 29 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocus, ymlConfig, globalConfig), 30 | 31 | apiToken: ymlConfig.UString("apiToken", os.Getenv("WTF_AZURE_DEVOPS_API_TOKEN")), 32 | labelColor: ymlConfig.UString("labelColor", "white"), 33 | maxRows: ymlConfig.UInt("maxRows", 3), 34 | orgURL: ymlConfig.UString("orgURL", os.Getenv("WTF_AZURE_DEVOPS_ORG_URL")), 35 | projectName: ymlConfig.UString("projectName", os.Getenv("WTF_AZURE_DEVOPS_PROJECT_NAME")), 36 | } 37 | 38 | cfg.ModuleSecret(name, globalConfig, &settings.apiToken). 39 | Service(settings.orgURL).Load() 40 | 41 | return &settings 42 | } 43 | -------------------------------------------------------------------------------- /modules/bamboohr/calendar.go: -------------------------------------------------------------------------------- 1 | package bamboohr 2 | 3 | type Calendar struct { 4 | Items []Item `xml:"item"` 5 | } 6 | 7 | /* -------------------- Public Functions -------------------- */ 8 | 9 | func (calendar *Calendar) Holidays() []Item { 10 | return calendar.filteredItems("holiday") 11 | } 12 | 13 | func (calendar *Calendar) ItemsByType(itemType string) []Item { 14 | if itemType == "timeOff" { 15 | return calendar.TimeOffs() 16 | } 17 | 18 | return calendar.Holidays() 19 | } 20 | 21 | func (calendar *Calendar) TimeOffs() []Item { 22 | return calendar.filteredItems("timeOff") 23 | } 24 | 25 | /* -------------------- Private Functions -------------------- */ 26 | 27 | func (calendar *Calendar) filteredItems(itemType string) []Item { 28 | items := []Item{} 29 | 30 | for _, item := range calendar.Items { 31 | if item.Type == itemType { 32 | items = append(items, item) 33 | } 34 | } 35 | 36 | return items 37 | } 38 | -------------------------------------------------------------------------------- /modules/bamboohr/employee.go: -------------------------------------------------------------------------------- 1 | package bamboohr 2 | 3 | /* 4 | * Note: this currently implements the minimum number of fields to fulfill the Away functionality. 5 | * Undoubtedly there are more fields than this to an employee 6 | */ 7 | type Employee struct { 8 | ID int `xml:"id,attr"` 9 | Name string `xml:",chardata"` 10 | } 11 | -------------------------------------------------------------------------------- /modules/bamboohr/item.go: -------------------------------------------------------------------------------- 1 | package bamboohr 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/wtfutil/wtf/wtf" 7 | ) 8 | 9 | type Item struct { 10 | Employee Employee `xml:"employee"` 11 | End string `xml:"end"` 12 | Holiday string `xml:"holiday"` 13 | Start string `xml:"start"` 14 | Type string `xml:"type,attr"` 15 | } 16 | 17 | func (item *Item) String() string { 18 | return fmt.Sprintf("Item: %s, %s, %s, %s", item.Type, item.Employee.Name, item.Start, item.End) 19 | } 20 | 21 | /* -------------------- Exported Functions -------------------- */ 22 | 23 | func (item *Item) IsOneDay() bool { 24 | return item.Start == item.End 25 | } 26 | 27 | func (item *Item) Name() string { 28 | if (item.Employee != Employee{}) { 29 | return item.Employee.Name 30 | } 31 | 32 | return item.Holiday 33 | } 34 | 35 | func (item *Item) PrettyStart() string { 36 | return wtf.PrettyDate(item.Start) 37 | } 38 | 39 | func (item *Item) PrettyEnd() string { 40 | return wtf.PrettyDate(item.End) 41 | } 42 | -------------------------------------------------------------------------------- /modules/bamboohr/request.go: -------------------------------------------------------------------------------- 1 | package bamboohr 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | ) 7 | 8 | func Request(apiKey string, apiURL string) ([]byte, error) { 9 | req, err := http.NewRequest("GET", apiURL, http.NoBody) 10 | if err != nil { 11 | return nil, err 12 | } 13 | 14 | req.SetBasicAuth(apiKey, "x") 15 | 16 | client := &http.Client{} 17 | resp, err := client.Do(req) 18 | if err != nil { 19 | return nil, err 20 | } 21 | defer func() { _ = resp.Body.Close() }() 22 | 23 | data, err := ParseBody(resp) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | return data, err 29 | } 30 | 31 | func ParseBody(resp *http.Response) ([]byte, error) { 32 | var buffer bytes.Buffer 33 | _, err := buffer.ReadFrom(resp.Body) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | return buffer.Bytes(), nil 39 | } 40 | -------------------------------------------------------------------------------- /modules/bamboohr/settings.go: -------------------------------------------------------------------------------- 1 | package bamboohr 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = false 12 | defaultTitle = "BambooHR" 13 | ) 14 | 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | apiKey string `help:"Your BambooHR API token."` 19 | subdomain string `help:"Your BambooHR API subdomain name."` 20 | } 21 | 22 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 23 | settings := Settings{ 24 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 25 | 26 | apiKey: ymlConfig.UString("apiKey", ymlConfig.UString("apikey", os.Getenv("WTF_BAMBOO_HR_TOKEN"))), 27 | subdomain: ymlConfig.UString("subdomain", os.Getenv("WTF_BAMBOO_HR_SUBDOMAIN")), 28 | } 29 | 30 | cfg.ModuleSecret(name, globalConfig, &settings.apiKey).Load() 31 | 32 | return &settings 33 | } 34 | -------------------------------------------------------------------------------- /modules/bargraph/settings.go: -------------------------------------------------------------------------------- 1 | package bargraph 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "Bargraph" 11 | ) 12 | 13 | type Settings struct { 14 | *cfg.Common 15 | } 16 | 17 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 18 | settings := Settings{ 19 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 20 | } 21 | 22 | return &settings 23 | } 24 | -------------------------------------------------------------------------------- /modules/buildkite/keyboard.go: -------------------------------------------------------------------------------- 1 | package buildkite 2 | 3 | func (widget *Widget) initializeKeyboardControls() { 4 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 5 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 6 | } 7 | -------------------------------------------------------------------------------- /modules/cds/favorites/keyboard.go: -------------------------------------------------------------------------------- 1 | package cdsfavorites 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | ) 6 | 7 | func (widget *Widget) initializeKeyboardControls() { 8 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 9 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 10 | 11 | widget.SetKeyboardChar("j", widget.Next, "Select next workflow") 12 | widget.SetKeyboardChar("k", widget.Prev, "Select previous workflow") 13 | widget.SetKeyboardChar("l", widget.NextSource, "Select next source") 14 | widget.SetKeyboardChar("h", widget.PrevSource, "Select previous source") 15 | widget.SetKeyboardChar("o", widget.openWorkflow, "Open workflow in browser") 16 | 17 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next workflow") 18 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous workflow") 19 | widget.SetKeyboardKey(tcell.KeyRight, widget.NextSource, "Select next source") 20 | widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevSource, "Select previous source") 21 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openWorkflow, "Open workflow in browser") 22 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 23 | } 24 | -------------------------------------------------------------------------------- /modules/cds/favorites/settings.go: -------------------------------------------------------------------------------- 1 | package cdsfavorites 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | "github.com/wtfutil/wtf/utils" 9 | ) 10 | 11 | const ( 12 | defaultFocusable = true 13 | defaultTitle = "CDS Favorites" 14 | ) 15 | 16 | // Settings defines the configuration properties for this module 17 | type Settings struct { 18 | *cfg.Common 19 | 20 | token string `help:"Your CDS API token."` 21 | apiURL string `help:"Your CDS API URL."` 22 | uiURL string 23 | hideTags []string `help:"Hide some workflow tags."` 24 | } 25 | 26 | // NewSettingsFromYAML creates a new settings instance from a YAML config block 27 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 28 | settings := Settings{ 29 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 30 | 31 | token: ymlConfig.UString("token", ymlConfig.UString("token", os.Getenv("CDS_TOKEN"))), 32 | apiURL: ymlConfig.UString("apiURL", os.Getenv("CDS_API_URL")), 33 | hideTags: utils.ToStrs(ymlConfig.UList("hideTags")), 34 | } 35 | 36 | settings.SetDocumentationPath("cds/favorites") 37 | 38 | return &settings 39 | } 40 | -------------------------------------------------------------------------------- /modules/cds/queue/keyboard.go: -------------------------------------------------------------------------------- 1 | package cdsqueue 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | ) 6 | 7 | func (widget *Widget) initializeKeyboardControls() { 8 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 9 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 10 | 11 | widget.SetKeyboardChar("j", widget.Next, "Select next workflow") 12 | widget.SetKeyboardChar("k", widget.Prev, "Select previous workflow") 13 | widget.SetKeyboardChar("l", widget.NextSource, "Select next filter") 14 | widget.SetKeyboardChar("h", widget.PrevSource, "Select previous filter") 15 | widget.SetKeyboardChar("o", widget.openWorkflow, "Open workflow in browser") 16 | 17 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next workflow") 18 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous workflow") 19 | widget.SetKeyboardKey(tcell.KeyRight, widget.NextSource, "Select next filter") 20 | widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevSource, "Select previous filter") 21 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openWorkflow, "Open workflow in browser") 22 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 23 | } 24 | -------------------------------------------------------------------------------- /modules/cds/queue/settings.go: -------------------------------------------------------------------------------- 1 | package cdsqueue 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = true 12 | defaultTitle = "CDS Queue" 13 | ) 14 | 15 | // Settings defines the configuration properties for this module 16 | type Settings struct { 17 | *cfg.Common 18 | 19 | token string `help:"Your CDS API token."` 20 | apiURL string `help:"Your CDS API URL."` 21 | uiURL string 22 | } 23 | 24 | // NewSettingsFromYAML creates a new settings instance from a YAML config block 25 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 26 | settings := Settings{ 27 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 28 | 29 | token: ymlConfig.UString("token", ymlConfig.UString("token", os.Getenv("CDS_TOKEN"))), 30 | apiURL: ymlConfig.UString("apiURL", os.Getenv("CDS_API_URL")), 31 | } 32 | 33 | settings.SetDocumentationPath("cds/queue") 34 | 35 | return &settings 36 | } 37 | -------------------------------------------------------------------------------- /modules/cds/status/keyboard.go: -------------------------------------------------------------------------------- 1 | package cdsstatus 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | ) 6 | 7 | func (widget *Widget) initializeKeyboardControls() { 8 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 9 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 10 | 11 | widget.SetKeyboardChar("j", widget.Next, "Select next line") 12 | widget.SetKeyboardChar("k", widget.Prev, "Select previous line") 13 | widget.SetKeyboardChar("o", widget.openWorkflow, "Open status in browser") 14 | 15 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next line") 16 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous line") 17 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openWorkflow, "Open status in browser") 18 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 19 | } 20 | -------------------------------------------------------------------------------- /modules/cds/status/settings.go: -------------------------------------------------------------------------------- 1 | package cdsstatus 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = true 12 | defaultTitle = "CDS Status" 13 | ) 14 | 15 | // Settings defines the configuration properties for this module 16 | type Settings struct { 17 | *cfg.Common 18 | 19 | token string `help:"Your CDS API token."` 20 | apiURL string `help:"Your CDS API URL."` 21 | uiURL string 22 | } 23 | 24 | // NewSettingsFromYAML creates a new settings instance from a YAML config block 25 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 26 | settings := Settings{ 27 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 28 | 29 | token: ymlConfig.UString("token", ymlConfig.UString("token", os.Getenv("CDS_TOKEN"))), 30 | apiURL: ymlConfig.UString("apiURL", os.Getenv("CDS_API_URL")), 31 | } 32 | 33 | settings.SetDocumentationPath("cds/status") 34 | 35 | return &settings 36 | } 37 | -------------------------------------------------------------------------------- /modules/circleci/build.go: -------------------------------------------------------------------------------- 1 | package circleci 2 | 3 | type Build struct { 4 | AuthorEmail string `json:"author_email"` 5 | AuthorName string `json:"author_name"` 6 | Branch string `json:"branch"` 7 | BuildNum int `json:"build_num"` 8 | Reponame string `json:"reponame"` 9 | Status string `json:"status"` 10 | } 11 | -------------------------------------------------------------------------------- /modules/circleci/settings.go: -------------------------------------------------------------------------------- 1 | package circleci 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = false 12 | defaultTitle = "CircleCI" 13 | ) 14 | 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | apiKey string `help:"Your CircleCI API token."` 19 | numberOfBuilds int `help:"The number of build, 10 by default"` 20 | } 21 | 22 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 23 | 24 | settings := Settings{ 25 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 26 | 27 | apiKey: ymlConfig.UString("apiKey", ymlConfig.UString("apikey", os.Getenv("WTF_CIRCLE_API_KEY"))), 28 | numberOfBuilds: ymlConfig.UInt("numberOfBuilds", 10), 29 | } 30 | 31 | cfg.ModuleSecret(name, globalConfig, &settings.apiKey).Load() 32 | 33 | return &settings 34 | } 35 | -------------------------------------------------------------------------------- /modules/clocks/clock.go: -------------------------------------------------------------------------------- 1 | package clocks 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | ) 7 | 8 | type Clock struct { 9 | Label string 10 | Location *time.Location 11 | } 12 | 13 | func NewClock(label string, timeLoc *time.Location) Clock { 14 | clock := Clock{ 15 | Label: label, 16 | Location: timeLoc, 17 | } 18 | 19 | return clock 20 | } 21 | 22 | func BuildClock(label string, location string) (clock Clock, err error) { 23 | timeLoc, err := time.LoadLocation(sanitizeLocation(location)) 24 | if err != nil { 25 | return Clock{}, err 26 | } 27 | return NewClock(label, timeLoc), nil 28 | } 29 | 30 | func (clock *Clock) Date(dateFormat string) string { 31 | return clock.LocalTime().Format(dateFormat) 32 | } 33 | 34 | func (clock *Clock) LocalTime() time.Time { 35 | return clock.ToLocal(time.Now()) 36 | } 37 | 38 | func (clock *Clock) ToLocal(t time.Time) time.Time { 39 | return t.In(clock.Location) 40 | } 41 | 42 | func (clock *Clock) Time(timeFormat string) string { 43 | return clock.LocalTime().Format(timeFormat) 44 | } 45 | 46 | func sanitizeLocation(locStr string) string { 47 | return strings.ReplaceAll(locStr, " ", "_") 48 | } 49 | -------------------------------------------------------------------------------- /modules/clocks/clock_collection.go: -------------------------------------------------------------------------------- 1 | package clocks 2 | 3 | import ( 4 | "sort" 5 | "time" 6 | ) 7 | 8 | type ClockCollection struct { 9 | Clocks []Clock 10 | } 11 | 12 | func (clocks *ClockCollection) Sorted(sortOrder string) []Clock { 13 | 14 | switch sortOrder { 15 | case "natural": 16 | // do nothing 17 | case "chronological": 18 | clocks.SortedChronologically() 19 | case "reversechronological": 20 | clocks.SortedReverseChronologically() 21 | default: 22 | clocks.SortedAlphabetically() 23 | } 24 | 25 | return clocks.Clocks 26 | } 27 | 28 | func (clocks *ClockCollection) SortedAlphabetically() { 29 | sort.Slice(clocks.Clocks, func(i, j int) bool { 30 | clock := clocks.Clocks[i] 31 | other := clocks.Clocks[j] 32 | 33 | return clock.Label < other.Label 34 | }) 35 | } 36 | 37 | func (clocks *ClockCollection) SortedChronologically() { 38 | now := time.Now() 39 | sort.Slice(clocks.Clocks, func(i, j int) bool { 40 | clock := clocks.Clocks[i] 41 | other := clocks.Clocks[j] 42 | 43 | return clock.ToLocal(now).String() < other.ToLocal(now).String() 44 | }) 45 | } 46 | 47 | func (clocks *ClockCollection) SortedReverseChronologically() { 48 | now := time.Now() 49 | sort.Slice(clocks.Clocks, func(i, j int) bool { 50 | clock := clocks.Clocks[i] 51 | other := clocks.Clocks[j] 52 | 53 | return clock.ToLocal(now).String() > other.ToLocal(now).String() 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /modules/clocks/display.go: -------------------------------------------------------------------------------- 1 | package clocks 2 | 3 | import "fmt" 4 | 5 | func (widget *Widget) display(clocks []Clock, dateFormat string, timeFormat string) { 6 | str := "" 7 | 8 | locationWidth := 12 9 | for _, clock := range clocks { 10 | if len(clock.Label) > locationWidth { 11 | locationWidth = len(clock.Label) + 2 12 | } 13 | } 14 | 15 | if len(clocks) == 0 { 16 | str = fmt.Sprintf("\n%s", " no timezone data available") 17 | } else { 18 | for idx, clock := range clocks { 19 | str += fmt.Sprintf( 20 | " [%s]%-*s %-10s %7s[white]\n", 21 | widget.CommonSettings().RowColor(idx), 22 | locationWidth, 23 | clock.Label, 24 | clock.Time(timeFormat), 25 | clock.Date(dateFormat), 26 | ) 27 | } 28 | } 29 | 30 | widget.Redraw(func() (string, string, bool) { return widget.CommonSettings().Title, str, false }) 31 | } 32 | -------------------------------------------------------------------------------- /modules/clocks/widget.go: -------------------------------------------------------------------------------- 1 | package clocks 2 | 3 | import ( 4 | "github.com/rivo/tview" 5 | "github.com/wtfutil/wtf/view" 6 | ) 7 | 8 | type Widget struct { 9 | view.TextWidget 10 | 11 | clockColl ClockCollection 12 | dateFormat string 13 | timeFormat string 14 | settings *Settings 15 | } 16 | 17 | func NewWidget(tviewApp *tview.Application, redrawChan chan bool, settings *Settings) *Widget { 18 | widget := Widget{ 19 | TextWidget: view.NewTextWidget(tviewApp, redrawChan, nil, settings.Common), 20 | 21 | settings: settings, 22 | dateFormat: settings.dateFormat, 23 | timeFormat: settings.timeFormat, 24 | } 25 | 26 | widget.clockColl = widget.buildClockCollection() 27 | 28 | return &widget 29 | } 30 | 31 | /* -------------------- Exported Functions -------------------- */ 32 | 33 | // Refresh updates the onscreen contents of the widget 34 | func (widget *Widget) Refresh() { 35 | sortedClocks := widget.clockColl.Sorted(widget.settings.sort) 36 | widget.display(sortedClocks, widget.dateFormat, widget.timeFormat) 37 | } 38 | 39 | /* -------------------- Unexported Functions -------------------- */ 40 | 41 | func (widget *Widget) buildClockCollection() ClockCollection { 42 | clockColl := ClockCollection{} 43 | 44 | clockColl.Clocks = widget.settings.locations 45 | 46 | return clockColl 47 | } 48 | -------------------------------------------------------------------------------- /modules/covid/cases.go: -------------------------------------------------------------------------------- 1 | package covid 2 | 3 | // Cases holds the latest cases 4 | type Cases struct { 5 | Latest Latest `json:"latest"` 6 | } 7 | 8 | // Latest holds the number of global confirmed cases and deaths due to Covid 9 | type Latest struct { 10 | Confirmed int `json:"confirmed"` 11 | Deaths int `json:"deaths"` 12 | // Not currently used but holds information about the country 13 | Locations []interface{} `json:"locations,omitempty"` 14 | } 15 | -------------------------------------------------------------------------------- /modules/covid/cases_test.go: -------------------------------------------------------------------------------- 1 | package covid 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func Test_CasesInclude(t *testing.T) { 9 | // The api does not seem to return the correct recovered numbers 10 | responseBody := `{"latest":{"confirmed":3093619,"deaths":73018,"recovered":0},"locations":[]}` 11 | latestData := Cases{} 12 | _ = json.Unmarshal([]byte(responseBody), &latestData) 13 | expectedConfirmed := 3093619 14 | expectedDeaths := 73018 15 | actualConfirmed := latestData.Latest.Confirmed 16 | actualDeaths := latestData.Latest.Deaths 17 | 18 | if expectedConfirmed != actualConfirmed { 19 | t.Errorf("\nexpected: %v\n got: %v", expectedConfirmed, actualConfirmed) 20 | } 21 | if expectedDeaths != actualDeaths { 22 | t.Errorf("\nexpected: %v\n got: %v", expectedDeaths, actualDeaths) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /modules/covid/settings.go: -------------------------------------------------------------------------------- 1 | package covid 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "Covid tracker" 11 | ) 12 | 13 | // Settings is the struct for this module's settings 14 | type Settings struct { 15 | *cfg.Common 16 | 17 | countries []interface{} `help:"Countries (codes) from which to retrieve stats."` 18 | } 19 | 20 | // NewSettingsFromYAML returns the settings from the config yaml file 21 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 22 | 23 | settings := Settings{ 24 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 25 | 26 | // List of countries to retrieve stats from 27 | countries: ymlConfig.UList("countries"), 28 | } 29 | 30 | return &settings 31 | } 32 | -------------------------------------------------------------------------------- /modules/cryptocurrency/bittrex/bittrex.go: -------------------------------------------------------------------------------- 1 | package bittrex 2 | 3 | type summaryList struct { 4 | items []*bCurrency 5 | } 6 | 7 | // Base Currency 8 | type bCurrency struct { 9 | name string 10 | displayName string 11 | markets []*mCurrency 12 | } 13 | 14 | // Market Currency 15 | type mCurrency struct { 16 | name string 17 | summaryInfo 18 | } 19 | 20 | type summaryInfo struct { 21 | Low string 22 | High string 23 | Volume string 24 | Last string 25 | OpenSellOrders string 26 | OpenBuyOrders string 27 | } 28 | 29 | type summaryResponse struct { 30 | Success bool `json:"success"` 31 | Message string `json:"message"` 32 | Result []struct { 33 | MarketName string `json:"MarketName"` 34 | High float64 `json:"High"` 35 | Low float64 `json:"Low"` 36 | Last float64 `json:"Last"` 37 | Volume float64 `json:"Volume"` 38 | OpenSellOrders int `json:"OpenSellOrders"` 39 | OpenBuyOrders int `json:"OpenBuyOrders"` 40 | } `json:"result"` 41 | } 42 | 43 | func (list *summaryList) addSummaryItem(name, displayName string, marketList []*mCurrency) { 44 | list.items = append(list.items, &bCurrency{ 45 | name: name, 46 | displayName: displayName, 47 | markets: marketList, 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /modules/cryptocurrency/blockfolio/settings.go: -------------------------------------------------------------------------------- 1 | package blockfolio 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "Blockfolio" 11 | ) 12 | 13 | type colors struct { 14 | name string 15 | grows string 16 | drop string 17 | } 18 | 19 | type Settings struct { 20 | *cfg.Common 21 | 22 | colors 23 | 24 | deviceToken string 25 | displayHoldings bool 26 | } 27 | 28 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 29 | 30 | settings := Settings{ 31 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 32 | 33 | deviceToken: ymlConfig.UString("device_token"), 34 | displayHoldings: ymlConfig.UBool("displayHoldings", true), 35 | } 36 | 37 | settings.SetDocumentationPath("cryptocurrencies/blockfolio") 38 | 39 | return &settings 40 | } 41 | -------------------------------------------------------------------------------- /modules/cryptocurrency/cryptolive/price/price.go: -------------------------------------------------------------------------------- 1 | package price 2 | 3 | type list struct { 4 | items []*fromCurrency 5 | } 6 | 7 | type fromCurrency struct { 8 | name string 9 | displayName string 10 | to []*toCurrency 11 | } 12 | 13 | type toCurrency struct { 14 | name string 15 | price float32 16 | } 17 | 18 | type cResponse map[string]float32 19 | 20 | /* -------------------- Unexported Functions -------------------- */ 21 | 22 | func (l *list) addItem(name string, displayName string, to []*toCurrency) { 23 | l.items = append(l.items, &fromCurrency{ 24 | name: name, 25 | displayName: displayName, 26 | to: to, 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /modules/cryptocurrency/cryptolive/toplist/toplist.go: -------------------------------------------------------------------------------- 1 | package toplist 2 | 3 | type cList struct { 4 | items []*fCurrency 5 | } 6 | 7 | type fCurrency struct { 8 | name, displayName string 9 | limit int 10 | to []*tCurrency 11 | } 12 | 13 | type tCurrency struct { 14 | name string 15 | info []tInfo 16 | } 17 | 18 | type tInfo struct { 19 | exchange string 20 | volume24h, volume24hTo float32 21 | } 22 | 23 | type responseInterface struct { 24 | Response string `json:"Response"` 25 | Data []struct { 26 | Exchange string `json:"exchange"` 27 | FromSymbol string `json:"fromSymbol"` 28 | ToSymbol string `json:"toSymbol"` 29 | Volume24h float32 `json:"volume24h"` 30 | Volume24hTo float32 `json:"volume24hTo"` 31 | } `json:"Data"` 32 | } 33 | 34 | func (list *cList) addItem(name, displayName string, limit int, to []*tCurrency) { 35 | list.items = append(list.items, &fCurrency{ 36 | name: name, 37 | displayName: displayName, 38 | limit: limit, 39 | to: to, 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /modules/cryptocurrency/mempool/settings.go: -------------------------------------------------------------------------------- 1 | package mempool 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "mempool" 11 | ) 12 | 13 | // Settings defines the configuration properties for this module 14 | type Settings struct { 15 | common *cfg.Common 16 | 17 | // Define your settings attributes here 18 | } 19 | 20 | // NewSettingsFromYAML creates a new settings instance from a YAML config block 21 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 22 | settings := Settings{ 23 | common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 24 | 25 | // Configure your settings attributes here. See http://github.com/olebedev/config for type details 26 | } 27 | 28 | return &settings 29 | } 30 | -------------------------------------------------------------------------------- /modules/datadog/client.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "github.com/wtfutil/wtf/utils" 5 | datadog "github.com/zorkian/go-datadog-api" 6 | ) 7 | 8 | // Monitors returns a list of Datadog monitors 9 | func (widget *Widget) Monitors() ([]datadog.Monitor, error) { 10 | client := datadog.NewClient( 11 | widget.settings.apiKey, 12 | widget.settings.applicationKey, 13 | ) 14 | 15 | tags := utils.ToStrs(widget.settings.tags) 16 | 17 | monitors, err := client.GetMonitorsByTags(tags) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | return monitors, nil 23 | } 24 | -------------------------------------------------------------------------------- /modules/datadog/keyboard.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | ) 6 | 7 | func (widget *Widget) initializeKeyboardControls() { 8 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 9 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 10 | 11 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 12 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 13 | widget.SetKeyboardChar("o", widget.openItem, "Open item in browser") 14 | 15 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 16 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 17 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 18 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openItem, "Open item in browser") 19 | } 20 | -------------------------------------------------------------------------------- /modules/datadog/settings.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = true 12 | defaultTitle = "DataDog" 13 | ) 14 | 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | apiKey string `help:"Your Datadog API key."` 19 | applicationKey string `help:"Your Datadog Application key."` 20 | tags []interface{} `help:"Array of tags you want to query monitors by."` 21 | } 22 | 23 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 24 | 25 | settings := Settings{ 26 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 27 | 28 | apiKey: ymlConfig.UString("apiKey", ymlConfig.UString("apikey", os.Getenv("WTF_DATADOG_API_KEY"))), 29 | applicationKey: ymlConfig.UString("applicationKey", os.Getenv("WTF_DATADOG_APPLICATION_KEY")), 30 | tags: ymlConfig.UList("monitors.tags"), 31 | } 32 | 33 | cfg.ModuleSecret(name+"-api", globalConfig, &settings.apiKey).Load() 34 | cfg.ModuleSecret(name+"-app", globalConfig, &settings.applicationKey).Load() 35 | 36 | return &settings 37 | } 38 | -------------------------------------------------------------------------------- /modules/devto/keyboard.go: -------------------------------------------------------------------------------- 1 | package devto 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("d", widget.Next, "Select next item") 10 | widget.SetKeyboardChar("a", widget.Prev, "Select previous item") 11 | widget.SetKeyboardChar("o", widget.openStory, "Open story in browser") 12 | 13 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 14 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 15 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openStory, "Open story in browser") 16 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 17 | } 18 | -------------------------------------------------------------------------------- /modules/devto/settings.go: -------------------------------------------------------------------------------- 1 | package devto 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | 6 | "github.com/wtfutil/wtf/cfg" 7 | ) 8 | 9 | const ( 10 | defaultFocusable = true 11 | defaultTitle = "dev.to | News Feed" 12 | ) 13 | 14 | // Settings defines the configuration options for this module 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | numberOfArticles int `help:"Number of stories to show. Default is 10" optional:"true"` 19 | contentTag string `help:"List articles from a specific tag. Default is empty" optional:"true"` 20 | contentUsername string `help:"List articles from a specific user. Default is empty" optional:"true"` 21 | contentState string `help:"Order the feed by fresh/rising. Default is rising" optional:"true"` 22 | } 23 | 24 | // NewSettingsFromYAML creates and returns an instance of Settings with configuration options populated 25 | func NewSettingsFromYAML(name string, yamlConfig *config.Config, globalConfig *config.Config) *Settings { 26 | settings := Settings{ 27 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, yamlConfig, globalConfig), 28 | 29 | numberOfArticles: yamlConfig.UInt("numberOfArticles", 10), 30 | contentTag: yamlConfig.UString("contentTag", ""), 31 | contentUsername: yamlConfig.UString("contentUsername", ""), 32 | contentState: yamlConfig.UString("contentState", ""), 33 | } 34 | 35 | return &settings 36 | } 37 | -------------------------------------------------------------------------------- /modules/digitalclock/display.go: -------------------------------------------------------------------------------- 1 | package digitalclock 2 | 3 | import "strings" 4 | 5 | func mergeLines(outString []string) string { 6 | return strings.Join(outString, "\n") 7 | } 8 | 9 | func renderWidget(widgetSettings Settings) string { 10 | var outputStrings []string 11 | 12 | clockString, needBorder := renderClock(widgetSettings) 13 | if needBorder { 14 | outputStrings = append(outputStrings, mergeLines([]string{"", clockString, ""})) 15 | } else { 16 | outputStrings = append(outputStrings, clockString) 17 | } 18 | 19 | if widgetSettings.withDate { 20 | outputStrings = append(outputStrings, getDate(widgetSettings.dateFormat, widgetSettings.withDatePrefix)) 21 | } 22 | 23 | if widgetSettings.withUTC { 24 | outputStrings = append(outputStrings, getUTC()) 25 | } 26 | 27 | if widgetSettings.withEpoch { 28 | outputStrings = append(outputStrings, getEpoch()) 29 | } 30 | 31 | return mergeLines(outputStrings) 32 | } 33 | 34 | func (widget *Widget) display() { 35 | widget.Redraw(func() (string, string, bool) { 36 | title := widget.CommonSettings().Title 37 | if widget.settings.dateTitle { 38 | title = getDate(widget.settings.dateFormat, false) 39 | } 40 | return title, renderWidget(*widget.settings), false 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /modules/digitalclock/widget.go: -------------------------------------------------------------------------------- 1 | package digitalclock 2 | 3 | import ( 4 | "github.com/rivo/tview" 5 | "github.com/wtfutil/wtf/view" 6 | ) 7 | 8 | // Widget is a text widget struct to hold info about the current widget 9 | type Widget struct { 10 | view.TextWidget 11 | 12 | settings *Settings 13 | } 14 | 15 | // NewWidget creates a new widget using settings 16 | func NewWidget(tviewApp *tview.Application, redrawChan chan bool, settings *Settings) *Widget { 17 | widget := Widget{ 18 | TextWidget: view.NewTextWidget(tviewApp, redrawChan, nil, settings.Common), 19 | 20 | settings: settings, 21 | } 22 | if settings.centerAlign { 23 | widget.View.SetTextAlign(tview.AlignCenter) 24 | } 25 | 26 | return &widget 27 | } 28 | 29 | /* -------------------- Exported Functions -------------------- */ 30 | 31 | // Refresh updates the onscreen contents of the widget 32 | func (widget *Widget) Refresh() { 33 | widget.display() 34 | } 35 | -------------------------------------------------------------------------------- /modules/digitalocean/droplet.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/digitalocean/godo" 7 | "github.com/wtfutil/wtf/utils" 8 | ) 9 | 10 | // Droplet represents WTF's view of a DigitalOcean droplet 11 | type Droplet struct { 12 | godo.Droplet 13 | 14 | Image godo.Image 15 | Region godo.Region 16 | } 17 | 18 | // NewDroplet creates and returns an instance of Droplet 19 | func NewDroplet(doDroplet godo.Droplet) *Droplet { 20 | return &Droplet{ 21 | doDroplet, 22 | *doDroplet.Image, 23 | *doDroplet.Region, 24 | } 25 | } 26 | 27 | /* -------------------- Exported Functions -------------------- */ 28 | 29 | // StringValueForProperty returns a string value for the given column 30 | func (drop *Droplet) StringValueForProperty(propName string) (string, error) { 31 | // Figure out if we should forward this property to a sub-object 32 | // Lets us support "Region.Name" column definitions 33 | split := strings.Split(propName, ".") 34 | 35 | switch split[0] { 36 | case "Image": 37 | return utils.StringValueForProperty(drop.Image, split[1]) 38 | case "Region": 39 | return utils.StringValueForProperty(drop.Region, split[1]) 40 | default: 41 | return utils.StringValueForProperty(drop, propName) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /modules/digitalocean/keyboard.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("?", widget.showInfo, "Show info about the selected droplet") 10 | 11 | widget.SetKeyboardChar("b", widget.dropletRestart, "Reboot the selected droplet") 12 | widget.SetKeyboardChar("j", widget.Prev, "Select previous item") 13 | widget.SetKeyboardChar("k", widget.Next, "Select next item") 14 | widget.SetKeyboardChar("p", widget.dropletEnabledPrivateNetworking, "Enable private networking for the selected drople") 15 | widget.SetKeyboardChar("s", widget.dropletShutDown, "Shut down the selected droplet") 16 | widget.SetKeyboardChar("u", widget.Unselect, "Clear selection") 17 | 18 | widget.SetKeyboardKey(tcell.KeyCtrlD, widget.dropletDestroy, "Destroy the selected droplet") 19 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 20 | widget.SetKeyboardKey(tcell.KeyEnter, widget.showInfo, "Show info about the selected droplet") 21 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 22 | } 23 | -------------------------------------------------------------------------------- /modules/docker/example-conf.yml: -------------------------------------------------------------------------------- 1 | wtf: 2 | colors: 3 | # background: black 4 | # foreground: blue 5 | border: 6 | focusable: darkslateblue 7 | focused: orange 8 | normal: gray 9 | checked: yellow 10 | highlight: 11 | fore: black 12 | back: gray 13 | rows: 14 | even: yellow 15 | odd: white 16 | grid: 17 | # How _wide_ the columns are, in terminal characters. In this case we have 18 | # four columns, each of which are 35 characters wide. 19 | # columns: [50, ] 20 | # How _high_ the rows are, in terminal lines. In this case we have four rows 21 | # that support ten line of text and one of four. 22 | # rows: [50] 23 | refreshInterval: 1 24 | openFileUtil: "open" 25 | mods: 26 | docker: 27 | type: docker 28 | title: "💻" 29 | enabled: true 30 | position: 31 | top: 0 32 | left: 0 33 | height: 3 34 | width: 3 35 | refreshInterval: 1 36 | labelColor: lightblue 37 | -------------------------------------------------------------------------------- /modules/docker/settings.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "docker" 11 | ) 12 | 13 | // Settings defines the configuration options for this module 14 | type Settings struct { 15 | *cfg.Common 16 | 17 | labelColor string 18 | } 19 | 20 | // NewSettingsFromYAML creates and returns an instance of Settings with configuration options populated 21 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 22 | settings := Settings{ 23 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 24 | labelColor: ymlConfig.UString("labelColor", "white"), 25 | } 26 | 27 | return &settings 28 | } 29 | -------------------------------------------------------------------------------- /modules/docker/utils.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "reflect" 7 | "strconv" 8 | ) 9 | 10 | func padSlice(padLeft bool, slice interface{}, getter func(i int) string, setter func(i int, newVal string)) { 11 | rv := reflect.ValueOf(slice) 12 | length := rv.Len() 13 | maxLen := 0 14 | for i := 0; i < length; i++ { 15 | val := getter(i) 16 | maxLen = int(math.Max(float64(len(val)), float64(maxLen))) 17 | } 18 | 19 | sign := "-" 20 | if padLeft { 21 | sign = "" 22 | } 23 | 24 | for i := 0; i < length; i++ { 25 | val := getter(i) 26 | val = fmt.Sprintf("%"+sign+strconv.Itoa(maxLen)+"s", val) 27 | setter(i, val) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modules/feedreader/keyboard.go: -------------------------------------------------------------------------------- 1 | package feedreader 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 10 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 11 | widget.SetKeyboardChar("o", widget.openStory, "Open story in browser") 12 | widget.SetKeyboardChar("t", widget.toggleDisplayText, "Toggle display between title, link and title+content") 13 | 14 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 15 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 16 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openStory, "Open story in browser") 17 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 18 | } 19 | -------------------------------------------------------------------------------- /modules/football/client.go: -------------------------------------------------------------------------------- 1 | package football 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | var ( 9 | footballAPIUrl = "https://api.football-data.org/v2" 10 | ) 11 | 12 | type leagueInfo struct { 13 | id int 14 | caption string 15 | } 16 | 17 | type Client struct { 18 | apiKey string 19 | } 20 | 21 | func NewClient(apiKey string) *Client { 22 | client := Client{ 23 | apiKey: apiKey, 24 | } 25 | 26 | return &client 27 | } 28 | 29 | func (client *Client) footballRequest(path string, id int) (*http.Response, error) { 30 | 31 | url := fmt.Sprintf("%s/competitions/%d/%s", footballAPIUrl, id, path) 32 | req, err := http.NewRequest("GET", url, http.NoBody) 33 | req.Header.Add("Accept", "application/json") 34 | req.Header.Add("Content-Type", "application/json") 35 | req.Header.Add("X-Auth-Token", client.apiKey) 36 | if err != nil { 37 | return nil, err 38 | } 39 | httpClient := &http.Client{} 40 | resp, err := httpClient.Do(req) 41 | if err != nil { 42 | return nil, err 43 | } 44 | defer func() { _ = resp.Body.Close() }() 45 | 46 | return resp, nil 47 | } 48 | -------------------------------------------------------------------------------- /modules/football/types.go: -------------------------------------------------------------------------------- 1 | package football 2 | 3 | type Team struct { 4 | Name string `json:"name"` 5 | } 6 | 7 | type LeagueStandings struct { 8 | Standings []struct { 9 | Table []Table `json:"table"` 10 | } `json:"standings"` 11 | } 12 | 13 | type Table struct { 14 | Draw int `json:"draw"` 15 | GoalDifference int `json:"goalDifference"` 16 | Lost int `json:"lost"` 17 | Won int `json:"won"` 18 | PlayedGames int `json:"playedGames"` 19 | Points int `json:"points"` 20 | Position int `json:"position"` 21 | Team Team `json:"team"` 22 | } 23 | 24 | type LeagueFixtuers struct { 25 | Matches []Matches `json:"matches"` 26 | } 27 | 28 | type Matches struct { 29 | AwayTeam Team `json:"awayTeam"` 30 | HomeTeam Team `json:"homeTeam"` 31 | Score Score `json:"score"` 32 | Stage string `json:"stage"` 33 | Status string `json:"status"` 34 | Date string `json:"utcDate"` 35 | } 36 | 37 | type Score struct { 38 | FullTime ScoreByTime `json:"fullTime"` 39 | HalfTime ScoreByTime `json:"halfTime"` 40 | Winner string `json:"winner"` 41 | } 42 | 43 | type ScoreByTime struct { 44 | AwayTeam int `json:"awayTeam"` 45 | HomeTeam int `json:"homeTeam"` 46 | } 47 | -------------------------------------------------------------------------------- /modules/football/util.go: -------------------------------------------------------------------------------- 1 | package football 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | "github.com/olekukonko/tablewriter" 10 | ) 11 | 12 | func createTable(header []string, buf *bytes.Buffer) *tablewriter.Table { 13 | 14 | table := tablewriter.NewWriter(buf) 15 | if len(header) != 0 { 16 | table.SetHeader(header) 17 | } 18 | table.SetBorder(false) 19 | table.SetCenterSeparator(" ") 20 | table.SetColumnSeparator(" ") 21 | table.SetRowSeparator(" ") 22 | table.SetAlignment(tablewriter.ALIGN_LEFT) 23 | 24 | return table 25 | } 26 | 27 | func parseDateString(d string) string { 28 | 29 | return fmt.Sprintf("🕙 %s", strings.Replace(d, "T", " ", 1)) 30 | } 31 | 32 | func getDateString(offset int) string { 33 | 34 | today := time.Now() 35 | return today.AddDate(0, 0, offset).Format("2006-01-02") 36 | 37 | } 38 | -------------------------------------------------------------------------------- /modules/gcal/widget.go: -------------------------------------------------------------------------------- 1 | package gcal 2 | 3 | import ( 4 | "github.com/rivo/tview" 5 | "github.com/wtfutil/wtf/view" 6 | ) 7 | 8 | type Widget struct { 9 | view.TextWidget 10 | 11 | calEvents []*CalEvent 12 | err error 13 | settings *Settings 14 | tviewApp *tview.Application 15 | } 16 | 17 | func NewWidget(tviewApp *tview.Application, redrawChan chan bool, settings *Settings) *Widget { 18 | widget := Widget{ 19 | TextWidget: view.NewTextWidget(tviewApp, redrawChan, nil, settings.Common), 20 | 21 | tviewApp: tviewApp, 22 | settings: settings, 23 | } 24 | 25 | return &widget 26 | } 27 | 28 | /* -------------------- Exported Functions -------------------- */ 29 | 30 | func (widget *Widget) Disable() { 31 | widget.TextWidget.Disable() 32 | } 33 | 34 | func (widget *Widget) Refresh() { 35 | if isAuthenticated(widget.settings.email) { 36 | widget.fetchAndDisplayEvents() 37 | return 38 | } 39 | 40 | widget.tviewApp.Suspend(widget.authenticate) 41 | widget.Refresh() 42 | } 43 | 44 | /* -------------------- Unexported Functions -------------------- */ 45 | 46 | func (widget *Widget) fetchAndDisplayEvents() { 47 | calEvents, err := widget.Fetch() 48 | if err != nil { 49 | widget.err = err 50 | widget.calEvents = []*CalEvent{} 51 | } else { 52 | widget.err = nil 53 | widget.calEvents = calEvents 54 | } 55 | 56 | widget.display() 57 | } 58 | -------------------------------------------------------------------------------- /modules/gerrit/keyboard.go: -------------------------------------------------------------------------------- 1 | package gerrit 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | ) 6 | 7 | func (widget *Widget) initializeKeyboardControls() { 8 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 9 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 10 | 11 | widget.SetKeyboardChar("h", widget.prevProject, "Select previous project") 12 | widget.SetKeyboardChar("l", widget.nextProject, "Select next project") 13 | widget.SetKeyboardChar("j", widget.nextReview, "Select next review") 14 | widget.SetKeyboardChar("k", widget.prevReview, "Select previous review") 15 | 16 | widget.SetKeyboardKey(tcell.KeyLeft, widget.prevProject, "Select previous project") 17 | widget.SetKeyboardKey(tcell.KeyRight, widget.nextProject, "Select next project") 18 | widget.SetKeyboardKey(tcell.KeyDown, widget.nextReview, "Select next review") 19 | widget.SetKeyboardKey(tcell.KeyUp, widget.prevReview, "Select previous review") 20 | widget.SetKeyboardKey(tcell.KeyEsc, widget.unselect, "Clear selection") 21 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openReview, "Open review in browser") 22 | } 23 | -------------------------------------------------------------------------------- /modules/git/keyboard.go: -------------------------------------------------------------------------------- 1 | package git 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("l", widget.NextSource, "Select next source") 10 | widget.SetKeyboardChar("h", widget.PrevSource, "Select previous source") 11 | widget.SetKeyboardChar("p", widget.Pull, "Pull repo") 12 | widget.SetKeyboardChar("c", widget.Checkout, "Checkout branch") 13 | 14 | widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevSource, "Select previous source") 15 | widget.SetKeyboardKey(tcell.KeyRight, widget.NextSource, "Select next source") 16 | } 17 | -------------------------------------------------------------------------------- /modules/git/variables.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package git 4 | 5 | const ( 6 | __go_cmd = "git" 7 | ) 8 | -------------------------------------------------------------------------------- /modules/git/variables_win.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package git 4 | 5 | const ( 6 | __go_cmd = "git.exe" 7 | ) 8 | -------------------------------------------------------------------------------- /modules/github/keyboard.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | ) 6 | 7 | func (widget *Widget) initializeKeyboardControls() { 8 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 9 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 10 | 11 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 12 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 13 | widget.SetKeyboardChar("l", widget.NextSource, "Select next source") 14 | widget.SetKeyboardChar("h", widget.PrevSource, "Select previous source") 15 | widget.SetKeyboardChar("o", widget.openRepo, "Open item in browser") 16 | widget.SetKeyboardChar("p", widget.openPulls, "Open pull requests in browser") 17 | widget.SetKeyboardChar("i", widget.openIssues, "Open issues in browser") 18 | 19 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 20 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 21 | widget.SetKeyboardKey(tcell.KeyRight, widget.NextSource, "Select next source") 22 | widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevSource, "Select previous source") 23 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openPr, "Open PR in browser") 24 | widget.SetKeyboardKey(tcell.KeyInsert, widget.openRepo, "Open item in browser") 25 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 26 | } 27 | -------------------------------------------------------------------------------- /modules/gitlab/keyboard.go: -------------------------------------------------------------------------------- 1 | package gitlab 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | ) 6 | 7 | func (widget *Widget) initializeKeyboardControls() { 8 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 9 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 10 | 11 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 12 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 13 | widget.SetKeyboardChar("l", widget.NextSource, "Select next project") 14 | widget.SetKeyboardChar("h", widget.PrevSource, "Select previous project") 15 | widget.SetKeyboardChar("o", widget.openRepo, "Open item in browser") 16 | widget.SetKeyboardChar("p", widget.openPulls, "Open merge requests in browser") 17 | widget.SetKeyboardChar("i", widget.openIssues, "Open issues in browser") 18 | 19 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 20 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 21 | widget.SetKeyboardKey(tcell.KeyRight, widget.NextSource, "Select next project") 22 | widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevSource, "Select previous project") 23 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openItemInBrowser, "Open item in browser") 24 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 25 | } 26 | -------------------------------------------------------------------------------- /modules/gitlabtodo/keyboard.go: -------------------------------------------------------------------------------- 1 | package gitlabtodo 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 10 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 11 | widget.SetKeyboardChar("o", widget.openTodo, "Open todo in browser") 12 | widget.SetKeyboardChar("x", widget.markAsDone, "Mark todo as done") 13 | 14 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 15 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 16 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openTodo, "Open todo in browser") 17 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 18 | } 19 | -------------------------------------------------------------------------------- /modules/gitlabtodo/settings.go: -------------------------------------------------------------------------------- 1 | package gitlabtodo 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = true 12 | defaultTitle = "GitLab Todos" 13 | ) 14 | 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | numberOfTodos int `help:"Defines number of stories to be displayed. Default is 10" optional:"true"` 19 | apiKey string `help:"A GitLab personal access token. Requires at least api access."` 20 | domain string `help:"Your GitLab corporate domain."` 21 | showProject bool `help:"Determines whether or not to show the project a given todo is for."` 22 | } 23 | 24 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 25 | 26 | settings := Settings{ 27 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 28 | 29 | numberOfTodos: ymlConfig.UInt("numberOfTodos", 10), 30 | apiKey: ymlConfig.UString("apiKey", os.Getenv("WTF_GITLAB_TOKEN")), 31 | domain: ymlConfig.UString("domain", "https://gitlab.com"), 32 | showProject: ymlConfig.UBool("showProject", true), 33 | } 34 | 35 | cfg.ModuleSecret(name, globalConfig, &settings.apiKey). 36 | Service(settings.domain).Load() 37 | 38 | return &settings 39 | } 40 | -------------------------------------------------------------------------------- /modules/gitter/gitter.go: -------------------------------------------------------------------------------- 1 | package gitter 2 | 3 | import "time" 4 | 5 | type Rooms struct { 6 | Results []Room `json:"results"` 7 | } 8 | 9 | type Room struct { 10 | ID string `json:"id"` 11 | Name string `json:"name"` 12 | URI string `json:"uri"` 13 | } 14 | 15 | type User struct { 16 | ID string `json:"id"` 17 | Username string `json:"username"` 18 | DisplayName string `json:"displayName"` 19 | } 20 | 21 | type Message struct { 22 | ID string `json:"id"` 23 | Text string `json:"text"` 24 | HTML string `json:"html"` 25 | Sent time.Time `json:"sent"` 26 | From User `json:"fromUser"` 27 | Unread bool `json:"unread"` 28 | } 29 | -------------------------------------------------------------------------------- /modules/gitter/keyboard.go: -------------------------------------------------------------------------------- 1 | package gitter 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 10 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 11 | 12 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 13 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 14 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 15 | } 16 | -------------------------------------------------------------------------------- /modules/gitter/settings.go: -------------------------------------------------------------------------------- 1 | package gitter 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = true 12 | defaultTitle = "Gitter" 13 | ) 14 | 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | apiToken string `help:"Your Gitter Personal Access Token."` 19 | numberOfMessages int `help:"Maximum number of (newest) messages to be displayed. Default is 10" optional:"true"` 20 | roomURI string `help:"The room you want to display." values:"Example: wtfutil/Lobby"` 21 | } 22 | 23 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 24 | 25 | settings := Settings{ 26 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 27 | 28 | apiToken: ymlConfig.UString("apiToken", os.Getenv("WTF_GITTER_API_TOKEN")), 29 | numberOfMessages: ymlConfig.UInt("numberOfMessages", 10), 30 | roomURI: ymlConfig.UString("roomUri", "wtfutil/Lobby"), 31 | } 32 | 33 | cfg.ModuleSecret(name, globalConfig, &settings.apiToken).Load() 34 | 35 | return &settings 36 | } 37 | -------------------------------------------------------------------------------- /modules/googleanalytics/settings.go: -------------------------------------------------------------------------------- 1 | package googleanalytics 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "Google Analytics" 11 | ) 12 | 13 | type Settings struct { 14 | *cfg.Common 15 | 16 | months int 17 | secretFile string `help:"Your Google client secret JSON file." values:"A string representing a file path to the JSON secret file."` 18 | viewIds map[string]interface{} 19 | enableRealtime bool 20 | } 21 | 22 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 23 | 24 | settings := Settings{ 25 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 26 | 27 | months: ymlConfig.UInt("months"), 28 | secretFile: ymlConfig.UString("secretFile"), 29 | viewIds: ymlConfig.UMap("viewIds"), 30 | enableRealtime: ymlConfig.UBool("enableRealtime", false), 31 | } 32 | 33 | settings.SetDocumentationPath("google/analytics") 34 | 35 | return &settings 36 | } 37 | -------------------------------------------------------------------------------- /modules/googleanalytics/widget.go: -------------------------------------------------------------------------------- 1 | package googleanalytics 2 | 3 | import ( 4 | "github.com/rivo/tview" 5 | "github.com/wtfutil/wtf/view" 6 | ) 7 | 8 | type Widget struct { 9 | view.TextWidget 10 | 11 | settings *Settings 12 | } 13 | 14 | func NewWidget(tviewApp *tview.Application, redrawChan chan bool, settings *Settings) *Widget { 15 | widget := Widget{ 16 | TextWidget: view.NewTextWidget(tviewApp, redrawChan, nil, settings.Common), 17 | 18 | settings: settings, 19 | } 20 | 21 | return &widget 22 | } 23 | 24 | func (widget *Widget) Refresh() { 25 | websiteReports := widget.fetch() 26 | contentTable := widget.createTable(websiteReports) 27 | 28 | widget.Redraw(func() (string, string, bool) { return widget.CommonSettings().Title, contentTable, false }) 29 | } 30 | -------------------------------------------------------------------------------- /modules/grafana/display.go: -------------------------------------------------------------------------------- 1 | package grafana 2 | 3 | import "fmt" 4 | 5 | func (widget *Widget) content() (string, string, bool) { 6 | title := widget.CommonSettings().Title 7 | 8 | var out string 9 | if widget.Err != nil { 10 | return title, widget.Err.Error(), false 11 | } else { 12 | for idx, alert := range widget.Alerts { 13 | out += fmt.Sprintf(` ["%d"][%s]%s - %s[""]`, 14 | idx, 15 | stateColor(alert.State), 16 | stateToEmoji(alert.State), 17 | alert.Name, 18 | ) 19 | out += "\n" 20 | } 21 | } 22 | 23 | return title, out, false 24 | } 25 | 26 | func stateColor(state AlertState) string { 27 | switch state { 28 | case Ok: 29 | return "green" 30 | case Paused: 31 | return "yellow" 32 | case Alerting: 33 | return "red" 34 | case Pending: 35 | return "orange" 36 | case NoData: 37 | return "yellow" 38 | default: 39 | return "white" 40 | } 41 | } 42 | 43 | func stateToEmoji(state AlertState) string { 44 | switch state { 45 | case Ok: 46 | return "✔" 47 | case Paused: 48 | return "⏸" 49 | case Alerting: 50 | return "✘" 51 | case Pending: 52 | return "?" 53 | case NoData: 54 | return "?" 55 | } 56 | return "" 57 | } 58 | -------------------------------------------------------------------------------- /modules/grafana/keyboard.go: -------------------------------------------------------------------------------- 1 | package grafana 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous alert") 7 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next alert") 8 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openAlert, "Open alert in browser") 9 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 10 | } 11 | -------------------------------------------------------------------------------- /modules/grafana/settings.go: -------------------------------------------------------------------------------- 1 | package grafana 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "strings" 7 | 8 | "github.com/olebedev/config" 9 | "github.com/wtfutil/wtf/cfg" 10 | ) 11 | 12 | const ( 13 | defaultFocusable = true 14 | defaultTitle = "Grafana" 15 | ) 16 | 17 | type Settings struct { 18 | *cfg.Common 19 | 20 | apiKey string `help:"Your Grafana API token."` 21 | baseURI string `help:"Base url of your grafana instance"` 22 | } 23 | 24 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 25 | 26 | settings := Settings{ 27 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 28 | 29 | apiKey: ymlConfig.UString("apiKey", os.Getenv("WTF_GRAFANA_API_KEY")), 30 | baseURI: ymlConfig.UString("baseUri", ""), 31 | } 32 | 33 | if settings.baseURI == "" { 34 | log.Fatal("baseUri for grafana is empty, but is required") 35 | } 36 | settings.baseURI = strings.TrimSuffix(settings.baseURI, "/") 37 | 38 | return &settings 39 | } 40 | -------------------------------------------------------------------------------- /modules/gspreadsheets/settings.go: -------------------------------------------------------------------------------- 1 | package gspreadsheets 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "Google Spreadsheets" 11 | ) 12 | 13 | type colors struct { 14 | values string 15 | } 16 | 17 | type Settings struct { 18 | colors 19 | *cfg.Common 20 | 21 | cellAddresses []interface{} 22 | cellNames []interface{} 23 | secretFile string 24 | sheetID string 25 | } 26 | 27 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 28 | 29 | settings := Settings{ 30 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 31 | 32 | cellNames: ymlConfig.UList("cells.names"), 33 | secretFile: ymlConfig.UString("secretFile"), 34 | sheetID: ymlConfig.UString("sheetId"), 35 | } 36 | 37 | settings.values = ymlConfig.UString("colors.values", "green") 38 | 39 | settings.SetDocumentationPath("google/spreadsheet") 40 | 41 | return &settings 42 | } 43 | -------------------------------------------------------------------------------- /modules/hackernews/keyboard.go: -------------------------------------------------------------------------------- 1 | package hackernews 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 10 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 11 | widget.SetKeyboardChar("o", widget.openStory, "Open story in browser") 12 | widget.SetKeyboardChar("c", widget.openComments, "Open comments in browser") 13 | 14 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 15 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 16 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openStory, "Open story in browser") 17 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 18 | } 19 | -------------------------------------------------------------------------------- /modules/hackernews/settings.go: -------------------------------------------------------------------------------- 1 | package hackernews 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = true 10 | defaultTitle = "HackerNews" 11 | ) 12 | 13 | type Settings struct { 14 | *cfg.Common 15 | 16 | numberOfStories int `help:"Defines number of stories to be displayed. Default is 10" optional:"true"` 17 | storyType string `help:"Category of story to see" values:"new, top, job, ask" optional:"true"` 18 | } 19 | 20 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 21 | 22 | settings := Settings{ 23 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 24 | 25 | numberOfStories: ymlConfig.UInt("numberOfStories", 10), 26 | storyType: ymlConfig.UString("storyType", "top"), 27 | } 28 | 29 | return &settings 30 | } 31 | -------------------------------------------------------------------------------- /modules/hackernews/story.go: -------------------------------------------------------------------------------- 1 | package hackernews 2 | 3 | import "fmt" 4 | 5 | const ( 6 | hnStoryPath = "https://news.ycombinator.com/item?id=" 7 | ) 8 | 9 | // Story represents a story submission on HackerNews 10 | type Story struct { 11 | By string `json:"by"` 12 | Descendants int `json:"descendants"` 13 | ID int `json:"id"` 14 | Kids []int `json:"kids"` 15 | Score int `json:"score"` 16 | Time int `json:"time"` 17 | Title string `json:"title"` 18 | Type string `json:"type"` 19 | URL string `json:"url"` 20 | } 21 | 22 | // CommentLink return the link to the HackerNews story comments page 23 | func (story *Story) CommentLink() string { 24 | return fmt.Sprintf("%s%d", hnStoryPath, story.ID) 25 | } 26 | 27 | // Link returns the link to a story. If the story has an external link, that is returned 28 | // If the story has no external link, the HackerNews comments link is returned instead 29 | func (story *Story) Link() string { 30 | if story.URL != "" { 31 | return story.URL 32 | } 33 | 34 | // Fall back to the HackerNews comment link 35 | return story.CommentLink() 36 | } 37 | -------------------------------------------------------------------------------- /modules/hackernews/story_test.go: -------------------------------------------------------------------------------- 1 | package hackernews 2 | 3 | import ( 4 | "testing" 5 | 6 | "gotest.tools/assert" 7 | ) 8 | 9 | func Test_CommentLink(t *testing.T) { 10 | story := Story{ 11 | ID: 3, 12 | } 13 | 14 | assert.Equal(t, "https://news.ycombinator.com/item?id=3", story.CommentLink()) 15 | } 16 | 17 | func Test_Link(t *testing.T) { 18 | tests := []struct { 19 | name string 20 | id int 21 | url string 22 | expected string 23 | }{ 24 | { 25 | name: "no external link", 26 | id: 1, 27 | url: "", 28 | expected: "https://news.ycombinator.com/item?id=1", 29 | }, 30 | { 31 | name: "with external link", 32 | id: 1, 33 | url: "https://www.link.ca", 34 | expected: "https://www.link.ca", 35 | }, 36 | } 37 | 38 | for _, tt := range tests { 39 | t.Run(tt.name, func(t *testing.T) { 40 | story := Story{ 41 | ID: tt.id, 42 | URL: tt.url, 43 | } 44 | 45 | actual := story.Link() 46 | 47 | assert.Equal(t, tt.expected, actual) 48 | }) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /modules/healthchecks/keyboard.go: -------------------------------------------------------------------------------- 1 | package healthchecks 2 | 3 | func (widget *Widget) initializeKeyboardControls() { 4 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 5 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 6 | } 7 | -------------------------------------------------------------------------------- /modules/healthchecks/settings.go: -------------------------------------------------------------------------------- 1 | package healthchecks 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | "github.com/wtfutil/wtf/utils" 9 | ) 10 | 11 | const ( 12 | defaultFocusable = true 13 | defaultTitle = "Healthchecks.io" 14 | ) 15 | 16 | type Settings struct { 17 | *cfg.Common 18 | 19 | apiKey string `help:"An healthchecks API key." optional:"false"` 20 | apiURL string `help:"Base URL for API" optional:"true"` 21 | tags []string `help:"Filters the checks and returns only the checks that are tagged with the specified value"` 22 | } 23 | 24 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 25 | 26 | settings := Settings{ 27 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 28 | 29 | apiKey: ymlConfig.UString("apiKey", os.Getenv("WTF_HEALTHCHECKS_APIKEY")), 30 | apiURL: ymlConfig.UString("apiURL", "https://hc-ping.com/"), 31 | tags: utils.ToStrs(ymlConfig.UList("tags")), 32 | } 33 | 34 | cfg.ModuleSecret(name, globalConfig, &settings.apiKey). 35 | Service(settings.apiURL).Load() 36 | 37 | return &settings 38 | } 39 | -------------------------------------------------------------------------------- /modules/hibp/hibp_breach.go: -------------------------------------------------------------------------------- 1 | package hibp 2 | 3 | import "time" 4 | 5 | // Breach represents a breach in the HIBP system 6 | type Breach struct { 7 | Date string `json:"BreachDate"` 8 | Name string `json:"Name"` 9 | } 10 | 11 | // BreachDate returns the date of the breach 12 | func (br *Breach) BreachDate() (time.Time, error) { 13 | dt, err := time.Parse("2006-01-02", br.Date) 14 | if err != nil { 15 | // I would much rather return (nil, err) err but that doesn't seem possible 16 | // Not sure what a better value would be 17 | return time.Now(), err 18 | } 19 | 20 | return dt, nil 21 | } 22 | -------------------------------------------------------------------------------- /modules/hibp/hibp_status.go: -------------------------------------------------------------------------------- 1 | package hibp 2 | 3 | // Status represents the status of an account in the HIBP system 4 | type Status struct { 5 | Account string 6 | Breaches []Breach 7 | } 8 | 9 | // NewStatus creates and returns an instance of Status 10 | func NewStatus(acct string, breaches []Breach) *Status { 11 | stat := Status{ 12 | Account: acct, 13 | Breaches: breaches, 14 | } 15 | 16 | return &stat 17 | } 18 | 19 | // HasBeenCompromised returns TRUE if the specified account has any breaches associated 20 | // with it, FALSE if no breaches are associated with it 21 | func (stat *Status) HasBeenCompromised() bool { 22 | return stat.Len() > 0 23 | } 24 | 25 | // Len returns the number of breaches found for the specified account 26 | func (stat *Status) Len() int { 27 | if stat == nil || stat.Breaches == nil { 28 | return 0 29 | } 30 | 31 | return len(stat.Breaches) 32 | } 33 | -------------------------------------------------------------------------------- /modules/ipaddresses/ipapi/settings.go: -------------------------------------------------------------------------------- 1 | package ipapi 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "IP API" 11 | ) 12 | 13 | type colors struct { 14 | name string 15 | value string 16 | } 17 | 18 | type Settings struct { 19 | colors 20 | *cfg.Common 21 | args []interface{} `help:"Defines what data to display and the order." values:"'ip', 'isp', 'as', 'asName', 'district', 'city', 'region', 'regionName', 'country', 'countryCode', 'continent', 'continentCode', 'coordinates', 'postalCode', 'currency', 'organization', 'timezone' and/or 'reverseDNS'"` 22 | } 23 | 24 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 25 | settings := Settings{ 26 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 27 | 28 | args: ymlConfig.UList("args"), 29 | } 30 | 31 | settings.name = ymlConfig.UString("colors.name", "red") 32 | settings.value = ymlConfig.UString("colors.value", "white") 33 | settings.SetDocumentationPath("ipaddress/ipapi") 34 | 35 | return &settings 36 | } 37 | -------------------------------------------------------------------------------- /modules/jenkins/job.go: -------------------------------------------------------------------------------- 1 | package jenkins 2 | 3 | type Job struct { 4 | Name string `json:"name"` 5 | Url string `json:"url"` 6 | Color string `json:"color"` 7 | } 8 | -------------------------------------------------------------------------------- /modules/jenkins/keyboard.go: -------------------------------------------------------------------------------- 1 | package jenkins 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 10 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 11 | widget.SetKeyboardChar("o", widget.openJob, "Open job in browser") 12 | 13 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 14 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 15 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openJob, "Open job in browser") 16 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 17 | } 18 | -------------------------------------------------------------------------------- /modules/jenkins/view.go: -------------------------------------------------------------------------------- 1 | package jenkins 2 | 3 | type View struct { 4 | Description string `json:"description"` 5 | Jobs []Job `json:"jobs"` 6 | ActiveConfigurations []Job `json:"activeConfigurations"` 7 | Name string `json:"name"` 8 | Url string `json:"url"` 9 | } 10 | -------------------------------------------------------------------------------- /modules/jira/issues.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | type Issue struct { 4 | Expand string `json:"expand"` 5 | ID string `json:"id"` 6 | Self string `json:"self"` 7 | Key string `json:"key"` 8 | 9 | IssueFields *IssueFields `json:"fields"` 10 | } 11 | 12 | type IssueFields struct { 13 | Summary string `json:"summary"` 14 | 15 | IssueType *IssueType `json:"issuetype"` 16 | IssueStatus *IssueStatus `json:"status"` 17 | } 18 | 19 | type IssueType struct { 20 | Self string `json:"self"` 21 | ID string `json:"id"` 22 | Description string `json:"description"` 23 | IconURL string `json:"iconUrl"` 24 | Name string `json:"name"` 25 | Subtask bool `json:"subtask"` 26 | } 27 | 28 | type IssueStatus struct { 29 | ISelf string `json:"self"` 30 | IDescription string `json:"description"` 31 | IName string `json:"name"` 32 | } 33 | -------------------------------------------------------------------------------- /modules/jira/keyboard.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | ) 6 | 7 | func (widget *Widget) initializeKeyboardControls() { 8 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 9 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 10 | 11 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 12 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 13 | widget.SetKeyboardChar("o", widget.openItem, "Open item in browser") 14 | 15 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 16 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 17 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openItem, "Open item in browser") 18 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 19 | } 20 | -------------------------------------------------------------------------------- /modules/jira/search_result.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | type SearchResult struct { 4 | StartAt int `json:"startAt"` 5 | MaxResults int `json:"maxResults"` 6 | Total int `json:"total"` 7 | Issues []Issue `json:"issues"` 8 | } 9 | -------------------------------------------------------------------------------- /modules/kubernetes/client.go: -------------------------------------------------------------------------------- 1 | package kubernetes 2 | 3 | import ( 4 | "k8s.io/client-go/kubernetes" 5 | // Includes authentication modules for various Kubernetes providers 6 | _ "k8s.io/client-go/plugin/pkg/client/auth" 7 | "k8s.io/client-go/tools/clientcmd" 8 | ) 9 | 10 | type clientInstance struct { 11 | Client kubernetes.Interface 12 | } 13 | 14 | // getInstance returns a Kubernetes interface for a clientset 15 | func (widget *Widget) getInstance() (*clientInstance, error) { 16 | var err error 17 | 18 | widget.clientOnce.Do(func() { 19 | widget.client = &clientInstance{} 20 | widget.client.Client, err = widget.getKubeClient() 21 | }) 22 | 23 | return widget.client, err 24 | } 25 | 26 | // getKubeClient returns a kubernetes clientset for the kubeconfig provided 27 | func (widget *Widget) getKubeClient() (kubernetes.Interface, error) { 28 | var overrides *clientcmd.ConfigOverrides 29 | if widget.context != "" { 30 | overrides = &clientcmd.ConfigOverrides{ 31 | CurrentContext: widget.context, 32 | } 33 | } 34 | 35 | config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 36 | &clientcmd.ClientConfigLoadingRules{ExplicitPath: widget.kubeconfig}, 37 | overrides).ClientConfig() 38 | 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | clientset, err := kubernetes.NewForConfig(config) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return clientset, nil 48 | } 49 | -------------------------------------------------------------------------------- /modules/kubernetes/settings.go: -------------------------------------------------------------------------------- 1 | package kubernetes 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | "github.com/wtfutil/wtf/utils" 7 | ) 8 | 9 | const ( 10 | defaultFocusable = false 11 | defaultTitle = "Kubernetes" 12 | ) 13 | 14 | type Settings struct { 15 | *cfg.Common 16 | 17 | objects []string `help:"Kubernetes objects to show. Options are: [nodes, pods, deployments]."` 18 | title string `help:"Override the title of widget."` 19 | kubeconfig string `help:"Location of a kubeconfig file."` 20 | namespaces []string `help:"List of namespaces to watch. If blank, defaults to all namespaces."` 21 | context string `help:"Kubernetes context to use. If blank, uses default context"` 22 | } 23 | 24 | func NewSettingsFromYAML(name string, moduleConfig *config.Config, globalConfig *config.Config) *Settings { 25 | 26 | settings := Settings{ 27 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, moduleConfig, globalConfig), 28 | 29 | objects: utils.ToStrs(moduleConfig.UList("objects")), 30 | title: moduleConfig.UString("title"), 31 | kubeconfig: moduleConfig.UString("kubeconfig"), 32 | namespaces: utils.ToStrs(moduleConfig.UList("namespaces")), 33 | context: moduleConfig.UString("context"), 34 | } 35 | 36 | return &settings 37 | } 38 | -------------------------------------------------------------------------------- /modules/logger/settings.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = true 10 | defaultTitle = "Logger" 11 | ) 12 | 13 | type Settings struct { 14 | *cfg.Common 15 | } 16 | 17 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 18 | settings := Settings{ 19 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 20 | } 21 | 22 | return &settings 23 | } 24 | -------------------------------------------------------------------------------- /modules/lunarphase/keyboard.go: -------------------------------------------------------------------------------- 1 | package lunarphase 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("n", widget.NextDay, "Show next day lunar phase") 10 | widget.SetKeyboardChar("p", widget.PrevDay, "Show previous day lunar phase") 11 | widget.SetKeyboardChar("t", widget.Today, "Show today lunar phase") 12 | widget.SetKeyboardChar("N", widget.NextWeek, "Show next week lunar phase") 13 | widget.SetKeyboardChar("P", widget.PrevWeek, "Show previous week lunar phase") 14 | widget.SetKeyboardChar("o", widget.OpenMoonPhase, "Open 'Moon Phase for Today' in browser") 15 | 16 | widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevDay, "Show previous day lunar phase") 17 | widget.SetKeyboardKey(tcell.KeyRight, widget.NextDay, "Show next day lunar phase") 18 | widget.SetKeyboardKey(tcell.KeyUp, widget.NextWeek, "Show next week lunar phase") 19 | widget.SetKeyboardKey(tcell.KeyDown, widget.PrevWeek, "Show previous week lunar phase") 20 | widget.SetKeyboardKey(tcell.KeyEnter, widget.OpenMoonPhase, "Open 'Moon Phase for Today' in browser") 21 | widget.SetKeyboardKey(tcell.KeyCtrlD, widget.DisableWidget, "Disable/Enable this widget instance") 22 | } 23 | -------------------------------------------------------------------------------- /modules/lunarphase/settings.go: -------------------------------------------------------------------------------- 1 | package lunarphase 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = true 10 | defaultTitle = "Phase of the Moon" 11 | dateFormat = "2006-01-02" 12 | phaseFormat = "01-02-2006" 13 | ) 14 | 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | language string 19 | requestTimeout int 20 | } 21 | 22 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 23 | settings := Settings{ 24 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 25 | 26 | language: ymlConfig.UString("language", "en"), 27 | requestTimeout: ymlConfig.UInt("timeout", 30), 28 | } 29 | 30 | settings.SetDocumentationPath("lunarphase") 31 | 32 | return &settings 33 | } 34 | -------------------------------------------------------------------------------- /modules/mercurial/keyboard.go: -------------------------------------------------------------------------------- 1 | package mercurial 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("l", widget.NextSource, "Select next source") 10 | widget.SetKeyboardChar("h", widget.PrevSource, "Select previous source") 11 | widget.SetKeyboardChar("p", widget.Pull, "Pull repo") 12 | widget.SetKeyboardChar("c", widget.Checkout, "Checkout branch") 13 | 14 | widget.SetKeyboardKey(tcell.KeyRight, widget.NextSource, "Select next source") 15 | widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevSource, "Select previous source") 16 | } 17 | -------------------------------------------------------------------------------- /modules/mercurial/settings.go: -------------------------------------------------------------------------------- 1 | package mercurial 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = true 10 | defaultTitle = "Mercurial" 11 | ) 12 | 13 | type Settings struct { 14 | *cfg.Common 15 | 16 | commitCount int `help:"The number of past commits to display." optional:"true"` 17 | commitFormat string `help:"The string format for the commit message." optional:"true"` 18 | repositories []interface{} `help:"Defines which mercurial repositories to watch." values:"A list of zero or more local file paths pointing to valid mercurial repositories."` 19 | } 20 | 21 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 22 | 23 | settings := Settings{ 24 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 25 | 26 | commitCount: ymlConfig.UInt("commitCount", 10), 27 | commitFormat: ymlConfig.UString("commitFormat", "[forestgreen]{rev}:{phase} [white]{desc|firstline|strip} [grey]{author|person} {date|age}[white]"), 28 | repositories: ymlConfig.UList("repositories"), 29 | } 30 | 31 | return &settings 32 | } 33 | -------------------------------------------------------------------------------- /modules/nbascore/keyboard.go: -------------------------------------------------------------------------------- 1 | package nbascore 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("l", widget.next, "Select next item") 10 | widget.SetKeyboardChar("h", widget.prev, "Select previous item") 11 | widget.SetKeyboardChar("c", widget.center, "Center on item") 12 | 13 | widget.SetKeyboardKey(tcell.KeyRight, widget.next, "Select next item") 14 | widget.SetKeyboardKey(tcell.KeyLeft, widget.prev, "Select previous item") 15 | } 16 | 17 | func (widget *Widget) center() { 18 | offset = 0 19 | widget.Refresh() 20 | } 21 | 22 | func (widget *Widget) next() { 23 | offset++ 24 | widget.Refresh() 25 | } 26 | 27 | func (widget *Widget) prev() { 28 | offset-- 29 | widget.Refresh() 30 | } 31 | -------------------------------------------------------------------------------- /modules/nbascore/settings.go: -------------------------------------------------------------------------------- 1 | package nbascore 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = true 10 | defaultTitle = "NBA Score" 11 | ) 12 | 13 | type Settings struct { 14 | *cfg.Common 15 | } 16 | 17 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 18 | settings := Settings{ 19 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 20 | } 21 | 22 | settings.SetDocumentationPath("sports/nbascore") 23 | 24 | return &settings 25 | } 26 | -------------------------------------------------------------------------------- /modules/newrelic/client.go: -------------------------------------------------------------------------------- 1 | package newrelic 2 | 3 | import ( 4 | nr "github.com/wtfutil/wtf/modules/newrelic/client" 5 | ) 6 | 7 | type Client2 struct { 8 | applicationId int 9 | nrClient *nr.Client 10 | } 11 | 12 | func NewClient(apiKey string, applicationId int) *Client2 { 13 | return &Client2{ 14 | applicationId: applicationId, 15 | nrClient: nr.NewClient(apiKey), 16 | } 17 | 18 | } 19 | 20 | func (client *Client2) Application() (*nr.Application, error) { 21 | 22 | application, err := client.nrClient.GetApplication(client.applicationId) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | return application, nil 28 | } 29 | 30 | func (client *Client2) Deployments() ([]nr.ApplicationDeployment, error) { 31 | 32 | opts := &nr.ApplicationDeploymentOptions{Page: 1} 33 | deployments, err := client.nrClient.GetApplicationDeployments(client.applicationId, opts) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | return deployments, nil 39 | } 40 | -------------------------------------------------------------------------------- /modules/newrelic/client/application_host_metrics.go: -------------------------------------------------------------------------------- 1 | package newrelic 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // GetApplicationHostMetrics will return a slice of Metric items for a 8 | // particular Application ID's Host ID, optionally filtering by 9 | // MetricsOptions. 10 | func (c *Client) GetApplicationHostMetrics(appID, hostID int, options *MetricsOptions) ([]Metric, error) { 11 | mc := NewMetricClient(c) 12 | 13 | return mc.GetMetrics( 14 | fmt.Sprintf( 15 | "applications/%d/hosts/%d/metrics.json", 16 | appID, 17 | hostID, 18 | ), 19 | options, 20 | ) 21 | } 22 | 23 | // GetApplicationHostMetricData will return all metric data for a particular 24 | // application's host and slice of metric names, optionally filtered by 25 | // MetricDataOptions. 26 | func (c *Client) GetApplicationHostMetricData(appID, hostID int, names []string, options *MetricDataOptions) (*MetricDataResponse, error) { 27 | mc := NewMetricClient(c) 28 | 29 | return mc.GetMetricData( 30 | fmt.Sprintf( 31 | "applications/%d/hosts/%d/metrics/data.json", 32 | appID, 33 | hostID, 34 | ), 35 | names, 36 | options, 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /modules/newrelic/client/application_instance_metrics.go: -------------------------------------------------------------------------------- 1 | package newrelic 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // GetApplicationInstanceMetrics will return a slice of Metric items for a 8 | // particular Application ID's instance ID, optionally filtering by 9 | // MetricsOptions. 10 | func (c *Client) GetApplicationInstanceMetrics(appID, instanceID int, options *MetricsOptions) ([]Metric, error) { 11 | mc := NewMetricClient(c) 12 | 13 | return mc.GetMetrics( 14 | fmt.Sprintf( 15 | "applications/%d/instances/%d/metrics.json", 16 | appID, 17 | instanceID, 18 | ), 19 | options, 20 | ) 21 | } 22 | 23 | // GetApplicationInstanceMetricData will return all metric data for a 24 | // particular application's instance and slice of metric names, optionally 25 | // filtered by MetricDataOptions. 26 | func (c *Client) GetApplicationInstanceMetricData(appID, instanceID int, names []string, options *MetricDataOptions) (*MetricDataResponse, error) { 27 | mc := NewMetricClient(c) 28 | 29 | return mc.GetMetricData( 30 | fmt.Sprintf( 31 | "applications/%d/instances/%d/metrics/data.json", 32 | appID, 33 | instanceID, 34 | ), 35 | names, 36 | options, 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /modules/newrelic/client/application_metrics.go: -------------------------------------------------------------------------------- 1 | package newrelic 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // GetApplicationMetrics will return a slice of Metric items for a 8 | // particular Application ID, optionally filtering by 9 | // MetricsOptions. 10 | func (c *Client) GetApplicationMetrics(id int, options *MetricsOptions) ([]Metric, error) { 11 | mc := NewMetricClient(c) 12 | 13 | return mc.GetMetrics( 14 | fmt.Sprintf( 15 | "applications/%d/metrics.json", 16 | id, 17 | ), 18 | options, 19 | ) 20 | } 21 | 22 | // GetApplicationMetricData will return all metric data for a particular 23 | // application and slice of metric names, optionally filtered by 24 | // MetricDataOptions. 25 | func (c *Client) GetApplicationMetricData(id int, names []string, options *MetricDataOptions) (*MetricDataResponse, error) { 26 | mc := NewMetricClient(c) 27 | 28 | return mc.GetMetricData( 29 | fmt.Sprintf( 30 | "applications/%d/metrics/data.json", 31 | id, 32 | ), 33 | names, 34 | options, 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /modules/newrelic/client/array.go: -------------------------------------------------------------------------------- 1 | package newrelic 2 | 3 | // An Array is a type expected by the NewRelic API that differs from a comma- 4 | // separated list. When passing GET params that expect an 'Array' type with 5 | // one to many values, the expected format is "key=val1&key=val2" but an 6 | // argument with zero to many values is of the form "key=val1,val2", and 7 | // neither can be used in the other's place, so we have to differentiate 8 | // somehow. 9 | type Array struct { 10 | arr []string 11 | } 12 | -------------------------------------------------------------------------------- /modules/newrelic/client/component_metrics.go: -------------------------------------------------------------------------------- 1 | package newrelic 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // GetComponentMetrics will return a slice of Metric items for a 8 | // particular Component ID, optionally filtered by MetricsOptions. 9 | func (c *Client) GetComponentMetrics(id int, options *MetricsOptions) ([]Metric, error) { 10 | mc := NewMetricClient(c) 11 | 12 | return mc.GetMetrics( 13 | fmt.Sprintf( 14 | "components/%d/metrics.json", 15 | id, 16 | ), 17 | options, 18 | ) 19 | } 20 | 21 | // GetComponentMetricData will return all metric data for a particular 22 | // component, optionally filtered by MetricDataOptions. 23 | func (c *Client) GetComponentMetricData(id int, names []string, options *MetricDataOptions) (*MetricDataResponse, error) { 24 | mc := NewMetricClient(c) 25 | 26 | return mc.GetMetricData( 27 | fmt.Sprintf( 28 | "components/%d/metrics/data.json", 29 | id, 30 | ), 31 | names, 32 | options, 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /modules/newrelic/client/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * NewRelic API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2016 by authors and contributors. 7 | */ 8 | 9 | package newrelic 10 | 11 | import ( 12 | "net/http" 13 | "net/url" 14 | "time" 15 | ) 16 | 17 | const ( 18 | // defaultAPIURL is the default base URL for New Relic's latest API. 19 | defaultAPIURL = "https://api.newrelic.com/v2/" 20 | // defaultTimeout is the default timeout for the http.Client used. 21 | defaultTimeout = 5 * time.Second 22 | ) 23 | 24 | // Client provides a set of methods to interact with the New Relic API. 25 | type Client struct { 26 | apiKey string 27 | httpClient *http.Client 28 | url *url.URL 29 | } 30 | 31 | // NewWithHTTPClient returns a new Client object for interfacing with the New 32 | // Relic API, allowing for override of the http.Client object. 33 | func NewWithHTTPClient(apiKey string, client *http.Client) *Client { 34 | u, err := url.Parse(defaultAPIURL) 35 | if err != nil { 36 | panic(err) 37 | } 38 | return &Client{ 39 | apiKey: apiKey, 40 | httpClient: client, 41 | url: u, 42 | } 43 | } 44 | 45 | // NewClient returns a new Client object for interfacing with the New Relic API. 46 | func NewClient(apiKey string) *Client { 47 | return NewWithHTTPClient(apiKey, &http.Client{Timeout: defaultTimeout}) 48 | } 49 | -------------------------------------------------------------------------------- /modules/newrelic/client/mobile_application_metrics.go: -------------------------------------------------------------------------------- 1 | package newrelic 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // GetMobileApplicationMetrics will return a slice of Metric items for a 8 | // particular MobileAplication ID, optionally filtering by 9 | // MetricsOptions. 10 | func (c *Client) GetMobileApplicationMetrics(id int, options *MetricsOptions) ([]Metric, error) { 11 | mc := NewMetricClient(c) 12 | 13 | return mc.GetMetrics( 14 | fmt.Sprintf( 15 | "mobile_applications/%d/metrics.json", 16 | id, 17 | ), 18 | options, 19 | ) 20 | } 21 | 22 | // GetMobileApplicationMetricData will return all metric data for a particular 23 | // MobileAplication and slice of metric names, optionally filtered by 24 | // MetricDataOptions. 25 | func (c *Client) GetMobileApplicationMetricData(id int, names []string, options *MetricDataOptions) (*MetricDataResponse, error) { 26 | mc := NewMetricClient(c) 27 | 28 | return mc.GetMetricData( 29 | fmt.Sprintf( 30 | "mobile_applications/%d/metrics/data.json", 31 | id, 32 | ), 33 | names, 34 | options, 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /modules/newrelic/client/server_metrics.go: -------------------------------------------------------------------------------- 1 | package newrelic 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // GetServerMetrics will return a slice of Metric items for a particular 8 | // Server ID, optionally filtering by MetricsOptions. 9 | func (c *Client) GetServerMetrics(id int, options *MetricsOptions) ([]Metric, error) { 10 | mc := NewMetricClient(c) 11 | 12 | return mc.GetMetrics( 13 | fmt.Sprintf( 14 | "servers/%d/metrics.json", 15 | id, 16 | ), 17 | options, 18 | ) 19 | } 20 | 21 | // GetServerMetricData will return all metric data for a particular Server and 22 | // slice of metric names, optionally filtered by MetricDataOptions. 23 | func (c *Client) GetServerMetricData(id int, names []string, options *MetricDataOptions) (*MetricDataResponse, error) { 24 | mc := NewMetricClient(c) 25 | 26 | return mc.GetMetricData( 27 | fmt.Sprintf( 28 | "servers/%d/metrics/data.json", 29 | id, 30 | ), 31 | names, 32 | options, 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /modules/newrelic/keyboard.go: -------------------------------------------------------------------------------- 1 | package newrelic 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevSource, "Select previous application") 7 | widget.SetKeyboardKey(tcell.KeyRight, widget.NextSource, "Select next application") 8 | } 9 | -------------------------------------------------------------------------------- /modules/newrelic/settings.go: -------------------------------------------------------------------------------- 1 | package newrelic 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = true 12 | defaultTitle = "NewRelic" 13 | ) 14 | 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | apiKey string `help:"Your New Relic API token."` 19 | deployCount int `help:"The number of past deploys to display on screen." optional:"true"` 20 | applicationIDs []interface{} `help:"The integer ID of the New Relic application you wish to report on."` 21 | } 22 | 23 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 24 | 25 | settings := Settings{ 26 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 27 | 28 | apiKey: ymlConfig.UString("apiKey", os.Getenv("WTF_NEW_RELIC_API_KEY")), 29 | deployCount: ymlConfig.UInt("deployCount", 5), 30 | applicationIDs: ymlConfig.UList("applicationIDs"), 31 | } 32 | 33 | cfg.ModuleSecret(name, globalConfig, &settings.apiKey).Load() 34 | 35 | return &settings 36 | } 37 | -------------------------------------------------------------------------------- /modules/nextbus/settings.go: -------------------------------------------------------------------------------- 1 | package nextbus 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "nextbus" 11 | ) 12 | 13 | // Settings defines the configuration properties for this module 14 | type Settings struct { 15 | common *cfg.Common 16 | 17 | route string `help:"Route Number of your bus"` 18 | agency string `help:"Transit agency of your bus"` 19 | stopID string `help:"Your bus stop number"` 20 | } 21 | 22 | // NewSettingsFromYAML creates a new settings instance from a YAML config block 23 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 24 | settings := Settings{ 25 | common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 26 | 27 | route: ymlConfig.UString("route"), 28 | agency: ymlConfig.UString("agency"), 29 | stopID: ymlConfig.UString("stopID"), 30 | } 31 | 32 | return &settings 33 | } 34 | -------------------------------------------------------------------------------- /modules/pagerduty/sort.go: -------------------------------------------------------------------------------- 1 | package pagerduty 2 | 3 | import "github.com/PagerDuty/go-pagerduty" 4 | 5 | type ByEscalationLevel []pagerduty.OnCall 6 | 7 | func (s ByEscalationLevel) Len() int { return len(s) } 8 | func (s ByEscalationLevel) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 9 | 10 | func (s ByEscalationLevel) Less(i, j int) bool { 11 | return s[i].EscalationLevel < s[j].EscalationLevel 12 | } 13 | -------------------------------------------------------------------------------- /modules/pihole/keyboard.go: -------------------------------------------------------------------------------- 1 | package pihole 2 | 3 | func (widget *Widget) initializeKeyboardControls() { 4 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 5 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 6 | 7 | widget.SetKeyboardChar("d", widget.disable, "disable Pi-hole") 8 | widget.SetKeyboardChar("e", widget.enable, "enable Pi-hole") 9 | } 10 | -------------------------------------------------------------------------------- /modules/pihole/settings.go: -------------------------------------------------------------------------------- 1 | package pihole 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = true 10 | defaultTitle = "Pi-hole" 11 | ) 12 | 13 | type Settings struct { 14 | *cfg.Common 15 | 16 | wrapText bool 17 | apiUrl string 18 | token string 19 | showTopItems int 20 | showTopClients int 21 | maxClientWidth int 22 | maxDomainWidth int 23 | showSummary bool 24 | } 25 | 26 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 27 | settings := Settings{ 28 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 29 | 30 | apiUrl: ymlConfig.UString("apiUrl"), 31 | token: ymlConfig.UString("token"), 32 | showSummary: ymlConfig.UBool("showSummary", true), 33 | showTopItems: ymlConfig.UInt("showTopItems", 5), 34 | showTopClients: ymlConfig.UInt("showTopClients", 5), 35 | maxClientWidth: ymlConfig.UInt("maxClientWidth", 20), 36 | maxDomainWidth: ymlConfig.UInt("maxDomainWidth", 20), 37 | } 38 | 39 | cfg.ModuleSecret(name, globalConfig, &settings.token). 40 | Service(settings.apiUrl).Load() 41 | 42 | return &settings 43 | } 44 | -------------------------------------------------------------------------------- /modules/pivotal/keyboard.go: -------------------------------------------------------------------------------- 1 | package pivotal 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | ) 6 | 7 | func (widget *Widget) initializeKeyboardControls() { 8 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 9 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 10 | 11 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 12 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 13 | widget.SetKeyboardChar("l", widget.NextSource, "Select next source") 14 | widget.SetKeyboardChar("h", widget.PrevSource, "Select previous source") 15 | widget.SetKeyboardChar("o", widget.Open, "Open item in browser") 16 | widget.SetKeyboardChar("p", widget.OpenPulls, "Open pull requests in browser") 17 | 18 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 19 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 20 | widget.SetKeyboardKey(tcell.KeyRight, widget.NextSource, "Select next source") 21 | widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevSource, "Select previous source") 22 | widget.SetKeyboardKey(tcell.KeyEnter, widget.Open, "Open PR in browser") 23 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 24 | } 25 | -------------------------------------------------------------------------------- /modules/pocket/item_service.go: -------------------------------------------------------------------------------- 1 | package pocket 2 | 3 | import "sort" 4 | 5 | type sortByTimeAdded []Item 6 | 7 | func (a sortByTimeAdded) Len() int { return len(a) } 8 | func (a sortByTimeAdded) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 9 | func (a sortByTimeAdded) Less(i, j int) bool { return a[i].TimeAdded > a[j].TimeAdded } 10 | 11 | func orderItemResponseByKey(response ItemLists) []Item { 12 | 13 | var items sortByTimeAdded 14 | for _, v := range response.List { 15 | items = append(items, v) 16 | } 17 | sort.Sort(items) 18 | return items 19 | } 20 | -------------------------------------------------------------------------------- /modules/pocket/keyboard.go: -------------------------------------------------------------------------------- 1 | package pocket 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("a", widget.toggleLink, "Toggle Link") 10 | widget.SetKeyboardChar("t", widget.toggleView, "Toggle view (links ,archived links)") 11 | widget.SetKeyboardChar("j", widget.Next, "Select Next Link") 12 | widget.SetKeyboardChar("k", widget.Prev, "Select Previous Link") 13 | widget.SetKeyboardChar("o", widget.openLink, "Open Link in the browser") 14 | 15 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select Next Link") 16 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select Previous Link") 17 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openLink, "Open Link in the browser") 18 | } 19 | -------------------------------------------------------------------------------- /modules/pocket/settings.go: -------------------------------------------------------------------------------- 1 | package pocket 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = true 10 | defaultTitle = "Pocket" 11 | ) 12 | 13 | type Settings struct { 14 | *cfg.Common 15 | 16 | consumerKey string 17 | requestKey *string 18 | accessToken *string 19 | } 20 | 21 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 22 | settings := Settings{ 23 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 24 | consumerKey: ymlConfig.UString("consumerKey"), 25 | } 26 | 27 | cfg.ModuleSecret(name, globalConfig, &settings.consumerKey).Load() 28 | 29 | return &settings 30 | } 31 | -------------------------------------------------------------------------------- /modules/power/settings.go: -------------------------------------------------------------------------------- 1 | package power 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "Power" 11 | ) 12 | 13 | type Settings struct { 14 | *cfg.Common 15 | } 16 | 17 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 18 | settings := Settings{ 19 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 20 | } 21 | 22 | return &settings 23 | } 24 | -------------------------------------------------------------------------------- /modules/power/source.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !freebsd 2 | 3 | package power 4 | 5 | import ( 6 | "os/exec" 7 | "regexp" 8 | "strings" 9 | 10 | "github.com/wtfutil/wtf/utils" 11 | ) 12 | 13 | const SingleQuotesRegExp = "'(.*)'" 14 | 15 | // powerSource returns the name of the current power source, probably one of 16 | // "AC Power" or "Battery Power" 17 | func powerSource() string { 18 | cmd := exec.Command("pmset", []string{"-g", "ps"}...) 19 | result := utils.ExecuteCommand(cmd) 20 | 21 | r, _ := regexp.Compile(SingleQuotesRegExp) 22 | 23 | source := r.FindString(result) 24 | source = strings.Replace(source, "'", "", -1) 25 | 26 | return source 27 | } 28 | -------------------------------------------------------------------------------- /modules/power/source_freebsd.go: -------------------------------------------------------------------------------- 1 | //go:build freebsd 2 | 3 | package power 4 | 5 | // powerSource returns the name of the current power source, probably one of 6 | // "AC Power" or "Battery Power" 7 | func powerSource() string { 8 | switch batteryState { 9 | case "1": 10 | return "AC Power" 11 | case "0": 12 | return "Battery Power" 13 | } 14 | return batteryState 15 | } 16 | -------------------------------------------------------------------------------- /modules/power/source_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | package power 4 | 5 | // powerSource returns the name of the current power source, probably one of 6 | // "AC Power" or "Battery Power" 7 | func powerSource() string { 8 | switch batteryState { 9 | case "charging", "fully-charged": 10 | return "AC Power" 11 | case "discharging": 12 | return "Battery Power" 13 | } 14 | return batteryState 15 | } 16 | -------------------------------------------------------------------------------- /modules/resourceusage/settings.go: -------------------------------------------------------------------------------- 1 | package resourceusage 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultRefreshInterval = "1s" 11 | defaultTitle = "ResourceUsage" 12 | ) 13 | 14 | type Settings struct { 15 | *cfg.Common 16 | 17 | cpuCombined bool 18 | showCPU bool 19 | showMem bool 20 | showSwp bool 21 | } 22 | 23 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 24 | settings := Settings{ 25 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 26 | 27 | cpuCombined: ymlConfig.UBool("cpuCombined", false), 28 | showCPU: ymlConfig.UBool("showCPU", true), 29 | showMem: ymlConfig.UBool("showMem", true), 30 | showSwp: ymlConfig.UBool("showSwp", true), 31 | } 32 | settings.RefreshInterval = cfg.ParseTimeString(ymlConfig, "refreshInterval", defaultRefreshInterval) 33 | 34 | return &settings 35 | } 36 | -------------------------------------------------------------------------------- /modules/rollbar/keyboard.go: -------------------------------------------------------------------------------- 1 | package rollbar 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 10 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 11 | widget.SetKeyboardChar("o", widget.openBuild, "Open item in browser") 12 | 13 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 14 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 15 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openBuild, "Open item in browser") 16 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 17 | } 18 | -------------------------------------------------------------------------------- /modules/rollbar/rollbar.go: -------------------------------------------------------------------------------- 1 | package rollbar 2 | 3 | type ActiveItems struct { 4 | Results Result `json:"result"` 5 | } 6 | type Item struct { 7 | Environment string `json:"environment"` 8 | Title string `json:"title"` 9 | Platform string `json:"platform"` 10 | Status string `json:"status"` 11 | TotalOccurrences int `json:"total_occurrences"` 12 | Level string `json:"level"` 13 | ID int `json:"counter"` 14 | } 15 | type Result struct { 16 | Items []Item `json:"items"` 17 | } 18 | -------------------------------------------------------------------------------- /modules/security/security_data.go: -------------------------------------------------------------------------------- 1 | package security 2 | 3 | type SecurityData struct { 4 | Dns []string 5 | FirewallEnabled string 6 | FirewallStealth string 7 | LoggedInUsers []string 8 | WifiEncryption string 9 | WifiName string 10 | } 11 | 12 | func NewSecurityData() *SecurityData { 13 | return &SecurityData{} 14 | } 15 | 16 | func (data SecurityData) DnsAt(idx int) string { 17 | if len(data.Dns) > idx { 18 | return data.Dns[idx] 19 | } 20 | return "" 21 | } 22 | 23 | func (data *SecurityData) Fetch() { 24 | data.Dns = DnsServers() 25 | data.FirewallEnabled = FirewallState() 26 | data.FirewallStealth = FirewallStealthState() 27 | data.LoggedInUsers = LoggedInUsers() 28 | data.WifiName = WifiName() 29 | data.WifiEncryption = WifiEncryption() 30 | } 31 | -------------------------------------------------------------------------------- /modules/security/settings.go: -------------------------------------------------------------------------------- 1 | package security 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "Security" 11 | ) 12 | 13 | type Settings struct { 14 | *cfg.Common 15 | } 16 | 17 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 18 | settings := Settings{ 19 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 20 | } 21 | 22 | return &settings 23 | } 24 | -------------------------------------------------------------------------------- /modules/spacex/client.go: -------------------------------------------------------------------------------- 1 | package spacex 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/wtfutil/wtf/utils" 7 | ) 8 | 9 | const ( 10 | spacexLaunchAPI = "https://api.spacexdata.com/v3/launches/next" 11 | ) 12 | 13 | type Launch struct { 14 | FlightNumber int `json:"flight_number"` 15 | MissionName string `json:"mission_name"` 16 | LaunchDate int64 `json:"launch_date_unix"` 17 | IsTentative bool `json:"tentative"` 18 | Rocket Rocket `json:"rocket"` 19 | LaunchSite LaunchSite `json:"launch_site"` 20 | Links Links `json:"links"` 21 | Details string `json:"details"` 22 | } 23 | 24 | type LaunchSite struct { 25 | Name string `json:"site_name_long"` 26 | } 27 | 28 | type Rocket struct { 29 | Name string `json:"rocket_name"` 30 | } 31 | 32 | type Links struct { 33 | RedditLink string `json:"reddit_campaign"` 34 | YouTubeLink string `json:"video_link"` 35 | } 36 | 37 | func NextLaunch() (*Launch, error) { 38 | resp, err := http.Get(spacexLaunchAPI) 39 | if err != nil { 40 | return nil, err 41 | } 42 | defer func() { _ = resp.Body.Close() }() 43 | 44 | var data Launch 45 | err = utils.ParseJSON(&data, resp.Body) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | return &data, nil 51 | } 52 | -------------------------------------------------------------------------------- /modules/spacex/settings.go: -------------------------------------------------------------------------------- 1 | package spacex 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | ) 11 | 12 | type Settings struct { 13 | *cfg.Common 14 | } 15 | 16 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 17 | spacex := ymlConfig.UString("spacex") 18 | settings := Settings{ 19 | Common: cfg.NewCommonSettingsFromModule(name, spacex, defaultFocusable, ymlConfig, globalConfig), 20 | } 21 | return &settings 22 | } 23 | -------------------------------------------------------------------------------- /modules/spotify/keyboard.go: -------------------------------------------------------------------------------- 1 | package spotify 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gdamore/tcell/v2" 7 | ) 8 | 9 | func (widget *Widget) initializeKeyboardControls() { 10 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 11 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 12 | 13 | widget.SetKeyboardChar("l", widget.next, "Select next item") 14 | widget.SetKeyboardChar("h", widget.previous, "Select previous item") 15 | widget.SetKeyboardChar(" ", widget.playPause, "Play/pause song") 16 | 17 | widget.SetKeyboardKey(tcell.KeyDown, widget.next, "Select next item") 18 | widget.SetKeyboardKey(tcell.KeyUp, widget.previous, "Select previous item") 19 | } 20 | 21 | func (widget *Widget) previous() { 22 | widget.client.Previous() 23 | time.Sleep(time.Second * 1) 24 | widget.Refresh() 25 | } 26 | 27 | func (widget *Widget) next() { 28 | widget.client.Next() 29 | time.Sleep(time.Second * 1) 30 | widget.Refresh() 31 | } 32 | 33 | func (widget *Widget) playPause() { 34 | widget.client.PlayPause() 35 | time.Sleep(time.Second * 1) 36 | widget.Refresh() 37 | } 38 | -------------------------------------------------------------------------------- /modules/spotify/settings.go: -------------------------------------------------------------------------------- 1 | package spotify 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = true 10 | defaultTitle = "Spotify" 11 | ) 12 | 13 | type colors struct { 14 | label string 15 | text string 16 | } 17 | 18 | type Settings struct { 19 | colors 20 | *cfg.Common 21 | } 22 | 23 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 24 | settings := Settings{ 25 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 26 | } 27 | 28 | settings.label = ymlConfig.UString("colors.label", "green") 29 | settings.text = ymlConfig.UString("colors.text", "white") 30 | 31 | return &settings 32 | } 33 | -------------------------------------------------------------------------------- /modules/spotifyweb/settings.go: -------------------------------------------------------------------------------- 1 | package spotifyweb 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = true 12 | defaultTitle = "Spotify Web" 13 | ) 14 | 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | callbackPort string 19 | clientID string 20 | secretKey string 21 | } 22 | 23 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 24 | 25 | settings := Settings{ 26 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 27 | 28 | callbackPort: ymlConfig.UString("callbackPort", "8080"), 29 | clientID: ymlConfig.UString("clientID", os.Getenv("SPOTIFY_ID")), 30 | secretKey: ymlConfig.UString("secretKey", os.Getenv("SPOTIFY_SECRET")), 31 | } 32 | 33 | cfg.ModuleSecret(name, globalConfig, &settings.secretKey).Load() 34 | 35 | return &settings 36 | } 37 | -------------------------------------------------------------------------------- /modules/status/settings.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "Status" 11 | ) 12 | 13 | type Settings struct { 14 | *cfg.Common 15 | } 16 | 17 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 18 | settings := Settings{ 19 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 20 | } 21 | 22 | return &settings 23 | } 24 | -------------------------------------------------------------------------------- /modules/status/widget.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | "github.com/rivo/tview" 5 | "github.com/wtfutil/wtf/view" 6 | ) 7 | 8 | type Widget struct { 9 | view.TextWidget 10 | 11 | CurrentIcon int 12 | 13 | settings *Settings 14 | } 15 | 16 | func NewWidget(tviewApp *tview.Application, redrawChan chan bool, settings *Settings) *Widget { 17 | widget := Widget{ 18 | TextWidget: view.NewTextWidget(tviewApp, redrawChan, nil, settings.Common), 19 | 20 | CurrentIcon: 0, 21 | 22 | settings: settings, 23 | } 24 | 25 | return &widget 26 | } 27 | 28 | /* -------------------- Exported Functions -------------------- */ 29 | 30 | func (widget *Widget) Refresh() { 31 | widget.Redraw(widget.animation) 32 | } 33 | 34 | /* -------------------- Unexported Functions -------------------- */ 35 | 36 | func (widget *Widget) animation() (string, string, bool) { 37 | icons := []string{"|", "/", "-", "\\", "|"} 38 | next := icons[widget.CurrentIcon] 39 | 40 | widget.CurrentIcon++ 41 | if widget.CurrentIcon == len(icons) { 42 | widget.CurrentIcon = 0 43 | } 44 | 45 | return widget.CommonSettings().Title, next, false 46 | } 47 | -------------------------------------------------------------------------------- /modules/steam/keyboard.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 10 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 11 | 12 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 13 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 14 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 15 | } 16 | -------------------------------------------------------------------------------- /modules/steam/settings.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | "github.com/wtfutil/wtf/utils" 9 | ) 10 | 11 | const ( 12 | defaultFocusable = true 13 | ) 14 | 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | numberOfResults int `help:"Number of rows to show. Default is 10." optional:"true"` 19 | key string `help:"Steam API key (default is env var STEAM_API_KEY)"` 20 | userIds []string `help:"Steam user ids" optional:"true"` 21 | } 22 | 23 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 24 | steam := ymlConfig.UString("steam") 25 | settings := Settings{ 26 | Common: cfg.NewCommonSettingsFromModule(name, steam, defaultFocusable, ymlConfig, globalConfig), 27 | 28 | numberOfResults: ymlConfig.UInt("numberOfResults", 10), 29 | key: ymlConfig.UString("key", os.Getenv("STEAM_API_KEY")), 30 | userIds: utils.ToStrs(ymlConfig.UList("userIds", make([]interface{}, 0))), 31 | } 32 | return &settings 33 | } 34 | -------------------------------------------------------------------------------- /modules/stocks/finnhub/quote.go: -------------------------------------------------------------------------------- 1 | package finnhub 2 | 3 | type Quote struct { 4 | C float64 `json:"c"` 5 | H float64 `json:"h"` 6 | L float64 `json:"l"` 7 | O float64 `json:"o"` 8 | Pc float64 `json:"pc"` 9 | T int `json:"t"` 10 | 11 | Stock string 12 | } 13 | -------------------------------------------------------------------------------- /modules/stocks/finnhub/settings.go: -------------------------------------------------------------------------------- 1 | package finnhub 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | "github.com/wtfutil/wtf/utils" 9 | ) 10 | 11 | const ( 12 | defaultFocusable = true 13 | defaultTitle = "📈 Stocks Price" 14 | ) 15 | 16 | // Settings defines the configuration properties for this module 17 | type Settings struct { 18 | *cfg.Common 19 | 20 | apiKey string `help:"Your finnhub API token."` 21 | symbols []string `help:"An array of stocks symbols (i.e. AAPL, MSFT)"` 22 | } 23 | 24 | // NewSettingsFromYAML creates a new settings instance from a YAML config block 25 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 26 | 27 | settings := Settings{ 28 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 29 | 30 | apiKey: ymlConfig.UString("apiKey", ymlConfig.UString("apikey", os.Getenv("WTF_FINNHUB_API_KEY"))), 31 | symbols: utils.ToStrs(ymlConfig.UList("symbols")), 32 | } 33 | 34 | cfg.ModuleSecret(name, globalConfig, &settings.apiKey).Load() 35 | 36 | return &settings 37 | } 38 | -------------------------------------------------------------------------------- /modules/subreddit/keyboard.go: -------------------------------------------------------------------------------- 1 | package subreddit 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 10 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 11 | widget.SetKeyboardChar("o", widget.openLink, "Open target URL in browser") 12 | widget.SetKeyboardChar("c", widget.openReddit, "Open Reddit comments in browser") 13 | 14 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 15 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 16 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openReddit, "Open story in browser") 17 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 18 | } 19 | -------------------------------------------------------------------------------- /modules/subreddit/link.go: -------------------------------------------------------------------------------- 1 | package subreddit 2 | 3 | type Link struct { 4 | Score int `json:"ups"` 5 | Title string `json:"title"` 6 | ItemURL string `json:"url"` 7 | Permalink string `json:"permalink"` 8 | } 9 | 10 | type RedditDocument struct { 11 | Data Subreddit `json:"data"` 12 | } 13 | 14 | type RedditLinkDocument struct { 15 | Data Link `json:"data"` 16 | } 17 | 18 | type Subreddit struct { 19 | Children []RedditLinkDocument `json:"Children"` 20 | } 21 | -------------------------------------------------------------------------------- /modules/subreddit/settings.go: -------------------------------------------------------------------------------- 1 | package subreddit 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = true 10 | ) 11 | 12 | // Settings contains the settings for the subreddit view 13 | type Settings struct { 14 | *cfg.Common 15 | 16 | subreddit string `help:"Subreddit to look at" optional:"false"` 17 | numberOfPosts int `help:"Number of posts to show. Default is 10." optional:"true"` 18 | sortOrder string `help:"Sort order for the posts (hot, new, rising, top), default hot" optional:"true"` 19 | topTimePeriod string `help:"If top sort is selected, the time period to show posts from (hour, week, day, month, year, all, default all)"` 20 | } 21 | 22 | // NewSettingsFromYAML creates the settings for this module from a yaml file 23 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 24 | subreddit := ymlConfig.UString("subreddit") 25 | settings := Settings{ 26 | Common: cfg.NewCommonSettingsFromModule(name, subreddit, defaultFocusable, ymlConfig, globalConfig), 27 | 28 | numberOfPosts: ymlConfig.UInt("numberOfPosts", 10), 29 | sortOrder: ymlConfig.UString("sortOrder", "hot"), 30 | topTimePeriod: ymlConfig.UString("topTimePeriod", "all"), 31 | subreddit: subreddit, 32 | } 33 | 34 | return &settings 35 | } 36 | -------------------------------------------------------------------------------- /modules/system/settings.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "System" 11 | ) 12 | 13 | type Settings struct { 14 | *cfg.Common 15 | } 16 | 17 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 18 | settings := Settings{ 19 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 20 | } 21 | 22 | return &settings 23 | } 24 | -------------------------------------------------------------------------------- /modules/system/system_info.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package system 4 | 5 | import ( 6 | "os/exec" 7 | "runtime" 8 | "strings" 9 | 10 | "github.com/wtfutil/wtf/utils" 11 | ) 12 | 13 | type SystemInfo struct { 14 | ProductName string 15 | ProductVersion string 16 | BuildVersion string 17 | } 18 | 19 | func NewSystemInfo() *SystemInfo { 20 | m := make(map[string]string) 21 | 22 | arg := []string{} 23 | 24 | var cmd *exec.Cmd 25 | switch runtime.GOOS { 26 | case "linux": 27 | arg = append(arg, "-a") 28 | cmd = exec.Command("lsb_release", arg...) 29 | case "darwin": 30 | cmd = exec.Command("sw_vers", arg...) 31 | default: 32 | cmd = exec.Command("sw_vers", arg...) 33 | } 34 | 35 | raw := utils.ExecuteCommand(cmd) 36 | 37 | for _, row := range strings.Split(raw, "\n") { 38 | parts := strings.Split(row, ":") 39 | if len(parts) < 2 { 40 | continue 41 | } 42 | 43 | m[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) 44 | } 45 | 46 | var sysInfo *SystemInfo 47 | switch runtime.GOOS { 48 | case "linux": 49 | sysInfo = &SystemInfo{ 50 | ProductName: m["Distributor ID"], 51 | ProductVersion: m["Description"], 52 | BuildVersion: m["Release"], 53 | } 54 | default: 55 | sysInfo = &SystemInfo{ 56 | ProductName: m["ProductName"], 57 | ProductVersion: m["ProductVersion"], 58 | BuildVersion: m["BuildVersion"], 59 | } 60 | 61 | } 62 | return sysInfo 63 | } 64 | -------------------------------------------------------------------------------- /modules/system/system_info_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package system 4 | 5 | import ( 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | type SystemInfo struct { 11 | ProductName string 12 | ProductVersion string 13 | BuildVersion string 14 | } 15 | 16 | func NewSystemInfo() *SystemInfo { 17 | m := make(map[string]string) 18 | 19 | cmd := exec.Command("powershell.exe", "(Get-CimInstance Win32_OperatingSystem).version") 20 | out, err := cmd.Output() 21 | if err != nil { 22 | panic(err) 23 | } 24 | s := strings.Split(string(out), ".") 25 | m["ProductName"] = "Windows" 26 | m["ProductVersion"] = "Windows " + s[0] + "." + s[1] 27 | m["BuildVersion"] = s[2] 28 | 29 | sysInfo := SystemInfo{ 30 | ProductName: m["ProductName"], 31 | ProductVersion: m["ProductVersion"], 32 | BuildVersion: m["BuildVersion"], 33 | } 34 | 35 | return &sysInfo 36 | } 37 | -------------------------------------------------------------------------------- /modules/system/widget.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/rivo/tview" 8 | "github.com/wtfutil/wtf/utils" 9 | "github.com/wtfutil/wtf/view" 10 | ) 11 | 12 | type Widget struct { 13 | view.TextWidget 14 | 15 | Date string 16 | Version string 17 | 18 | settings *Settings 19 | systemInfo *SystemInfo 20 | } 21 | 22 | func NewWidget(tviewApp *tview.Application, redrawChan chan bool, date, version string, settings *Settings) *Widget { 23 | widget := Widget{ 24 | TextWidget: view.NewTextWidget(tviewApp, redrawChan, nil, settings.Common), 25 | 26 | Date: date, 27 | 28 | settings: settings, 29 | Version: version, 30 | } 31 | 32 | widget.systemInfo = NewSystemInfo() 33 | 34 | return &widget 35 | } 36 | 37 | func (widget *Widget) display() (string, string, bool) { 38 | content := fmt.Sprintf( 39 | "%8s: %s\n%8s: %s\n\n%8s: %s\n%8s: %s", 40 | "Built", 41 | widget.prettyDate(), 42 | "Vers", 43 | widget.Version, 44 | "OS", 45 | widget.systemInfo.ProductVersion, 46 | "Build", 47 | widget.systemInfo.BuildVersion, 48 | ) 49 | 50 | return widget.CommonSettings().Title, content, false 51 | } 52 | 53 | func (widget *Widget) Refresh() { 54 | widget.Redraw(widget.display) 55 | } 56 | 57 | func (widget *Widget) prettyDate() string { 58 | str, err := time.Parse(utils.TimestampFormat, widget.Date) 59 | 60 | if err != nil { 61 | return err.Error() 62 | } 63 | 64 | return str.Format("Jan _2, 15:04") 65 | } 66 | -------------------------------------------------------------------------------- /modules/textfile/keyboard.go: -------------------------------------------------------------------------------- 1 | package textfile 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | "github.com/wtfutil/wtf/utils" 6 | ) 7 | 8 | func (widget *Widget) initializeKeyboardControls() { 9 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 10 | widget.InitializeRefreshKeyboardControl(nil) 11 | 12 | widget.SetKeyboardChar("l", widget.NextSource, "Select next file") 13 | widget.SetKeyboardChar("h", widget.PrevSource, "Select previous file") 14 | widget.SetKeyboardChar("o", widget.openFile, "Open file") 15 | 16 | widget.SetKeyboardKey(tcell.KeyRight, widget.NextSource, "Select next file") 17 | widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevSource, "Select previous file") 18 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openFile, "Open file") 19 | } 20 | 21 | func (widget *Widget) openFile() { 22 | src := widget.CurrentSource() 23 | utils.OpenFile(src) 24 | } 25 | -------------------------------------------------------------------------------- /modules/textfile/settings.go: -------------------------------------------------------------------------------- 1 | package textfile 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = true 10 | defaultTitle = "Textfile" 11 | ) 12 | 13 | // Settings defines the configuration properties for this module 14 | type Settings struct { 15 | *cfg.Common 16 | 17 | filePaths []interface{} 18 | format bool 19 | formatStyle string 20 | wrapText bool 21 | } 22 | 23 | // NewSettingsFromYAML creates a new settings instance from a YAML config block 24 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 25 | 26 | settings := Settings{ 27 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 28 | 29 | filePaths: ymlConfig.UList("filePaths"), 30 | format: ymlConfig.UBool("format", false), 31 | formatStyle: ymlConfig.UString("formatStyle", "vim"), 32 | wrapText: ymlConfig.UBool("wrapText", true), 33 | } 34 | 35 | return &settings 36 | } 37 | -------------------------------------------------------------------------------- /modules/todo_plus/backend/backend.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | ) 6 | 7 | type Backend interface { 8 | Title() string 9 | Setup(*config.Config) 10 | BuildProjects() []*Project 11 | GetProject(string) *Project 12 | LoadTasks(string) ([]Task, error) 13 | CloseTask(*Task) error 14 | DeleteTask(*Task) error 15 | Sources() []string 16 | } 17 | -------------------------------------------------------------------------------- /modules/todo_plus/backend/project.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | type Task struct { 4 | ID string 5 | Completed bool 6 | Name string 7 | } 8 | 9 | type Project struct { 10 | ID string 11 | Name string 12 | 13 | Index int 14 | Tasks []Task 15 | Err error 16 | backend Backend 17 | } 18 | 19 | func (proj *Project) IsLast() bool { 20 | return proj.Index >= len(proj.Tasks)-1 21 | } 22 | 23 | func (proj *Project) loadTasks() { 24 | Tasks, err := proj.backend.LoadTasks(proj.ID) 25 | proj.Err = err 26 | proj.Tasks = Tasks 27 | } 28 | 29 | func (proj *Project) LongestLine() int { 30 | maxLen := 0 31 | 32 | for _, task := range proj.Tasks { 33 | if len(task.Name) > maxLen { 34 | maxLen = len(task.Name) 35 | } 36 | } 37 | 38 | return maxLen 39 | } 40 | 41 | func (proj *Project) currentTask() *Task { 42 | if proj.Index < 0 { 43 | return nil 44 | } 45 | 46 | return &proj.Tasks[proj.Index] 47 | } 48 | 49 | func (proj *Project) CloseSelectedTask() { 50 | currTask := proj.currentTask() 51 | 52 | if currTask != nil { 53 | _ = proj.backend.CloseTask(currTask) 54 | proj.loadTasks() 55 | } 56 | } 57 | 58 | func (proj *Project) DeleteSelectedTask() { 59 | currTask := proj.currentTask() 60 | 61 | if currTask != nil { 62 | _ = proj.backend.DeleteTask(currTask) 63 | 64 | proj.loadTasks() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /modules/todo_plus/display.go: -------------------------------------------------------------------------------- 1 | package todo_plus 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/rivo/tview" 7 | "github.com/wtfutil/wtf/utils" 8 | ) 9 | 10 | func (widget *Widget) content() (string, string, bool) { 11 | proj := widget.CurrentProject() 12 | 13 | if proj == nil { 14 | return widget.CommonSettings().Title, "", false 15 | } 16 | 17 | if proj.Err != nil { 18 | return widget.CommonSettings().Title, proj.Err.Error(), true 19 | } 20 | 21 | title := fmt.Sprintf( 22 | "[%s]%s[white]", 23 | widget.settings.Colors.Title, 24 | proj.Name) 25 | 26 | str := "" 27 | 28 | for idx, item := range proj.Tasks { 29 | row := fmt.Sprintf( 30 | `[%s]| | %s[%s]`, 31 | widget.RowColor(idx), 32 | tview.Escape(item.Name), 33 | widget.RowColor(idx), 34 | ) 35 | 36 | str += utils.HighlightableHelper(widget.View, row, idx, len(item.Name)) 37 | } 38 | return title, str, false 39 | } 40 | 41 | func (widget *Widget) display() { 42 | widget.Redraw(widget.content) 43 | } 44 | -------------------------------------------------------------------------------- /modules/todo_plus/keyboard.go: -------------------------------------------------------------------------------- 1 | package todo_plus 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("d", widget.Delete, "Delete item") 10 | widget.SetKeyboardChar("j", widget.Prev, "Select previous item") 11 | widget.SetKeyboardChar("k", widget.Next, "Select next item") 12 | widget.SetKeyboardChar("h", widget.PrevSource, "Select previous project") 13 | widget.SetKeyboardChar("c", widget.Close, "Close item") 14 | widget.SetKeyboardChar("l", widget.NextSource, "Select next project") 15 | widget.SetKeyboardChar("u", widget.Unselect, "Clear selection") 16 | 17 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 18 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 19 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 20 | widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevSource, "Select previous project") 21 | widget.SetKeyboardKey(tcell.KeyRight, widget.NextSource, "Select next project") 22 | } 23 | -------------------------------------------------------------------------------- /modules/transmission/keyboard.go: -------------------------------------------------------------------------------- 1 | package transmission 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(nil) 8 | 9 | widget.SetKeyboardChar("j", widget.Prev, "Select previous item") 10 | widget.SetKeyboardChar("k", widget.Next, "Select next item") 11 | widget.SetKeyboardChar("u", widget.Unselect, "Clear selection") 12 | 13 | widget.SetKeyboardKey(tcell.KeyCtrlD, widget.deleteSelectedTorrent, "Delete the selected torrent") 14 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 15 | widget.SetKeyboardKey(tcell.KeyEnter, widget.pauseUnpauseTorrent, "Pause/unpause torrent") 16 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 17 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 18 | } 19 | -------------------------------------------------------------------------------- /modules/travisci/keyboard.go: -------------------------------------------------------------------------------- 1 | package travisci 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 10 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 11 | widget.SetKeyboardChar("o", widget.openBuild, "Open item in browser") 12 | 13 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 14 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 15 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 16 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openBuild, "Open item in browser") 17 | } 18 | -------------------------------------------------------------------------------- /modules/travisci/settings.go: -------------------------------------------------------------------------------- 1 | package travisci 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = true 12 | defaultTitle = "TravisCI" 13 | ) 14 | 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | apiKey string 19 | baseURL string `help:"Your TravisCI Enterprise API URL." optional:"true"` 20 | compact bool 21 | limit string 22 | pro bool 23 | sort_by string 24 | } 25 | 26 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 27 | settings := Settings{ 28 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 29 | 30 | apiKey: ymlConfig.UString("apiKey", ymlConfig.UString("apikey", os.Getenv("WTF_TRAVIS_API_TOKEN"))), 31 | baseURL: ymlConfig.UString("baseURL", ymlConfig.UString("baseURL", os.Getenv("WTF_TRAVIS_BASE_URL"))), 32 | pro: ymlConfig.UBool("pro", false), 33 | compact: ymlConfig.UBool("compact", false), 34 | limit: ymlConfig.UString("limit", "10"), 35 | sort_by: ymlConfig.UString("sort_by", "id:desc"), 36 | } 37 | 38 | cfg.ModuleSecret(name, globalConfig, &settings.apiKey). 39 | Service(settings.baseURL).Load() 40 | 41 | return &settings 42 | } 43 | -------------------------------------------------------------------------------- /modules/travisci/travis.go: -------------------------------------------------------------------------------- 1 | package travisci 2 | 3 | type Builds struct { 4 | Builds []Build `json:"builds"` 5 | } 6 | 7 | type Build struct { 8 | ID int `json:"id"` 9 | CreatedBy Owner `json:"created_by"` 10 | Branch Branch `json:"branch"` 11 | Number string `json:"number"` 12 | Repository Repository `json:"repository"` 13 | Commit Commit `json:"commit"` 14 | State string `json:"state"` 15 | } 16 | 17 | type Owner struct { 18 | Login string `json:"login"` 19 | } 20 | 21 | type Branch struct { 22 | Name string `json:"name"` 23 | } 24 | 25 | type Repository struct { 26 | Name string `json:"name"` 27 | Slug string `json:"slug"` 28 | } 29 | 30 | type Commit struct { 31 | Message string `json:"message"` 32 | } 33 | -------------------------------------------------------------------------------- /modules/twitch/keyboard.go: -------------------------------------------------------------------------------- 1 | package twitch 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 10 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 11 | widget.SetKeyboardChar("o", widget.openTwitch, "Open target URL in browser") 12 | widget.SetKeyboardChar("s", widget.openStreamlink, "Open target stream via streamlink (github.com/streamlink/streamlink)") 13 | 14 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 15 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 16 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openTwitch, "Open stream in browser") 17 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 18 | } 19 | -------------------------------------------------------------------------------- /modules/twitter/keyboard.go: -------------------------------------------------------------------------------- 1 | package twitter 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | "github.com/wtfutil/wtf/utils" 6 | ) 7 | 8 | func (widget *Widget) initializeKeyboardControls() { 9 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 10 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 11 | 12 | widget.SetKeyboardChar("l", widget.NextSource, "Select next source") 13 | widget.SetKeyboardChar("h", widget.PrevSource, "Select previous source") 14 | widget.SetKeyboardChar("o", widget.openFile, "Open source") 15 | 16 | widget.SetKeyboardKey(tcell.KeyRight, widget.NextSource, "Select next source") 17 | widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevSource, "Select previous source") 18 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openFile, "Open source") 19 | } 20 | 21 | func (widget *Widget) openFile() { 22 | src := widget.currentSourceURI() 23 | utils.OpenFile(src) 24 | } 25 | -------------------------------------------------------------------------------- /modules/twitter/request.go: -------------------------------------------------------------------------------- 1 | package twitter 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | ) 7 | 8 | func Request(httpClient *http.Client, apiURL string) ([]byte, error) { 9 | resp, err := httpClient.Get(apiURL) 10 | if err != nil { 11 | return nil, err 12 | } 13 | defer func() { _ = resp.Body.Close() }() 14 | 15 | data, err := ParseBody(resp) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | return data, err 21 | } 22 | 23 | func ParseBody(resp *http.Response) ([]byte, error) { 24 | var buffer bytes.Buffer 25 | _, err := buffer.ReadFrom(resp.Body) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return buffer.Bytes(), nil 31 | } 32 | -------------------------------------------------------------------------------- /modules/twitter/settings.go: -------------------------------------------------------------------------------- 1 | package twitter 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = true 12 | defaultTitle = "Twitter" 13 | ) 14 | 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | bearerToken string 19 | consumerKey string 20 | consumerSecret string 21 | count int 22 | screenNames []interface{} 23 | } 24 | 25 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 26 | settings := Settings{ 27 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 28 | 29 | bearerToken: ymlConfig.UString("bearerToken", os.Getenv("WTF_TWITTER_BEARER_TOKEN")), 30 | consumerKey: ymlConfig.UString("consumerKey", os.Getenv("WTF_TWITTER_CONSUMER_KEY")), 31 | consumerSecret: ymlConfig.UString("consumerSecret", os.Getenv("WTF_TWITTER_CONSUMER_SECRET")), 32 | count: ymlConfig.UInt("count", 5), 33 | screenNames: ymlConfig.UList("screenName"), 34 | } 35 | 36 | settings.SetDocumentationPath("twitter/tweets") 37 | 38 | return &settings 39 | } 40 | -------------------------------------------------------------------------------- /modules/twitter/tweet.go: -------------------------------------------------------------------------------- 1 | package twitter 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type Tweet struct { 9 | User User `json:"user"` 10 | Text string `json:"text"` 11 | CreatedAt string `json:"created_at"` 12 | } 13 | 14 | func (tweet *Tweet) String() string { 15 | return fmt.Sprintf("Tweet: %s at %s by %s", tweet.Text, tweet.CreatedAt, tweet.User.ScreenName) 16 | } 17 | 18 | /* -------------------- Exported Functions -------------------- */ 19 | 20 | func (tweet *Tweet) Username() string { 21 | return tweet.User.ScreenName 22 | } 23 | 24 | func (tweet *Tweet) Created() time.Time { 25 | newTime, _ := time.Parse(time.RubyDate, tweet.CreatedAt) 26 | return newTime 27 | } 28 | 29 | func (tweet *Tweet) PrettyCreatedAt() string { 30 | newTime := tweet.Created() 31 | return fmt.Sprint(newTime.Format("Jan 2, 2006")) 32 | } 33 | -------------------------------------------------------------------------------- /modules/twitter/user.go: -------------------------------------------------------------------------------- 1 | package twitter 2 | 3 | // User is used as part of the Tweet struct to get user information 4 | type User struct { 5 | ScreenName string `json:"screen_name"` 6 | } 7 | -------------------------------------------------------------------------------- /modules/twitterstats/settings.go: -------------------------------------------------------------------------------- 1 | package twitterstats 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = true 12 | defaultTitle = "Twitter Stats" 13 | ) 14 | 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | bearerToken string 19 | consumerKey string 20 | consumerSecret string 21 | screenNames []interface{} 22 | } 23 | 24 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 25 | settings := Settings{ 26 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 27 | 28 | bearerToken: ymlConfig.UString("bearerToken", os.Getenv("WTF_TWITTER_BEARER_TOKEN")), 29 | consumerKey: ymlConfig.UString("consumerKey", os.Getenv("WTF_TWITTER_CONSUMER_KEY")), 30 | consumerSecret: ymlConfig.UString("consumerSecret", os.Getenv("WTF_TWITTER_CONSUMER_SECRET")), 31 | 32 | screenNames: ymlConfig.UList("screenNames"), 33 | } 34 | 35 | settings.SetDocumentationPath("twitter/stats") 36 | 37 | return &settings 38 | } 39 | -------------------------------------------------------------------------------- /modules/twitterstats/widget.go: -------------------------------------------------------------------------------- 1 | package twitterstats 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/rivo/tview" 7 | "github.com/wtfutil/wtf/view" 8 | ) 9 | 10 | type Widget struct { 11 | view.TextWidget 12 | 13 | client *Client 14 | settings *Settings 15 | } 16 | 17 | func NewWidget(tviewApp *tview.Application, redrawChan chan bool, _ *tview.Pages, settings *Settings) *Widget { 18 | widget := Widget{ 19 | TextWidget: view.NewTextWidget(tviewApp, redrawChan, nil, settings.Common), 20 | 21 | client: NewClient(settings), 22 | settings: settings, 23 | } 24 | 25 | widget.View.SetBorderPadding(1, 1, 1, 1) 26 | widget.View.SetWrap(false) 27 | widget.View.SetWordWrap(true) 28 | 29 | return &widget 30 | } 31 | 32 | func (widget *Widget) Refresh() { 33 | widget.Redraw(widget.content) 34 | } 35 | 36 | func (widget *Widget) content() (string, string, bool) { 37 | // Add header row 38 | str := fmt.Sprintf( 39 | "[%s]%-12s %10s %8s[white]\n", 40 | widget.settings.Colors.Subheading, 41 | "Username", 42 | "Followers", 43 | "Tweets", 44 | ) 45 | 46 | stats := widget.client.GetStats() 47 | 48 | // Add rows for each of the followed usernames 49 | for i, username := range widget.client.screenNames { 50 | str += fmt.Sprintf( 51 | "%-12s %10d %8d\n", 52 | username, 53 | stats[i].FollowerCount, 54 | stats[i].TweetCount, 55 | ) 56 | } 57 | 58 | return "Twitter Stats", str, false 59 | } 60 | -------------------------------------------------------------------------------- /modules/unknown/settings.go: -------------------------------------------------------------------------------- 1 | package unknown 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "Unknown" 11 | ) 12 | 13 | type Settings struct { 14 | *cfg.Common 15 | } 16 | 17 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 18 | settings := Settings{ 19 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 20 | } 21 | 22 | return &settings 23 | } 24 | -------------------------------------------------------------------------------- /modules/unknown/widget.go: -------------------------------------------------------------------------------- 1 | package unknown 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/rivo/tview" 7 | "github.com/wtfutil/wtf/view" 8 | ) 9 | 10 | type Widget struct { 11 | view.TextWidget 12 | 13 | settings *Settings 14 | } 15 | 16 | func NewWidget(tviewApp *tview.Application, redrawChan chan bool, settings *Settings) *Widget { 17 | widget := Widget{ 18 | TextWidget: view.NewTextWidget(tviewApp, redrawChan, nil, settings.Common), 19 | 20 | settings: settings, 21 | } 22 | 23 | return &widget 24 | } 25 | 26 | /* -------------------- Exported Functions -------------------- */ 27 | 28 | func (widget *Widget) Refresh() { 29 | content := fmt.Sprintf("Widget %s and/or type %s does not exist", widget.Name(), widget.CommonSettings().Type) 30 | widget.Redraw(func() (string, string, bool) { return widget.CommonSettings().Title, content, true }) 31 | } 32 | -------------------------------------------------------------------------------- /modules/updown/keyboard.go: -------------------------------------------------------------------------------- 1 | package updown 2 | 3 | func (widget *Widget) initializeKeyboardControls() { 4 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 5 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 6 | } 7 | -------------------------------------------------------------------------------- /modules/updown/settings.go: -------------------------------------------------------------------------------- 1 | package updown 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | "github.com/wtfutil/wtf/utils" 9 | ) 10 | 11 | const ( 12 | defaultFocusable = true 13 | defaultTitle = "Updown.io" 14 | ) 15 | 16 | type Settings struct { 17 | *cfg.Common 18 | 19 | apiKey string `help:"An Updown API key." optional:"false"` 20 | tokens []string `help:"Filters the checks and returns only the checks with the specified tokens"` 21 | } 22 | 23 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 24 | 25 | settings := Settings{ 26 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 27 | 28 | apiKey: ymlConfig.UString("apiKey", os.Getenv("WTF_UPDOWN_APIKEY")), 29 | tokens: utils.ToStrs(ymlConfig.UList("tokens")), 30 | } 31 | 32 | cfg.ModuleSecret(name, globalConfig, &settings.apiKey).Load() 33 | 34 | return &settings 35 | } 36 | -------------------------------------------------------------------------------- /modules/uptimerobot/keyboard.go: -------------------------------------------------------------------------------- 1 | package uptimerobot 2 | 3 | func (widget *Widget) initializeKeyboardControls() { 4 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 5 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 6 | } 7 | -------------------------------------------------------------------------------- /modules/uptimerobot/settings.go: -------------------------------------------------------------------------------- 1 | package uptimerobot 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = true 12 | defaultTitle = "Uptime Robot" 13 | ) 14 | 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | apiKey string `help:"An UptimeRobot API key."` 19 | uptimePeriods string `help:"The periods over which to display uptime (in days, dash-separated)." optional:"true"` 20 | offlineFirst bool `help:"Display offline monitors at the top." optional:"true"` 21 | } 22 | 23 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 24 | 25 | settings := Settings{ 26 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 27 | 28 | apiKey: ymlConfig.UString("apiKey", os.Getenv("WTF_UPTIMEROBOT_APIKEY")), 29 | uptimePeriods: ymlConfig.UString("uptimePeriods", "30"), 30 | offlineFirst: ymlConfig.UBool("offlineFirst", false), 31 | } 32 | 33 | cfg.ModuleSecret(name, globalConfig, &settings.apiKey). 34 | Service("https://api.uptimerobot.com").Load() 35 | 36 | return &settings 37 | } 38 | -------------------------------------------------------------------------------- /modules/urlcheck/client.go: -------------------------------------------------------------------------------- 1 | package urlcheck 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/wtfutil/wtf/logger" 11 | ) 12 | 13 | // Perform the requet of the header for a given URL 14 | func DoRequest(urlRequest string, timeout time.Duration, client *http.Client) (int, string) { 15 | 16 | // Define a Context with the timeout for the request 17 | ctx, cancel := context.WithTimeout(context.Background(), timeout) 18 | defer cancel() 19 | 20 | // Request 21 | req, err := http.NewRequest(http.MethodHead, urlRequest, nil) 22 | if err != nil { 23 | logger.Log(fmt.Sprintf("[urlcheck] ERROR %s: %s", urlRequest, err.Error())) 24 | return InvalidResultCode, "New Request Error" 25 | } 26 | req = req.WithContext(ctx) 27 | 28 | // Send the request 29 | res, err := client.Do(req) 30 | if err != nil { 31 | if errors.Is(err, context.DeadlineExceeded) { 32 | status := "Timeout" 33 | logger.Log(fmt.Sprintf("[urlcheck] %s: %s", urlRequest, status)) 34 | return InvalidResultCode, status 35 | } 36 | logger.Log(fmt.Sprintf("[urlcheck] %s: %s", urlRequest, err.Error())) 37 | return InvalidResultCode, "Error" 38 | } 39 | 40 | defer res.Body.Close() 41 | 42 | return res.StatusCode, res.Status 43 | } 44 | -------------------------------------------------------------------------------- /modules/urlcheck/client_test.go: -------------------------------------------------------------------------------- 1 | package urlcheck 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | "time" 8 | 9 | "gotest.tools/assert" 10 | ) 11 | 12 | func TestTimeout(t *testing.T) { 13 | 14 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 | time.Sleep(time.Second * 1) 16 | })) 17 | defer ts.Close() 18 | 19 | client := &http.Client{ 20 | Timeout: time.Millisecond * 10, 21 | } 22 | 23 | timeout := 1 * time.Microsecond 24 | statusCode, statusMsg := DoRequest(ts.URL, timeout, client) 25 | 26 | assert.Equal(t, 999, statusCode) 27 | assert.Equal(t, "Timeout", statusMsg) 28 | 29 | } 30 | -------------------------------------------------------------------------------- /modules/urlcheck/settings.go: -------------------------------------------------------------------------------- 1 | package urlcheck 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "URLcheck" 11 | ) 12 | 13 | type Settings struct { 14 | Common *cfg.Common 15 | 16 | requestTimeout int `help:"Max Request duration in seconds"` 17 | urls []string `help:"A list of URL to check"` 18 | } 19 | 20 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 21 | settings := Settings{ 22 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 23 | 24 | requestTimeout: ymlConfig.UInt("timeout", 30), 25 | } 26 | settings.urls = cfg.ParseAsMapOrList(ymlConfig, "urls") 27 | return &settings 28 | } 29 | -------------------------------------------------------------------------------- /modules/urlcheck/urlResult.go: -------------------------------------------------------------------------------- 1 | package urlcheck 2 | 3 | import ( 4 | "net/url" 5 | ) 6 | 7 | const InvalidResultCode = 999 8 | 9 | // Collect useful properties of each given URL 10 | type urlResult struct { 11 | Url string 12 | ResultCode int 13 | ResultMessage string 14 | IsValid bool 15 | } 16 | 17 | // Create a UrlResult instance from an urls occurence in the settings 18 | func newUrlResult(urlString string) *urlResult { 19 | 20 | uResult := urlResult{ 21 | Url: urlString, 22 | } 23 | 24 | _, err := url.ParseRequestURI(urlString) 25 | if err != nil { 26 | uResult.ResultMessage = err.Error() 27 | uResult.ResultCode = InvalidResultCode 28 | uResult.IsValid = false 29 | return &uResult 30 | } 31 | 32 | uResult.IsValid = true 33 | return &uResult 34 | } 35 | -------------------------------------------------------------------------------- /modules/urlcheck/urlResult_test.go: -------------------------------------------------------------------------------- 1 | package urlcheck 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func checkValid(t *testing.T, got *urlResult) { 10 | assert.True(t, got.IsValid) 11 | assert.Less(t, got.ResultCode, 500) 12 | assert.Len(t, got.ResultMessage, 0) 13 | } 14 | 15 | func checkInvalid(t *testing.T, got *urlResult) { 16 | assert.False(t, got.IsValid) 17 | assert.GreaterOrEqual(t, got.ResultCode, 500) 18 | assert.Greater(t, len(got.ResultMessage), 0) 19 | } 20 | 21 | func Test_newUrlResult(t *testing.T) { 22 | type args struct { 23 | urlString string 24 | } 25 | type checks func(t *testing.T, res *urlResult) 26 | 27 | tests := []struct { 28 | name string 29 | args args 30 | checks checks 31 | }{ 32 | {"good", args{"http://www.go.dev"}, checkValid}, 33 | {"good_with_page", args{"https://go.dev/doc/install"}, checkValid}, 34 | {"good_with_args", args{"https://mysite.com?var=1"}, checkValid}, 35 | {"no_url", args{""}, checkInvalid}, 36 | {"no_escape_chars", args{"http://not\nurl.com?var=1"}, checkInvalid}, 37 | {"no_protocol", args{"go.dev"}, checkInvalid}, 38 | } 39 | for _, tt := range tests { 40 | t.Run(tt.name, func(t *testing.T) { 41 | if tt.checks != nil { 42 | tt.checks(t, newUrlResult(tt.args.urlString)) 43 | } 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /modules/victorops/oncallresponse.go: -------------------------------------------------------------------------------- 1 | package victorops 2 | 3 | // OnCallResponse object 4 | type OnCallResponse struct { 5 | TeamsOnCall []struct { 6 | Team struct { 7 | Name string `json:"name"` 8 | Slug string `json:"slug"` 9 | } `json:"team"` 10 | OnCallNow []struct { 11 | EscalationPolicy struct { 12 | Name string `json:"name"` 13 | Slug string `json:"slug"` 14 | } `json:"escalationPolicy"` 15 | Users []struct { 16 | OnCallUser struct { 17 | Username string `json:"username"` 18 | } `json:"onCalluser"` 19 | } `json:"users"` 20 | } `json:"oncallNow"` 21 | } `json:"teamsOnCall"` 22 | } 23 | -------------------------------------------------------------------------------- /modules/victorops/oncallteam.go: -------------------------------------------------------------------------------- 1 | package victorops 2 | 3 | // OnCallTeam object to make 4 | // managing objects easier 5 | type OnCallTeam struct { 6 | Name string 7 | Slug string 8 | OnCall []OnCall 9 | } 10 | 11 | // OnCall object to handle 12 | // different on call policies 13 | type OnCall struct { 14 | Policy string 15 | Userlist string 16 | } 17 | -------------------------------------------------------------------------------- /modules/victorops/settings.go: -------------------------------------------------------------------------------- 1 | package victorops 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = true 12 | defaultTitle = "VictorOps" 13 | ) 14 | 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | apiID string 19 | apiKey string 20 | team string 21 | } 22 | 23 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 24 | settings := Settings{ 25 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 26 | 27 | apiID: ymlConfig.UString("apiID", os.Getenv("WTF_VICTOROPS_API_ID")), 28 | apiKey: ymlConfig.UString("apiKey", ymlConfig.UString("apikey", os.Getenv("WTF_VICTOROPS_API_KEY"))), 29 | team: ymlConfig.UString("team"), 30 | } 31 | 32 | cfg.ModuleSecret(name, globalConfig, &settings.apiKey).Load() 33 | 34 | return &settings 35 | } 36 | -------------------------------------------------------------------------------- /modules/weatherservices/arpansagovau/settings.go: -------------------------------------------------------------------------------- 1 | package arpansagovau 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "ARPANSA UV Data" 11 | ) 12 | 13 | type Settings struct { 14 | *cfg.Common 15 | 16 | city string 17 | } 18 | 19 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 20 | settings := Settings{ 21 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 22 | city: ymlConfig.UString("locationid"), 23 | } 24 | 25 | settings.SetDocumentationPath("weather_services/arpansagovau") 26 | 27 | return &settings 28 | } 29 | -------------------------------------------------------------------------------- /modules/weatherservices/prettyweather/settings.go: -------------------------------------------------------------------------------- 1 | package prettyweather 2 | 3 | import ( 4 | "github.com/olebedev/config" 5 | "github.com/wtfutil/wtf/cfg" 6 | ) 7 | 8 | const ( 9 | defaultFocusable = false 10 | defaultTitle = "Pretty Weather" 11 | ) 12 | 13 | type Settings struct { 14 | *cfg.Common 15 | 16 | city string 17 | unit string 18 | view string 19 | language string 20 | } 21 | 22 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 23 | settings := Settings{ 24 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 25 | 26 | city: ymlConfig.UString("city", "Barcelona"), 27 | language: ymlConfig.UString("language", "en"), 28 | unit: ymlConfig.UString("unit", "m"), 29 | view: ymlConfig.UString("view", "0"), 30 | } 31 | 32 | settings.SetDocumentationPath("weather_services/prettyweather") 33 | 34 | return &settings 35 | } 36 | -------------------------------------------------------------------------------- /modules/weatherservices/weather/keyboard.go: -------------------------------------------------------------------------------- 1 | package weather 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("h", widget.PrevSource, "Select previous city") 10 | widget.SetKeyboardChar("l", widget.NextSource, "Select next city") 11 | 12 | widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevSource, "Select previous city") 13 | widget.SetKeyboardKey(tcell.KeyRight, widget.NextSource, "Select next city") 14 | } 15 | -------------------------------------------------------------------------------- /modules/weatherservices/weather/settings.go: -------------------------------------------------------------------------------- 1 | package weather 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = true 12 | defaultTitle = "Weather" 13 | ) 14 | 15 | type colors struct { 16 | current string 17 | } 18 | 19 | type Settings struct { 20 | colors 21 | *cfg.Common 22 | 23 | apiKey string 24 | cityIDs []interface{} 25 | language string 26 | tempUnit string 27 | useEmoji bool 28 | compact bool 29 | } 30 | 31 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 32 | settings := Settings{ 33 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 34 | 35 | apiKey: ymlConfig.UString("apiKey", ymlConfig.UString("apikey", os.Getenv("WTF_OWM_API_KEY"))), 36 | cityIDs: ymlConfig.UList("cityids"), 37 | language: ymlConfig.UString("language", "EN"), 38 | tempUnit: ymlConfig.UString("tempUnit", "C"), 39 | useEmoji: ymlConfig.UBool("useEmoji", true), 40 | compact: ymlConfig.UBool("compact", false), 41 | } 42 | 43 | settings.SetDocumentationPath("weather_services/weather/") 44 | 45 | settings.current = ymlConfig.UString("colors.current", "green") 46 | 47 | return &settings 48 | } 49 | -------------------------------------------------------------------------------- /modules/zendesk/client.go: -------------------------------------------------------------------------------- 1 | package zendesk 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | ) 8 | 9 | type Resource struct { 10 | Response interface{} 11 | Raw string 12 | } 13 | 14 | func (widget *Widget) api(meth string) (*Resource, error) { 15 | trn := &http.Transport{} 16 | 17 | client := &http.Client{ 18 | Transport: trn, 19 | } 20 | 21 | baseURL := fmt.Sprintf("https://%v.zendesk.com/api/v2", widget.settings.subdomain) 22 | URL := baseURL + "/tickets.json?sort_by=status" 23 | 24 | req, err := http.NewRequest(meth, URL, http.NoBody) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | req.Header.Add("Content-Type", "application/json") 30 | 31 | apiUser := fmt.Sprintf("%v/token", widget.settings.username) 32 | req.SetBasicAuth(apiUser, widget.settings.apiKey) 33 | 34 | resp, err := client.Do(req) 35 | if err != nil { 36 | return nil, err 37 | } 38 | defer func() { _ = resp.Body.Close() }() 39 | 40 | data, err := io.ReadAll(resp.Body) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return &Resource{Response: &resp, Raw: string(data)}, nil 46 | } 47 | -------------------------------------------------------------------------------- /modules/zendesk/keyboard.go: -------------------------------------------------------------------------------- 1 | package zendesk 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | func (widget *Widget) initializeKeyboardControls() { 6 | widget.InitializeHelpTextKeyboardControl(widget.ShowHelp) 7 | widget.InitializeRefreshKeyboardControl(widget.Refresh) 8 | 9 | widget.SetKeyboardChar("j", widget.Next, "Select next item") 10 | widget.SetKeyboardChar("k", widget.Prev, "Select previous item") 11 | widget.SetKeyboardChar("o", widget.openTicket, "Open item") 12 | 13 | widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") 14 | widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") 15 | widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") 16 | widget.SetKeyboardKey(tcell.KeyEnter, widget.openTicket, "Open item") 17 | } 18 | -------------------------------------------------------------------------------- /modules/zendesk/settings.go: -------------------------------------------------------------------------------- 1 | package zendesk 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olebedev/config" 7 | "github.com/wtfutil/wtf/cfg" 8 | ) 9 | 10 | const ( 11 | defaultFocusable = true 12 | defaultTitle = "Zendesk" 13 | ) 14 | 15 | type Settings struct { 16 | *cfg.Common 17 | 18 | apiKey string 19 | status string 20 | subdomain string 21 | username string 22 | } 23 | 24 | func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { 25 | settings := Settings{ 26 | Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), 27 | 28 | apiKey: ymlConfig.UString("apiKey", ymlConfig.UString("apikey", os.Getenv("ZENDESK_API"))), 29 | status: ymlConfig.UString("status"), 30 | subdomain: ymlConfig.UString("subdomain", os.Getenv("ZENDESK_SUBDOMAIN")), 31 | username: ymlConfig.UString("username"), 32 | } 33 | 34 | cfg.ModuleSecret(name, globalConfig, &settings.apiKey).Load() 35 | 36 | return &settings 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "all-contributors-cli": "^6.20.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /scripts/check-uncommitted-vendor-files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | GOPROXY="https://proxy.golang.org,direct" GOSUMDB=off GO111MODULE=on go mod tidy 6 | 7 | untracked_files=$(git ls-files --others --exclude-standard | wc -l) 8 | 9 | diff_stat=$(git diff --shortstat) 10 | 11 | if [[ "${untracked_files}" -ne 0 || -n "${diff_stat}" ]]; then 12 | echo 'Untracked or diff in tracked vendor files found. Please run "go mod tidy" and commit the changes' 13 | exit 1 14 | fi 15 | -------------------------------------------------------------------------------- /utils/colors.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "fmt" 4 | 5 | // ColorizePercent provides a standard way to colorize percentages for which 6 | // large numbers are good (green) and small numbers are bad (red). 7 | func ColorizePercent(percent float64) string { 8 | var color string 9 | 10 | switch { 11 | case percent >= 70: 12 | color = "green" 13 | case percent >= 35: 14 | color = "yellow" 15 | case percent < 0: 16 | color = "grey" 17 | default: 18 | color = "red" 19 | } 20 | 21 | return fmt.Sprintf("[%s]%v[%s]", color, percent, "white") 22 | } 23 | -------------------------------------------------------------------------------- /utils/colors_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_ColorizePercent(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | percent float64 13 | expected string 14 | }{ 15 | { 16 | name: "with high percent", 17 | percent: 70, 18 | expected: "[green]70[white]", 19 | }, 20 | { 21 | name: "with medium percent", 22 | percent: 35, 23 | expected: "[yellow]35[white]", 24 | }, 25 | { 26 | name: "with low percent", 27 | percent: 1, 28 | expected: "[red]1[white]", 29 | }, 30 | { 31 | name: "with negative percent", 32 | percent: -5, 33 | expected: "[grey]-5[white]", 34 | }, 35 | } 36 | 37 | for _, tt := range tests { 38 | t.Run(tt.name, func(t *testing.T) { 39 | actual := ColorizePercent(tt.percent) 40 | assert.Equal(t, tt.expected, actual) 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /utils/email_addresses.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strings" 5 | 6 | "golang.org/x/text/cases" 7 | "golang.org/x/text/language" 8 | ) 9 | 10 | // NameFromEmail takes an email address and returns the part that comes before the @ symbol 11 | // 12 | // Example: 13 | // 14 | // NameFromEmail("test_user@example.com") 15 | // > "Test_user" 16 | func NameFromEmail(email string) string { 17 | parts := strings.Split(email, "@") 18 | name := strings.ReplaceAll(parts[0], ".", " ") 19 | 20 | c := cases.Title(language.English) 21 | return c.String(name) 22 | } 23 | 24 | // NamesFromEmails takes a slice of email addresses and returns a slice of the parts that 25 | // come before the @ symbol 26 | // 27 | // Example: 28 | // 29 | // NamesFromEmail("test_user@example.com", "other_user@example.com") 30 | // > []string{"Test_user", "Other_user"} 31 | func NamesFromEmails(emails []string) []string { 32 | names := make([]string, len(emails)) 33 | 34 | for i, email := range emails { 35 | names[i] = NameFromEmail(email) 36 | } 37 | 38 | return names 39 | } 40 | -------------------------------------------------------------------------------- /utils/email_addresses_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_NameFromEmail(t *testing.T) { 10 | assert.Equal(t, "", NameFromEmail("")) 11 | assert.Equal(t, "Chris Cummer", NameFromEmail("chris.cummer@me.com")) 12 | } 13 | 14 | func Test_NamesFromEmails(t *testing.T) { 15 | var result []string 16 | 17 | result = NamesFromEmails([]string{}) 18 | assert.Equal(t, []string{}, result) 19 | 20 | result = NamesFromEmails([]string{"chris.cummer@me.com", "chriscummer@me.com"}) 21 | assert.Equal(t, []string{"Chris Cummer", "Chriscummer"}, result) 22 | } 23 | -------------------------------------------------------------------------------- /utils/homedir.go: -------------------------------------------------------------------------------- 1 | // Package homedir helps with detecting and expanding the user's home directory 2 | 3 | // Copied (mostly) verbatim from https://github.com/Atrox/homedir 4 | 5 | package utils 6 | 7 | import ( 8 | "errors" 9 | "os" 10 | "path/filepath" 11 | ) 12 | 13 | // ExpandHomeDir expands the path to include the home directory if the path 14 | // is prefixed with `~`. If it isn't prefixed with `~`, the path is 15 | // returned as-is. 16 | func ExpandHomeDir(path string) (string, error) { 17 | if path == "" { 18 | return path, nil 19 | } 20 | 21 | if path[0] != '~' { 22 | return path, nil 23 | } 24 | 25 | if len(path) > 1 && path[1] != '/' && path[1] != '\\' { 26 | return "", errors.New("cannot expand user-specific home dir") 27 | } 28 | 29 | dir, err := os.UserHomeDir() 30 | if err != nil { 31 | return "", err 32 | } 33 | 34 | return filepath.Join(dir, path[1:]), nil 35 | } 36 | -------------------------------------------------------------------------------- /utils/homedir_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_ExpandHomeDir(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | path string 13 | expectedStart string 14 | expectedContains string 15 | expectedError error 16 | }{ 17 | { 18 | name: "with empty path", 19 | path: "", 20 | expectedStart: "", 21 | expectedContains: "", 22 | expectedError: nil, 23 | }, 24 | { 25 | name: "with relative path", 26 | path: "~/test", 27 | expectedStart: "/", 28 | expectedContains: "/test", 29 | expectedError: nil, 30 | }, 31 | { 32 | name: "with absolute path", 33 | path: "/Users/test", 34 | expectedStart: "/", 35 | expectedContains: "/test", 36 | expectedError: nil, 37 | }, 38 | } 39 | 40 | for _, tt := range tests { 41 | t.Run(tt.name, func(t *testing.T) { 42 | actual, err := ExpandHomeDir(tt.path) 43 | 44 | if len(tt.path) > 0 { 45 | assert.Equal(t, tt.expectedStart, string(actual[0])) 46 | } 47 | 48 | assert.Contains(t, actual, tt.expectedContains) 49 | assert.Equal(t, tt.expectedError, err) 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /utils/init.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // OpenFileUtil defines the system utility to use to open files 4 | var OpenFileUtil = "open" 5 | var OpenUrlUtil = []string{} 6 | 7 | // Init initializes global settings in the wtf package 8 | func Init(openFileUtil string, openUrlUtil []string) { 9 | OpenFileUtil = openFileUtil 10 | OpenUrlUtil = openUrlUtil 11 | } 12 | -------------------------------------------------------------------------------- /utils/init_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_Init(t *testing.T) { 10 | Init("cats", []string{"dogs"}) 11 | 12 | assert.Equal(t, OpenFileUtil, "cats") 13 | assert.Equal(t, OpenUrlUtil, []string{"dogs"}) 14 | } 15 | -------------------------------------------------------------------------------- /utils/reflective.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // StringValueForProperty returns a string value for the given property 9 | // If the property doesn't exist, it returns an error 10 | func StringValueForProperty(ref interface{}, propName string) (string, error) { 11 | v := reflect.ValueOf(ref) 12 | refVal := reflect.Indirect(v).FieldByName(propName) 13 | 14 | if !refVal.IsValid() { 15 | return "", fmt.Errorf("invalid property name: %s", propName) 16 | } 17 | 18 | strVal := fmt.Sprintf("%v", refVal) 19 | 20 | return strVal, nil 21 | } 22 | -------------------------------------------------------------------------------- /utils/sums.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // SumInts takes a slice of ints and returns the sum of them 4 | func SumInts(vals []int) int { 5 | sum := 0 6 | 7 | for _, a := range vals { 8 | sum += a 9 | } 10 | 11 | return sum 12 | } 13 | -------------------------------------------------------------------------------- /utils/sums_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_SumInts(t *testing.T) { 10 | expected := 6 11 | result := SumInts([]int{1, 3, 2}) 12 | 13 | assert.Equal(t, expected, result) 14 | 15 | expected = 46 16 | result = SumInts([]int{4, 6, 7, 23, 6}) 17 | 18 | assert.Equal(t, expected, result) 19 | 20 | expected = 4 21 | result = SumInts([]int{4}) 22 | 23 | assert.Equal(t, expected, result) 24 | } 25 | -------------------------------------------------------------------------------- /wtf/datetime.go: -------------------------------------------------------------------------------- 1 | package wtf 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | const ( 9 | // DateFormat defines the format we expect to receive dates from BambooHR in 10 | DateFormat = "2006-01-02" 11 | 12 | // TimeFormat defines the format we expect to receive times from BambooHR in 13 | TimeFormat = "15:04" 14 | ) 15 | 16 | // IsToday returns TRUE if the date is today, FALSE if the date is not today 17 | func IsToday(date time.Time) bool { 18 | now := time.Now().Local() 19 | 20 | return (date.Year() == now.Year()) && 21 | (date.Month() == now.Month()) && 22 | (date.Day() == now.Day()) 23 | } 24 | 25 | // PrettyDate takes a programmer-style date string and converts it 26 | // in a friendlier-to-read format 27 | func PrettyDate(dateStr string) string { 28 | newTime, err := time.Parse(DateFormat, dateStr) 29 | if err != nil { 30 | return dateStr 31 | } 32 | 33 | return fmt.Sprint(newTime.Format("Jan 2, 2006")) 34 | } 35 | 36 | // UnixTime takes a Unix epoch time (in seconds) and returns a 37 | // time.Time instance 38 | func UnixTime(unix int64) time.Time { 39 | return time.Unix(unix, 0) 40 | } 41 | -------------------------------------------------------------------------------- /wtf/enablable.go: -------------------------------------------------------------------------------- 1 | package wtf 2 | 3 | // Enablable is the interface that enforces enable/disable capabilities on a module 4 | type Enablable interface { 5 | Disable() 6 | Disabled() bool 7 | Enabled() bool 8 | } 9 | -------------------------------------------------------------------------------- /wtf/numbers.go: -------------------------------------------------------------------------------- 1 | package wtf 2 | 3 | import "math" 4 | 5 | // Round rounds a float to an integer 6 | func Round(num float64) int { 7 | return int(num + math.Copysign(0.5, num)) 8 | } 9 | 10 | // TruncateFloat64 truncates the decimal places of a float64 to the specified precision 11 | func TruncateFloat64(num float64, precision int) float64 { 12 | output := math.Pow(10, float64(precision)) 13 | return float64(Round(num*output)) / output 14 | } 15 | -------------------------------------------------------------------------------- /wtf/schedulable.go: -------------------------------------------------------------------------------- 1 | package wtf 2 | 3 | import "time" 4 | 5 | // Schedulable is the interface that enforces scheduling capabilities on a module 6 | type Schedulable interface { 7 | Refresh() 8 | Refreshing() bool 9 | RefreshInterval() time.Duration 10 | } 11 | -------------------------------------------------------------------------------- /wtf/stoppable.go: -------------------------------------------------------------------------------- 1 | package wtf 2 | 3 | // Stoppable is the interface that enforces a stoppable state 4 | type Stoppable interface { 5 | Stop() 6 | } 7 | -------------------------------------------------------------------------------- /wtf/terminal.go: -------------------------------------------------------------------------------- 1 | package wtf 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/logrusorgru/aurora/v4" 8 | "github.com/olebedev/config" 9 | ) 10 | 11 | // SetTerminal sets the TERM environment variable, defaulting to whatever the OS 12 | // has as the current value if none is specified. 13 | // See https://www.gnu.org/software/gettext/manual/html_node/The-TERM-variable.html for 14 | // more details. 15 | func SetTerminal(config *config.Config) { 16 | term := config.UString("wtf.term", os.Getenv("TERM")) 17 | err := os.Setenv("TERM", term) 18 | if err != nil { 19 | fmt.Printf("\n%s Failed to set $TERM to %s.\n", aurora.Red("ERROR"), aurora.Yellow(term)) 20 | os.Exit(1) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /wtf/wtfable.go: -------------------------------------------------------------------------------- 1 | package wtf 2 | 3 | import ( 4 | "github.com/wtfutil/wtf/cfg" 5 | 6 | "github.com/rivo/tview" 7 | ) 8 | 9 | // Wtfable is the interface that enforces WTF system capabilities on a module 10 | type Wtfable interface { 11 | Enablable 12 | Schedulable 13 | Stoppable 14 | 15 | BorderColor() string 16 | ConfigText() string 17 | FocusChar() string 18 | Focusable() bool 19 | HelpText() string 20 | Name() string 21 | QuitChan() chan bool 22 | SetFocusChar(string) 23 | TextView() *tview.TextView 24 | 25 | CommonSettings() *cfg.Common 26 | } 27 | --------------------------------------------------------------------------------