diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..79f8224be --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ + + +Title: pkg/github: + +What +- One-sentence summary of the change. + +Why +- Short rationale and intended effect on MCP tools or docs. + +Dev steps performed +- `script/lint` ✅ +- `script/test` ✅ +- `UPDATE_TOOLSNAPS=true go test ./...` (if applicable) ✅ +- `script/generate-docs` (if applicable) ✅ + +Files to review +- `pkg/github/` +- `pkg/github/__toolsnaps__/*.snap` (if changed) +- README.md / docs/ changes (if changed) + +Checklist (required for toolsnaps/docs changes) +- [ ] I ran `script/lint` and fixed formatting/lint issues +- [ ] I ran `script/test` and all tests pass +- [ ] I updated tool snapshots and committed `.snap` files (when schema changed) +- [ ] I ran `script/generate-docs` and included README diffs (if applicable) +- [ ] CI passes: docs-check, lint, license-check + - [ ] CI passes: docs-check, lint, license-check, link-check + +Notes for reviewers +- Brief notes on anything reviewers should watch for (e.g., schema changes, backward-compat concerns). diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f1b4cf9cb..4e94b627b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -45,248 +45,90 @@ If you change any MCP tool definitions or schemas: ```bash # Download dependencies (rarely needed - usually cached) go mod download +## Quick agent guide — GitHub MCP Server -# Build the server binary -go build -v ./cmd/github-mcp-server - -# Run the server -./github-mcp-server stdio - -# Run specific package tests -go test ./pkg/github -v - -# Run specific test -go test ./pkg/github -run TestGetMe -``` - -## Project Structure - -### Directory Layout - -``` -. -├── cmd/ -│ ├── github-mcp-server/ # Main MCP server entry point (PRIMARY FOCUS) -│ └── mcpcurl/ # MCP testing utility (secondary - don't break it) -├── pkg/ # Public API packages -│ ├── github/ # GitHub API MCP tools implementation -│ │ └── __toolsnaps__/ # Tool schema snapshots (*.snap files) -│ ├── toolsets/ # Toolset configuration & management -│ ├── errors/ # Error handling utilities -│ ├── sanitize/ # HTML/content sanitization -│ ├── log/ # Logging utilities -│ ├── raw/ # Raw data handling -│ ├── buffer/ # Buffer utilities -│ └── translations/ # i18n translation support -├── internal/ # Internal implementation packages -│ ├── ghmcp/ # GitHub MCP server core logic -│ ├── githubv4mock/ # GraphQL API mocking for tests -│ ├── toolsnaps/ # Toolsnap validation system -│ └── profiler/ # Performance profiling -├── e2e/ # End-to-end tests (require GitHub PAT) -├── script/ # Build and maintenance scripts -├── docs/ # Documentation -├── .github/workflows/ # CI/CD workflows -└── [config files] # See below -``` - -### Key Configuration Files - -- **go.mod / go.sum:** Go module dependencies (Go 1.24.0+) -- **.golangci.yml:** Linter configuration (v2 format, ~15 linters enabled) -- **Dockerfile:** Multi-stage build (golang:1.25.3-alpine → distroless) -- **server.json:** MCP server metadata for registry -- **.goreleaser.yaml:** Release automation config -- **.gitignore:** Excludes bin/, dist/, vendor/, *.DS_Store, github-mcp-server binary - -### Important Scripts (script/ directory) - -- **script/lint** - Runs `gofmt` + `golangci-lint`. **MUST RUN** before committing -- **script/test** - Runs `go test -race ./...` (full test suite) -- **script/generate-docs** - Updates README.md tool documentation. Run after tool changes -- **script/licenses** - Updates third-party license files when dependencies change -- **script/licenses-check** - Validates license compliance (runs in CI) -- **script/get-me** - Quick test script for get_me tool -- **script/get-discussions** - Quick test for discussions -- **script/tag-release** - **NEVER USE THIS** - releases are managed separately - -## GitHub Workflows (CI/CD) - -All workflows run on push/PR unless noted. Located in `.github/workflows/`: - -1. **go.yml** - Build and test on ubuntu/windows/macos. Runs `script/test` and builds binary -2. **lint.yml** - Runs golangci-lint-action v2.5 (GitHub Action) with actions/setup-go stable -3. **docs-check.yml** - Verifies README.md is up-to-date by running generate-docs and checking git diff -4. **code-scanning.yml** - CodeQL security analysis for Go and GitHub Actions -5. **license-check.yml** - Runs `script/licenses-check` to validate compliance -6. **docker-publish.yml** - Publishes container image to ghcr.io -7. **goreleaser.yml** - Creates releases (main branch only) -8. **registry-releaser.yml** - Updates MCP registry - -**All of these must pass for PR merge.** If docs-check fails, run `script/generate-docs` and commit changes. - -## Testing Guidelines - -### Unit Tests - -- Use `testify` for assertions (`require` for critical checks, `assert` for non-blocking) -- Tests are in `*_test.go` files alongside implementation (internal tests, not `_test` package) -- Mock GitHub API with `go-github-mock` (REST) or `githubv4mock` (GraphQL) -- Test structure for tools: - 1. Test tool snapshot - 2. Verify critical schema properties (e.g., ReadOnly annotation) - 3. Table-driven behavioral tests +This repository implements a local Model Context Protocol (MCP) server for GitHub. Focus areas for an AI coding agent: -### Toolsnaps (Tool Schema Snapshots) +- Primary server: [cmd/github-mcp-server](cmd/github-mcp-server) — build with `go build ./cmd/github-mcp-server` and run `./github-mcp-server stdio`. +- Tools implementation: `pkg/github/` (tool defs, prompts, and tool registration). Tool schema snapshots live in `pkg/github/__toolsnaps__/`. +- Core runtime: `internal/ghmcp/` — server wiring, middleware (see user-agent middleware in `internal/ghmcp/server.go`). -- Every MCP tool has a JSON schema snapshot in `pkg/github/__toolsnaps__/*.snap` -- Tests fail if current schema differs from snapshot (shows diff) -- To update after intentional changes: `UPDATE_TOOLSNAPS=true go test ./...` -- **MUST commit updated .snap files** - they document API changes -- Missing snapshots cause CI failure - -### End-to-End Tests - -- Located in `e2e/` directory with `e2e_test.go` -- **Require GitHub PAT token** - you usually cannot run these yourself -- Run with: `GITHUB_MCP_SERVER_E2E_TOKEN= go test -v --tags e2e ./e2e` -- Tests interact with live GitHub API via Docker container -- **Keep e2e tests updated when changing MCP tools** -- **Use only the e2e test style** when modifying tests in this directory -- For debugging: `GITHUB_MCP_SERVER_E2E_DEBUG=true` runs in-process (no Docker) - -## Code Style & Linting - -### Go Code Requirements - -- **gofmt with simplify flag (-s)** - Automatically run by `script/lint` -- **golangci-lint** with these linters enabled: - - bodyclose, gocritic, gosec, makezero, misspell, nakedret, revive - - errcheck, staticcheck, govet, ineffassign, unused -- Exclusions for: third_party/, builtin/, examples/, generated code - -### Go Naming Conventions - -- **Acronyms in identifiers:** Use `ID` not `Id`, `API` not `Api`, `URL` not `Url`, `HTTP` not `Http` -- Examples: `userID`, `getAPI`, `parseURL`, `HTTPClient` -- This applies to variable names, function names, struct fields, etc. - -### Code Patterns - -- **Keep changes minimal and focused** on the specific issue being addressed -- **Prefer clarity over cleverness** - code must be understandable by a wide audience -- **Atomic commits** - each commit should be a complete, logical change -- **Maintain or improve structure** - never degrade code organization -- Use table-driven tests for behavioral testing -- Comment sparingly - code should be self-documenting -- Follow standard Go conventions (Effective Go, Go proverbs) -- **Test changes thoroughly** before committing -- Export functions (capitalize) if they could be used by other repos as a library - -## Common Development Workflows - -### Adding a New MCP Tool - -1. Add tool implementation in `pkg/github/` (e.g., `foo_tools.go`) -2. Register tool in appropriate toolset in `pkg/github/` or `pkg/toolsets/` -3. Write unit tests following the tool test pattern -4. Run `UPDATE_TOOLSNAPS=true go test ./...` to create snapshot -5. Run `script/generate-docs` to update README -6. Run `script/lint` and `script/test` before committing -7. If e2e tests are relevant, update `e2e/e2e_test.go` using existing test style -8. Commit code + snapshots + README changes together - -### Fixing a Bug - -1. Write a failing test that reproduces the bug -2. Fix the bug with minimal changes -3. Verify test passes and existing tests still pass -4. Run `script/lint` and `script/test` -5. If tool schema changed, update toolsnaps (see above) - -### Updating Dependencies - -1. Update `go.mod` (e.g., `go get -u ./...` or manually) -2. Run `go mod tidy` -3. Run `script/licenses` to update license files -4. Run `script/test` to verify nothing broke -5. Commit go.mod, go.sum, and third-party-licenses* files - -## Common Errors & Solutions - -### "Documentation is out of date" in CI - -**Fix:** Run `script/generate-docs` and commit README.md changes - -### Toolsnap mismatch failures - -**Fix:** Run `UPDATE_TOOLSNAPS=true go test ./...` and commit updated .snap files - -### Lint failures - -**Fix:** Run `script/lint` locally - it will auto-format and show issues. Fix manually reported issues. - -### License check failures - -**Fix:** Run `script/licenses` to regenerate license files after dependency changes - -### Test failures after changing a tool - -**Likely causes:** -1. Forgot to update toolsnaps - run with `UPDATE_TOOLSNAPS=true` -2. Changed behavior broke existing tests - verify intent and fix tests -3. Schema change not reflected in test - update test expectations - -## Environment Variables - -- **GITHUB_PERSONAL_ACCESS_TOKEN** - Required for server operation and e2e tests -- **GITHUB_HOST** - For GitHub Enterprise Server (prefix with `https://`) -- **GITHUB_TOOLSETS** - Comma-separated toolset list (overrides --toolsets flag) -- **GITHUB_READ_ONLY** - Set to "1" for read-only mode -- **GITHUB_DYNAMIC_TOOLSETS** - Set to "1" for dynamic toolset discovery -- **UPDATE_TOOLSNAPS** - Set to "true" when running tests to update snapshots -- **GITHUB_MCP_SERVER_E2E_TOKEN** - Token for e2e tests -- **GITHUB_MCP_SERVER_E2E_DEBUG** - Set to "true" for in-process e2e debugging - -## Key Files Reference - -### Root Directory Files -``` -.dockerignore - Docker build exclusions -.gitignore - Git exclusions (includes bin/, dist/, vendor/, binaries) -.golangci.yml - Linter configuration -.goreleaser.yaml - Release automation -CODE_OF_CONDUCT.md - Community guidelines -CONTRIBUTING.md - Contribution guide (fork, clone, test, lint workflow) -Dockerfile - Multi-stage Go build -LICENSE - MIT license -README.md - Main documentation (auto-generated sections) -SECURITY.md - Security policy -SUPPORT.md - Support resources -gemini-extension.json - Gemini CLI configuration -go.mod / go.sum - Go dependencies -server.json - MCP server registry metadata -``` - -### Main Entry Point - -`cmd/github-mcp-server/main.go` - Uses cobra for CLI, viper for config, supports: -- `stdio` command (default) - MCP stdio transport -- `generate-docs` command - Documentation generation -- Flags: --toolsets, --read-only, --dynamic-toolsets, --gh-host, --log-file - -## Important Reminders - -1. **PRIMARY FOCUS:** The local stdio MCP server (github-mcp-server) - this is what you should work on and test with -2. **REMOTE SERVER:** Ignore remote server instructions when making code changes (unless specifically asked). This repo is used as a library by the remote server, so keep functions exported (capitalized) if they could be called by other repos, even if not needed internally. -3. **ALWAYS** trust these instructions first - only search if information is incomplete or incorrect -4. **NEVER** use `script/tag-release` or push tags -5. **NEVER** skip `script/lint` before committing Go code changes -6. **ALWAYS** update toolsnaps when changing MCP tool schemas -7. **ALWAYS** run `script/generate-docs` after modifying tools -8. For specific test files, use `go test ./path -run TestName` not full suite -9. E2E tests require PAT token - you likely cannot run them -10. Toolsnaps are API documentation - treat changes seriously -11. Build/test/lint are very fast (~1s each) - run frequently -12. CI failures for docs-check or license-check have simple fixes (run the script) -13. mcpcurl is secondary - don't break it, but it's not the priority \ No newline at end of file +Required quick commands (run before committing): + +- `script/lint` — formats and runs `golangci-lint` (always run). +- `script/test` — runs `go test -race ./...` (use `-run TestName` for focused tests). +- If you changed tool schemas: `UPDATE_TOOLSNAPS=true go test ./...` and commit files from `pkg/github/__toolsnaps__/`. +- If you changed tools/docs: `script/generate-docs` to refresh README tool docs. + +Important patterns and conventions (project-specific): + +- Tool snapshots: every MCP tool has a JSON `.snap` in `pkg/github/__toolsnaps__`. Tests fail on snapshot drift — update intentionally with `UPDATE_TOOLSNAPS=true`. +- Tool registration & prompts: search `pkg/github/*_tools.go`, `prompts.go`, and `workflow_prompts.go` for examples of tool prompts and usage (e.g., `AssignCodingAgentPrompt`). +- Export surface: this repo is consumed as a library by other servers — prefer exporting functions (capitalize) when they might be reused. +- Naming: acronyms use ALL CAPS in identifiers (`ID`, `HTTP`, `API`, `URL`). +- Tests: table-driven tests are common. Mocks used: `go-github-mock` (REST) and `internal/githubv4mock` (GraphQL). + +Where to look for quick examples: + +- Tool snapshot example: [pkg/github/__toolsnaps__/assign_copilot_to_issue.snap](pkg/github/__toolsnaps__/assign_copilot_to_issue.snap) +- Tool prompts and registration: [pkg/github/issues.go](pkg/github/issues.go) and [pkg/github/prompts.go](pkg/github/prompts.go) +- Main server entry: [cmd/github-mcp-server/main.go](cmd/github-mcp-server/main.go) +- Core wiring and middleware: [internal/ghmcp/server.go](internal/ghmcp/server.go) + +CI and workflows: + +- CI runs `script/test` and `script/lint`. `docs-check.yml` ensures `script/generate-docs` is run when tools change. +- Don't use `script/tag-release`; releases are managed separately. + +Developer tips for agents: + +- When adding or modifying a tool: implement in `pkg/github/`, add tests, run `UPDATE_TOOLSNAPS=true go test ./...`, run `script/generate-docs`, then `script/lint` and `script/test`. +- Use focused tests: `go test ./pkg/github -run TestName` or `go test ./pkg/github -run TestGetMe`. +- E2E tests require a GitHub PAT and are in `e2e/` (run with `GITHUB_MCP_SERVER_E2E_TOKEN= go test -v --tags e2e ./e2e`). + +If anything in these instructions is unclear or missing, tell me which areas you'd like expanded (examples, common code locations, or sample PR checklist). +Below is an optional appendix with concrete commands, CI notes, a PR checklist, and a short commit/PR template for changes that affect toolsnaps or generated docs. + +--- + +**Appendix — Scripts, CI notes, PR checklist** + +- Quick scripts (run before commit or when CI fails): + - `script/lint` — formats and runs `golangci-lint` (auto-fixes formatting). + - `script/test` — full unit test suite (`go test -race ./...`). + - `UPDATE_TOOLSNAPS=true go test ./...` — regenerate tool schema snapshots (`pkg/github/__toolsnaps__`). + - `script/generate-docs` — refreshes README sections derived from tools. + +- Common commands: + - Build server: `go build ./cmd/github-mcp-server` + - Run server: `./github-mcp-server stdio` + - Run focused tests: `go test ./pkg/github -run TestName` + - Run e2e (requires PAT): `GITHUB_MCP_SERVER_E2E_TOKEN= go test -v --tags e2e ./e2e` + +- CI notes (when a CI job fails): + - docs-check.yml failing → run `script/generate-docs` and commit README changes. + - lint.yml failing → run `script/lint`, fix reported issues, re-run tests. + - license-check.yml failing → run `script/licenses` then commit updated third-party license files. + +- PR checklist for changes affecting tools or docs (include in PR description): + 1. Run `script/lint` and `script/test` locally. + 2. If tool schema changed: run `UPDATE_TOOLSNAPS=true go test ./...` and commit updated `.snap` files from `pkg/github/__toolsnaps__/`. + 3. If tools or prompts changed: run `script/generate-docs` and include README diffs. + 4. Ensure table-driven tests cover new behavior; add mocks in `internal/githubv4mock` if needed. + 5. Verify CI passes `docs-check`, `lint`, and `license-check` before requesting review. + +- Minimal PR commit/description template for toolsnaps/docs changes: + + Title: `pkg/github: ` + + Body: + - What: one-sentence summary of the change. + - Why: brief rationale and intended effect on MCP tools. + - Dev steps performed: + - `script/lint` ✅ + - `script/test` ✅ + - `UPDATE_TOOLSNAPS=true go test ./...` (if applicable) ✅ + - `script/generate-docs` (if applicable) ✅ + - Files to review: `pkg/github/`, `pkg/github/__toolsnaps__/*.snap`, README changes. + +If you want, I can also add a small `.github/PULL_REQUEST_TEMPLATE.md` using this template. Say the word and I will create it. diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml new file mode 100644 index 000000000..6d632c575 --- /dev/null +++ b/.github/workflows/check-links.yml @@ -0,0 +1,66 @@ +name: Check external links + +on: + workflow_dispatch: {} + pull_request: + push: + branches: + - main + - 'add/*' + +jobs: + linkcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Gather external URLs + run: | + # find http(s) links in repo files and dedupe + grep -RhoE "https?://[^\")'\s]+" . | sort -u > /tmp/links.txt || true + echo "Found $(wc -l < /tmp/links.txt || echo 0) links" + # remove obvious non-http schemes and local addresses + grep -vE "^https?://(localhost|127\.|\[::1\])" /tmp/links.txt | grep -vE "^(file:|mailto:|data:)" | sort -u > /tmp/links_filtered.txt || true + echo "Filtered to $(wc -l < /tmp/links_filtered.txt || echo 0) external links" + cat /tmp/links_filtered.txt + - name: Check links (with retries) + env: + # Optional allowlist regex (set in repo secrets or workflow_dispatch inputs). By default allow common CDNs and github domains. + ALLOWLIST: "github.com|jsdelivr.net|cdn.jsdelivr.net|raw.githubusercontent.com|fonts.googleapis.com|cdnjs.cloudflare.com|app.github.dev" + run: | + set -euo pipefail + INPUT_FILE=/tmp/links_filtered.txt + # If ALLOWLIST is set, filter links to only matching domains (helps reduce noise) + if [ -n "${ALLOWLIST:-}" ]; then + echo "Applying allowlist: $ALLOWLIST" + grep -E "$ALLOWLIST" "$INPUT_FILE" > /tmp/links_allowed.txt || true + mv /tmp/links_allowed.txt $INPUT_FILE + fi + + failed=0 + while read -r url; do + [ -z "$url" ] && continue + echo "Checking: $url" + ok=0 + for i in 1 2 3; do + status=$(curl -sS -o /dev/null -w "%{http_code}" -I -L --max-time 15 "$url" || echo "000") + echo " attempt $i -> HTTP $status" + case "$status" in + 2*|3*) ok=1; break;; + 429) sleep $((i*2)); continue;; + 5*) sleep $((i*2)); continue;; + 000) sleep 2; continue;; + *) break;; + esac + done + if [ "$ok" -eq 1 ]; then + echo "OK: $url" + else + echo "FAIL: $url" >&2 + failed=1 + fi + done < "$INPUT_FILE" + if [ "$failed" -ne 0 ]; then + echo "One or more external links failed checks" >&2 + exit 2 + fi + shell: bash diff --git a/docs/LINKCHECK.md b/docs/LINKCHECK.md new file mode 100644 index 000000000..a26ab9cc9 --- /dev/null +++ b/docs/LINKCHECK.md @@ -0,0 +1,32 @@ +# Запуск проверки ссылок (check-links) + +Короткая инструкция, как вручную запустить workflow проверки ссылок `check-links.yml`. + +## Через веб-интерфейс GitHub +1. Откройте репозиторий (форк или upstream) в GitHub. +2. Перейдите в раздел **Actions** → найдите `check-links.yml`. +3. Нажмите **Run workflow** (обычно справа) и в выпадающем списке выберите ветку `add/downloaded-homepage`. +4. Нажмите **Run workflow** — статус и логи выполнения появятся в разделе Actions. + +> Примечание: если вы запустите workflow в upstream (github/github-mcp-server), убедитесь, что у вас есть права на запуск workflow в этом репозитории. + +## Через `gh` CLI +Если вы предпочитаете CLI и у вас есть локальная настройка `gh` с нужными правами, выполните: + +```bash +gh workflow run check-links.yml --ref add/downloaded-homepage --repo / +``` + +- Замените `/` на `KARTYOM3248/github-mcp-server` (ваш форк) или на `github/github-mcp-server` (upstream), в зависимости от того, где хотите запустить. +- Для программы потребуется токен с правом `workflow` (и `repo` если репозиторий приватный). Убедитесь, что `gh auth status` показывает корректную учётную запись. + +## Что делать при ошибке 403 при dispatch +- Ошибка `403 Resource not accessible by integration` означает, что текущий токен/интеграция не имеет права диспатчить workflow. Возможные решения: + - Использовать личный токен (PAT) с скоупами `repo` + `workflow` и авторизовать `gh` или вызвать GitHub API с этим PAT. + - Запустить workflow вручную через веб-интерфейс (Actions → Run workflow). + +## Контекст +- Workflow: `.github/workflows/check-links.yml` +- Ветка по умолчанию для инструкций: `add/downloaded-homepage` + +Если хотите, могу: (а) закоммитить и запушить этот файл в ветку `add/downloaded-homepage`, или (б) сразу выполнить `gh workflow run` при наличии PAT. Напишите, что предпочитаете. \ No newline at end of file diff --git a/downloaded_homepage.html b/downloaded_homepage.html new file mode 100644 index 000000000..bee74f7ff --- /dev/null +++ b/downloaded_homepage.html @@ -0,0 +1,223 @@ + + + + + + Финансовый Дашборд + + + + + + + 🌕 +
+
+

💰 Финансовый Дашборд

+ +
+ +
+ Главное фото
+ +
+ +
+
+
💳
+
+

Общий баланс

+

₽ 125,450

+ +12.5% за месяц +
+
+ +
+
📈
+
+

Доходы

+

₽ 85,000

+ +8.2% за месяц +
+
+ +
+
📉
+
+

Расходы

+

₽ 42,300

+ -5.1% за месяц +
+
+ +
+
🏦
+
+

Накопления

+

₽ 82,750

+ +15.3% за месяц +
+
+
+ + +
+
+

Динамика финансов

+ +
+ +
+

Распределение расходов

+ +
+
+ + +
+
+

Последние транзакции

+ +
+
+ +
+
+ + +
+

Бюджет на месяц

+
+
+
+ Продукты + ₽ 15,000 / ₽ 20,000 +
+
+
+
+
+
+
+ Транспорт + ₽ 3,500 / ₽ 5,000 +
+
+
+
+
+
+
+ Развлечения + ₽ 8,200 / ₽ 10,000 +
+
+
+
+
+
+
+ Здоровье + ₽ 5,600 / ₽ 8,000 +
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + diff --git a/index.html b/index.html new file mode 100644 index 000000000..84bb3e89d --- /dev/null +++ b/index.html @@ -0,0 +1,221 @@ + + + + + + Финансовый Дашборд + + + + + 🌕 +
+
+

💰 Финансовый Дашборд

+ +
+ +
+ Главное фото +
+ +
+ +
+
+
💳
+
+

Общий баланс

+

₽ 125,450

+ +12.5% за месяц +
+
+ +
+
📈
+
+

Доходы

+

₽ 85,000

+ +8.2% за месяц +
+
+ +
+
📉
+
+

Расходы

+

₽ 42,300

+ -5.1% за месяц +
+
+ +
+
🏦
+
+

Накопления

+

₽ 82,750

+ +15.3% за месяц +
+
+
+ + +
+
+

Динамика финансов

+ +
+ +
+

Распределение расходов

+ +
+
+ + +
+
+

Последние транзакции

+ +
+
+ +
+
+ + +
+

Бюджет на месяц

+
+
+
+ Продукты + ₽ 15,000 / ₽ 20,000 +
+
+
+
+
+
+
+ Транспорт + ₽ 3,500 / ₽ 5,000 +
+
+
+
+
+
+
+ Развлечения + ₽ 8,200 / ₽ 10,000 +
+
+
+
+
+
+
+ Здоровье + ₽ 5,600 / ₽ 8,000 +
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + diff --git a/script.js b/script.js new file mode 100644 index 000000000..57b4098ef --- /dev/null +++ b/script.js @@ -0,0 +1,182 @@ +// Простая логика для дашборда: графики, транзакции и модал +const formatter = new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB', maximumFractionDigits: 0 }); +let transactions = [ + {id:1,type:'income',category:'salary',amount:85000,description:'Зарплата'}, + {id:2,type:'expense',category:'food',amount:8200,description:'Покупки в супермаркете'}, + {id:3,type:'expense',category:'transport',amount:1200,description:'Такси'}, +]; + +function $(id){return document.getElementById(id)} + +function updateBalances(){ + const totalIncome = transactions.filter(t=>t.type==='income').reduce((s,t)=>s+t.amount,0); + const totalExpense = transactions.filter(t=>t.type==='expense').reduce((s,t)=>s+t.amount,0); + const totalSavings = Math.max(0, totalIncome - totalExpense); + const totalBalance = totalIncome - totalExpense; + $('totalIncome').textContent = formatter.format(totalIncome); + $('totalExpense').textContent = formatter.format(totalExpense); + $('totalSavings').textContent = formatter.format(totalSavings); + $('totalBalance').textContent = formatter.format(totalBalance); +} + +// Charts +let financeChart = null; +let expenseChart = null; +function renderCharts(){ + const ctx = $('financeChart').getContext('2d'); + const months = ['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек']; + const incomeData = Array.from({length:12},(_,i)=> Math.round(50000 + Math.random()*50000)); + const expenseData = Array.from({length:12},(_,i)=> Math.round(15000 + Math.random()*30000)); + if(financeChart) financeChart.destroy(); + financeChart = new Chart(ctx,{ + type:'line', + data:{labels:months,datasets:[{label:'Доходы',data:incomeData,borderColor:'#16a34a',backgroundColor:'rgba(16,163,127,0.08)',tension:0.3},{label:'Расходы',data:expenseData,borderColor:'#ef4444',backgroundColor:'rgba(239,68,68,0.06)',tension:0.3}]}, + options:{responsive:true,plugins:{legend:{position:'bottom'}}} + }); + + const ctx2 = $('expenseChart').getContext('2d'); + const categories = ['Продукты','Транспорт','Развлечения','Здоровье','Другое']; + const catValues = [15000,3500,8200,5600,4000]; + if(expenseChart) expenseChart.destroy(); + expenseChart = new Chart(ctx2,{type:'doughnut',data:{labels:categories,datasets:[{data:catValues,backgroundColor:['#60a5fa','#34d399','#f97316','#f472b6','#a78bfa']}]},options:{responsive:true,plugins:{legend:{position:'bottom'}}}}); +} + +// Transactions +function renderTransactions(){ + const list = $('transactionsList'); + list.innerHTML = ''; + transactions.slice().reverse().forEach(t=>{ + const el = document.createElement('div'); + el.className = 'transaction'; + + const meta = document.createElement('div'); + meta.className = 'meta'; + + const category = document.createElement('div'); + category.className = 'category'; + category.textContent = (t.type==='income'? '📈 ' : '📉 ') + t.category; + + const desc = document.createElement('div'); + desc.className = 'desc'; + desc.textContent = t.description || ''; + + meta.appendChild(category); + meta.appendChild(desc); + + const amountEl = document.createElement('div'); + amountEl.className = 'amount'; + amountEl.textContent = (t.type==='income'? '+ ' : '- ') + formatter.format(t.amount); + + el.appendChild(meta); + el.appendChild(amountEl); + list.appendChild(el); + }); +} + +function openModal(){ + $('transactionModal').style.display = 'flex'; +} +function closeModal(){ + $('transactionModal').style.display = 'none'; +} + +function addTransaction(){ + openModal(); +} + +// Логика для модального окна входа +function openLoginModal() { + document.getElementById('loginModal').style.display = 'flex'; +} + +function closeLoginModal() { + document.getElementById('loginModal').style.display = 'none'; +} + +// Логика для модального окна регистрации +function openRegisterModal() { + document.getElementById('registerModal').style.display = 'flex'; +} + +function closeRegisterModal() { + document.getElementById('registerModal').style.display = 'none'; +} + +// Form handling +document.addEventListener('DOMContentLoaded',()=>{ + renderCharts(); + renderTransactions(); + updateBalances(); + const form = $('transactionForm'); + form.addEventListener('submit',(e)=>{ + e.preventDefault(); + const type = $('transactionType').value; + const category = $('transactionCategory').value; + const amount = Math.abs(Number($('transactionAmount').value)||0); + const description = $('transactionDescription').value; + if(amount<=0) return alert('Введите сумму больше 0'); + const id = Date.now(); + transactions.push({id,type,category,amount,description}); + renderTransactions(); + updateBalances(); + renderCharts(); + closeModal(); + form.reset(); + }); + + // close modal when clicking outside content + $('transactionModal').addEventListener('click',(e)=>{ if(e.target.id==='transactionModal') closeModal(); }); + + // Attach event listeners to buttons (avoid inline handlers) + const registerButton = document.getElementById('btn-register'); + const loginButton = document.getElementById('btn-login'); + const addTxButton = document.getElementById('btn-add-transaction'); + const txClose = document.getElementById('tx-close'); + const loginClose = document.getElementById('login-close'); + const registerClose = document.getElementById('register-close'); + + if (registerButton) registerButton.addEventListener('click', openRegisterModal); + if (loginButton) loginButton.addEventListener('click', openLoginModal); + if (addTxButton) addTxButton.addEventListener('click', addTransaction); + if (txClose) txClose.addEventListener('click', closeModal); + if (loginClose) loginClose.addEventListener('click', closeLoginModal); + if (registerClose) registerClose.addEventListener('click', closeRegisterModal); + + const loginForm = document.getElementById('loginForm'); + loginForm.addEventListener('submit', (e) => { + e.preventDefault(); + const username = document.getElementById('loginUsername').value; + const password = document.getElementById('loginPassword').value; + // Authentication is disabled in this demo snapshot. + // Do NOT ship hard-coded credentials in production. Masking demo behavior here. + console.warn('Login attempt for user:', username); + alert('В демо-режиме вход отключён.'); + closeLoginModal(); + }); + + const registerForm = document.getElementById('registerForm'); + registerForm.addEventListener('submit', (e) => { + e.preventDefault(); + const username = document.getElementById('registerUsername').value; + const password = document.getElementById('registerPassword').value; + const confirmPassword = document.getElementById('confirmPassword').value; + + if (password !== confirmPassword) { + alert('Пароли не совпадают.'); + return; + } + + alert('Регистрация успешна! Добро пожаловать, ' + username + '!'); + closeRegisterModal(); + registerForm.reset(); + }); + + // Закрытие модального окна при клике вне контента + document.getElementById('loginModal').addEventListener('click', (e) => { + if (e.target.id === 'loginModal') closeLoginModal(); + }); + + document.getElementById('registerModal').addEventListener('click', (e) => { + if (e.target.id === 'registerModal') closeRegisterModal(); + }); +});