Merge pull request #1298 from itflow-org/develop

Develop to Master for Release
This commit is contained in:
Johnny
2026-09-01 13:01:18 -04:00
committed by GitHub
900 changed files with 32168 additions and 56945 deletions

View File

@@ -2,6 +2,144 @@
This file documents all notable changes made to ITFlow.
## [26.09]
### Upgrading to 26.09
> **The database update has to be run from the command line one more time.** The web interface no longer applies database updates, and the new queued-update path needs a schema change that this release itself adds — so on this one upgrade there is nothing in the browser that can finish the job. After this, it goes back to being a single button.
1. **Back everything up.** Maintenance > Backup, or a full VM snapshot.
2. **Update the files from Maintenance > Update as normal.** The old page pulls the new files and reports success, then drops you into 26.09 running against the 26.08 database. Errors at that point are expected and stop as soon as step 3 completes.
3. **Run the database update from the command line.** Run it as the user that owns the ITFlow files — the script tells you which user if you get it wrong:
```bash
sudo -u www-data php /var/www/itflow.example.com/scripts/update_cli.php
```
It applies every pending version from 2.6.8 to 2.7.8 in order and reports each one as it goes. If a step fails it stops there without advancing the recorded version, so you can fix the cause and run it again.
4. **Check it took.** Maintenance > Update should show the database up to date and the Queue Update button available.
From here on the command line is optional. Maintenance > Update hands the work to cron, which updates the files and the database in one pass.
### Breaking Changes and Notes
- Updates are no longer applied by your browser. Maintenance > Update either hands the job to cron, which runs it in the background, or you run it from the shell. As a side effect ITFlow now updates cleanly on hardened hosts that disable PHP's shell functions, which it could not do before.
- Database updates have been removed from the web interface entirely. Cron applies them as part of a queued update, or you run `php scripts/update_cli.php` yourself.
- Running `php scripts/update_cli.php` with no arguments updates the files and then the database in one go, and the file update is forced — any local edits you have made to shipped files are discarded. Use `--update_db` if you only want the database half; it never touches your files.
- If you have added your own CSS or JavaScript under `agent/custom/`, expect it to need updating. The interface has moved to a new major version of the framework it is built on and most class names have changed. Details are in Developer Updates below.
- Deleting a payment now requires Full access to both Sales and Financial, up from Modify. Refunding requires the same. Deleting a payment removes money from the books, so it is treated as a delete rather than an edit — agents who could remove payments before may no longer be able to.
- The five built-in ticket statuses now have fixed SLA clock behaviour and can no longer be configured. New and Open always run; On Hold, Resolved and Closed always pause the resolution clock. Only custom statuses keep the SLA Clock dropdown, and the built-ins are marked "(fixed)" in the list.
- On Hold pauses the resolution clock, not the response clock, and only once the ticket has had its first reply. A ticket parked on hold before anyone replies still breaches its response target — reply first, then hold.
- Breaches already recorded against on-hold tickets under the old behaviour are left as they are. Change a ticket's priority or SLA to re-stamp it if you want it recalculated.
- Holidays and closure days are new and start empty. Until you add them, SLA clocks keep running through your closures exactly as they did before.
- Tickets default to Medium priority when none is given, and ticket replies default to Public.
- Closed tickets can no longer be deleted. A closed ticket is treated as a permanent record.
- Send Email on invoices and quotes now opens a contact picker rather than sending straight away. Quick Send, described below, keeps the old one-click behaviour, but any bookmarked direct links to the send action no longer work.
### New Features & Updates
- The interface has had a full visual overhaul, with dark mode carried properly through the calendar, the editor, confirmation dialogs, tables and form controls.
- Your theme colour and any custom CSS now apply everywhere, including the client portal, the guest pages, login and setup. Those pages previously ignored both and rendered in the default styling.
- Ticket: canned responses. Add them under Admin > Templates > Canned Responses, scoped to a ticket category or to all categories, and pick one from the reply form. It inserts at the cursor, so picking one into a half-written reply adds to it rather than replacing it.
- Ticket: SLA response targets now appear on ticket-created emails, rolled up to business days, with a note to call in on High and Urgent.
- Ticket: the reply card stays out of the way until you click Reply.
- SLA: holidays and closure days. Define the days you are closed and the SLA clock pauses through them, with a one-click importer for US federal holidays.
- Networks: a full IP address section under each subnet — address, hostname and description, with search, sort, bulk delete, CSV import and export. Addresses are checked against the subnet they are being added to and duplicates within a subnet are refused, so the same guard applies whether you type one in or import a thousand. Both IPv4 and IPv6, sorted numerically so .9 comes before .10.
- Designate one client record as your own organization, under Company Details. Clicking your company name at the top of the side navigation now takes you straight to it.
- Invoices and Quotes: Quick Send is a one-click send to the default contacts, the way Send Email used to work — primary and billing for invoices, primary for quotes. It sits at the top of the actions menu with a lightning bolt and asks for confirmation, and never opens the picker.
- Invoices and Quotes: Send Email, below it, opens a modal listing every contact on the client with their email addresses and the same defaults pre-checked, for when the invoice needs to go somewhere other than the usual place. Who it went to is recorded in the document history either way.
- Invoices and Quotes: Mark Sent asks how it was sent — snail mail, an email client, in person and so on — and records the reason in the history.
- Stripe: refund a card payment from the invoice itself. The refund goes through Stripe, is written to the invoice history, and the payment is removed, replacing the old routine of deleting the payment in ITFlow and refunding it by hand in the Stripe dashboard.
- Stripe: adding or editing the Stripe provider now checks the secret key against Stripe and tells you if it is wrong, instead of the key looking fine until the first client tries to pay.
- Stripe: a client paying from a guest invoice link no longer needs a Stripe customer record to exist first — Pay Now sets one up as part of the payment.
- Client account statements. Send one from the invoice list with a date range and an option to leave out paid invoices, view it on the guest invoice page, and download it as a PDF from the client portal.
- Files: documents and files are now told apart, with a filter for all documents or just files. Thumbnail view shows every file, previews work for documents, PDFs and text, and your choice of list or thumbnail view survives navigating between folders.
- Client Portal: the profile page has been rebuilt — department, location, title and phone with inline editing, PIN changes, recent sign-ins and recent activity, with a separate full activity page.
- Client Portal: empty tables now say what is missing instead of showing a bare header row, and the saved payment method wording is clearer about what saving a card actually does.
- Guest: the ticket task approval page has been rebuilt. The request being approved leads the page, the ticket body sits underneath as context, the approve action is a proper button rather than a link in a paragraph, and approving or declining lands on a real confirmation screen. Internal routing jargon that meant nothing to a client has been dropped.
- Guest and client portal pages now have proper footers, and the agent footer stays at the bottom of short pages instead of floating mid-screen.
- Phone numbers show their country code, and the country is kept when you edit the number. The international phone input is now used on every phone field.
- Maintenance > Update: Queue Update hands the update to cron, which runs it in its own process and updates the files and the database in one pass. Check now looks for new commits without needing shell access, listing the pending commits with their dates and descriptions.
- Mail Parser: you are now notified when the parser skips an autogenerated email rather than it disappearing silently.
- Demo data. Twenty fictional clients with two years of history — a mix of managed and break-fix, tickets, invoices, expenses, assets and contracts, including FOSS products — loaded from Maintenance > Starter Content and tagged so it can be cleared out again.
- API: reads accept an optional `client_id` filter on top of the key's own client scoping, so a key that can see everything can ask for one client's records. It can only narrow — a key with no access to that client still gets nothing.
- API: new endpoints to close a ticket and to delete a client. Closing resolves the ticket first if it has not been resolved already, so the SLA figures come out right. Thanks to @BoredManCodes for the client delete endpoint.
- Client pages load noticeably faster, running roughly a third fewer queries than before, and several pages that were slow on large installs have been sped up.
- Lists now say whether nothing matched your filters or there are no records at all, instead of showing an empty table either way.
- Assets and Contacts: an "Add primary" link where a client has no primary location or contact set.
- The client header at the top of client pages collapses, and stays collapsed as you move between pages.
- Page changes fade in rather than flashing, the calendar reserves its height so the page no longer jumps as it loads, and table listings line up consistently across the app.
### Security
- A restricted agent could open an asset, contact or location belonging to a client they have no access to by entering its id in the address bar. Those pages are now gated on the module permission that owns them and checked against the agent's client access rules, like every other record page.
- Client Portal: changing a password or a PIN now requires the current password. Contacts signing in through SSO are exempt — there is no local password to check and the identity provider has already done it.
- A user name containing HTML could inject markup into the page through the ticket task approver list. It no longer can.
- Deleting and refunding payments now require Full access to both Sales and Financial, as described under Breaking Changes.
### Bug Fixes
- Tickets could not be opened for clients marked as a lead.
- Client Portal: raising a ticket sent no new-ticket notification — the notification errored out instead.
- The readable password generator called a function that no longer existed, so generating one did nothing.
- Client pages flashed blank on every load.
- Invoice emails went to archived contacts, and every copy carried the primary contact's name in the greeting rather than the name of the contact receiving it.
- Guest: the confirmation dialog on ticket task approvals and on quote accept and decline rendered as plain text at the foot of the page instead of as a dialog, because the guest pages never loaded its stylesheet.
- Filtering the audit log by date was slow on large installs.
- API: deleting an asset reported how many interfaces it had removed rather than confirming the asset itself was deleted.
- API: updating a client failed outright if the request did not include the lead field.
- API: a request that authenticated correctly but then failed on the query wrote nothing to the app log, leaving nothing to debug from.
### Developer Updates
Front-end framework migration:
- AdminLTE 3.2.0 to 4.9.1 and Bootstrap 4.6.2 to 5.3.8. The layout skeleton is renamed throughout — `content-wrapper`, `main-sidebar` and `main-header` become `app-main`, `app-content`, `app-sidebar` and `app-header`.
- AdminLTE 4 dropped a number of v3 classes ITFlow relies on. `text-bold`, `text-sm`, `btn-default`, `img-circle`, the `.alert .icon` pairing, the sidebar badge positioning, the `small-box` watermark icon and all sixteen theme colours are reproduced in `css/itflow_custom.css` at v3's computed values, driven by a single `--itflow-accent` variable per theme.
- Bootstrap 5 split `.bg-*` from `.text-bg-*`, so every `bg-dark` card and modal header needed its text colour restored explicitly.
- `input-group-append` and `input-group-prepend` wrappers are deleted rather than renamed, selects moved from `form-control` to `form-select`, `data-toggle="buttons"` groups became `.btn-check`, and `custom-control`, `custom-select` and `custom-file` are gone.
- `.input-group > .form-control` at specificity (0,2,0) outranks `.form-control-color`'s `width:3rem` at (0,1,0), which collapsed every colour swatch. Fixed with a matching-specificity `.input-group > .form-control-color` rule rather than a `w-auto` override, which only hands the width to the UA default.
- Stylesheets and scripts were repeatedly found loaded by `includes/header.php` only. `css/itflow_custom.css` and `libs/sweetalert2/css/sweetalert2.min.css` are now loaded by the client portal, guest, login, setup and MFA headers as well — the SweetAlert2 gap is why guest dialogs rendered in normal flow, since all of its positioning lives in the stylesheet while `includes/footer.php` was loading the JS fine.
jQuery removal:
- jQuery, jQuery UI, select2, Inputmask, daterangepicker, Moment, Tempus Dominus, toastr, pdfmake, Dropzone and Popper are all gone. Replacements are Tom Select, Flatpickr, IMask, SweetAlert2 and Bootstrap's own toasts, plus `js/autocomplete.js` for the product and OS autocompletes.
- New helpers in `js/app.js`: `itflowPostForm()` reproduces jQuery's bracketed array encoding that `ajax.php` parses, `itflowBindOnce()` replaces the namespaced `.off().on()` pattern, and `initTomSelect` / `refreshTomSelect` / `clearTomSelect` / `setTomSelectValue` wrap Tom Select.
- `includes/modal_footer.php` re-executes `js/app.js` on every ajax modal open, so every initialiser needs a re-entry guard or it double-initialises.
- New `itflowReady()` restores jQuery's `.ready()` semantics. Scripts injected into an ajax modal run after `DOMContentLoaded` has already fired, so a bare listener never runs — this is what broke notification pagination, asset OS autocomplete, the contact authentication toggle, AI ticket summaries and AI document template generation mid-cycle.
- `js/ajax_modal.js` re-injects `<script>` tags explicitly, because `innerHTML` does not execute them and `.append()` did.
Schema and queries:
- `history_description` widens from `varchar(200)` to `text` (2.7.7). A send to several recipients overflows 200 characters and strict mode errors rather than truncating.
- Indexes added on the client-scoped columns (2.7.5), the per-parent child fetches and the mail queue loop (2.7.6), and `logs(log_user_id, log_client_id)` for the portal profile and activity pages (2.7.8). No new migrations after 2.7.8 — the Stripe work reuses `client_payment_provider`.
- `agent/includes/inc_all_client.php` rewritten so the sidebar badge counts are one query per table rather than one per number: 43 queries to 27 per client page load, with invoices scanned once instead of eight times.
- `admin/audit_logs.php`'s date filter was `DATE(log_created_at) BETWEEN`, which is not sargable and made `KEY log_created_at` unusable. It is now a half-open range.
API:
- `apiClientScopeSql()` now takes an optional caller-supplied `client_id` and appends it after the scope fragment, so the filter can only narrow what the key is already allowed to see. Reads only — writes take `client_id` as the target they act on and validate it separately.
- `api/v1/tickets/close.php` sets status 4 with `ticket_resolved_at` first where the ticket has not been resolved, calls `syncTicketSlaClock()` and `setTicketResolutionSlaMet()`, then sets status 5 and stamps `ticket_closed_by`. Every query is scoped by `ticket_client_id` and `ticket_closed_at IS NULL`, so a second call is a no-op rather than a re-close.
- The four `*_output.php` helpers now `logApp()` on query failure, which previously returned an error to the caller and left no trace server-side.
Other:
- The client portal PIN handler checked length before `escapeSql()`, whose `strip_tags()` then emptied the value, and stored the blank while flashing success. Length is now checked after sanitising.
- The client delete modal's script was blocking the parser mid-body, which is why every client page flashed blank; it is deferred now.
- The gating fix under Security covers `agent/asset.php`, `agent/contact.php`, `agent/contacts.php` and `agent/locations.php`.
- The ticket task approver picker built `<option>` markup by template literal into `innerHTML`; it uses `new Option()` now, which assigns text.
- Stripe refunds live in `agent/post/payment.php` behind `refund_payment_stripe`, pulling the payment intent out of the stored reference and calling `refunds->create()` for the full amount. Partial refunds are not supported.
- Payment provider post variables moved into a model to stop the add and edit paths drifting apart.
- New `functions/network.php` (subnet containment and IP normalisation, v4 and v6) and `functions/files.php`; `functions/sla.php` extended for holidays and closure days.
- Phone input handling moved out of `js/app.js` into `js/phone_inputs.js` so the client portal can load it without pulling in everything else.
- Update path: `admin/post/update.php` no longer shells out at all. `cron/update_check.php` does the fetch and stores the result, `cron/app_update.php` runs `scripts/update_cli.php` as a child process, and the database phase re-execs against the newly updated code so a migration calling a brand new helper does not hit an undefined function.
- Removed 29 dead or duplicate files, including 348KB of unused FullCalendar themes, and fixed two broken script paths.
- The debug page now recommends 512M for PHP's memory limit.
- README, CONTRIBUTING and SECURITY.md updated.
### Library Updates
- Bump AdminLTE from 3.2.0 to 4.9.1.
- Bump Bootstrap from 4.6.2 to 5.3.8.
- Bump intl-tel-input from 25.3.0 to 29.2.3.
- Bump ImapEngine from 1.25.4 to 1.25.6, along with its dependencies.
- Added Tom Select, Flatpickr, IMask and SweetAlert2, none of which need jQuery.
- DataTables now uses its Bootstrap 5 styling build.
- Removed jQuery, jQuery UI, select2, select2-bootstrap4-theme, Inputmask, daterangepicker, Moment, Tempus Dominus, toastr, pdfmake, Dropzone, Popper and Show-Hide-Passwords-Bootstrap-4.
## [26.08.2] Maint Release
### Upgrading to 26.08.2
@@ -16,7 +154,6 @@ Update the files from Settings > Update as normal. There is no database change i
- API: restored the full record on the credentials list endpoint.
- Mail: switching an existing install from Standard SMTP/IMAP to Microsoft 365 or Google OAuth no longer leaves the old mail server behind, which stopped sending and ticket email fetching from working. The connection settings for OAuth providers are now fixed by the provider and cleared on save.
## [26.08.1] Maint Release
### Upgrading to 26.08.1

View File

@@ -146,6 +146,7 @@ ITFlow does not use prepared statements or an ORM; queries are built as strings.
- **Integers** (IDs, flags, counts): `intval($_POST['ticket_id'])`. Interpolate unquoted.
- **Strings**: `escapeSql($_POST['subject'])`. This normalizes encoding to UTF-8, then runs `strip_tags()`, `trim()`, and `mysqli_real_escape_string()`. Because it relies on SQL escaping, the value **must be placed inside quotes in the query** (`'$subject'`). An escaped string interpolated without quotes is still injectable.
- **Values read back from the database** get the same treatment before reuse in another query (you will see `escapeSql($row['ticket_prefix'])` throughout — this is why).
- **`logAudit()`, `appNotify()`, `logHistory()` and `logTicketHistory()` are queries too.** They interpolate their `$description` / `$details` / `$status` arguments straight into an `INSERT` — the SQL is just hidden inside the helper. A DB-read value passed into one of them (`logAudit("Asset", "Delete", "$asset_name ...", ...)`) must be `escapeSql`'d first, exactly as if you had written the `INSERT` by hand. This is easy to miss precisely because the call doesn't *look* like a query. Note that `escapeHtml()` is **not** a substitute here: it encodes `'` and `"` so it happens to block a quote-breakout, but it leaves backslashes untouched, so a value ending in `\` still escapes the closing quote. All four sinks now trim an odd trailing backslash as a backstop, but the value still owes `escapeSql` — the guard is defence-in-depth, not the fix.
If you write a query and even one variable in it skipped these, that is a SQL injection. This is the single most common review rejection.
**Fetch helpers return raw values — you escape them.** `getFieldById()` and `getTicketStatusName()` hand back exactly what is in the column. Escaping is the call site's job, the same as any other row you read:
@@ -291,6 +292,7 @@ Line endings and indentation are enforced by `.gitattributes` and `.editorconfig
- CI runs PHP lint and db.sql lint; SonarCloud scans for security issues. Green checks are required but not sufficient — the conventions above are checked by human review.
- Test your change against a real install: fresh setup from `db.sql` **and** an upgrade via `database_updates.php` if you touched schema.
- For anything larger than a bug fix, **open an issue first** and discuss the approach. ITFlow's roadmap favors incremental modernization of the existing PHP codebase; large rewrites, framework introductions, and new runtime dependencies are out of scope.
## Getting help
Open a GitHub issue using the templates, or ask in the community forum linked from the README. When in doubt about a convention, find the closest existing example in the codebase and follow it — consistency beats novelty here.
Ask in the community forum linked from the README. When in doubt about a convention, find the closest existing example in the codebase and follow it — consistency beats novelty here.

View File

@@ -27,7 +27,7 @@
·
<a href="https://forum.itflow.org/t/bug">Report Bug</a>
·
<a href="https://forum.itflow.org/t/features">Request Feature</a>
<a href="https://forum.itflow.org/t/features">Suggest Feature</a>
·
<a href="https://github.com/itflow-org/itflow/security/policy">Security</a>
</p>
@@ -44,16 +44,16 @@
### The Problem
- You're a small but busy managed service provider with 101 things to do. Information about your clients is unorganised, unstructured and outdated.
- For some work, you seem to spend longer looking for the relevant documentation than actually working on the issue/project.
- On top of the technical day to day, you also have to take care of the financial side of the business - consistent pricing, quotes/invoicing, and accounting.
- MSP tools can be expensive, with recurring licensing costs adding up quickly.
- On top of the technical day-to-day, you also have to take care of the financial side of the business - consistent pricing, quotes/invoicing, and accounting.
### The Solution: ITFlow
- ITFlow consolidates common MSP needs (documentation, ticketing and billing) into one unified system.
- ITFlow consolidates common MSP needs (documentation, ticketing and billing) into one unified, free platform that you control.
<!-- GETTING STARTED -->
## Getting Started
### Self Hosting
- The best installation method is to use the [install script](https://docs.itflow.org/installation_script) on Ubuntu/Debian. A video walk through is available [here](https://www.youtube.com/watch?v=kKz9NOU_1XE).
- The best installation method is to use the [install script](https://docs.itflow.org/installation_script) on Ubuntu/Debian. See the [video](https://www.youtube.com/watch?v=kKz9NOU_1XE) walk through.
```
wget -O itflow_install.sh https://github.com/itflow-org/itflow-install-script/raw/main/itflow_install.sh
bash itflow_install.sh
@@ -61,25 +61,25 @@
- Other manual installation methods are available in the [docs](https://docs.itflow.org/installation).
### Managed Hosting
- If you'd prefer, we can [host ITFlow for you](https://services.itflow.org/hosting.php).
- If you'd prefer, we can [host ITFlow for you](https://services.itflow.org/hosting.php) (coming soon).
<!-- FEATURES -->
## Key Features
* Client documentation - assets, contacts, domains, docs, files, passwords, and more
* Accounting / Billing - finance dashboard, quotes, invoices, accounting, expenses, etc
* Client Portal - self service quote/invoice/ticket management for clients
* Alerting - account balance, invoices, domain/SSL renewals
* Completely free & open-source alternative to ITGlue and Hudu
* Client documentation - assets, contacts, domains, docs/files, passwords, tickets, and more
* Billing - quotes, invoices and expenses, with Stripe integration for online payments
* Alerting - domain and SSL expiry/renewals
* Client Portal - self service quote, invoice, and ticket management for clients
<!-- ROADMAP -->
## Roadmap / Future to-do
We track the implementation of confirmed features and bugs via [TaskFlow](https://tasks.dev.itflow.org/tasks.php). Use the [forum](https://forum.itflow.org) to request features or raise bug reports.
We track the implementation of confirmed features and bugs via [TaskFlow](https://tasks.dev.itflow.org/tasks.php). Use the [forum](https://forum.itflow.org) to suggest new features or report bugs.
<!-- CONTRIBUTING -->
## Support & Contributions
### Forum
For help using ITFlow, bugs, feature requests, and general ideas / discussions please use the community [forum](https://forum.itflow.org).
For help using ITFlow, bugs, feature suggestions, and other general ideas or discussions, please use the community [forum](https://forum.itflow.org).
### Contributing
We have temporarily paused PRs from the community.
@@ -90,17 +90,16 @@ We have temporarily paused PRs from the community.
</a>
### Supporters
Were incredibly grateful to the organizations and individuals who support the project - a big thank you to:
Were incredibly grateful to the organizations and individuals who [sponsor](https://services.itflow.org) the project - a big thank you to:
- CompuMatter
- F1 for HELP
- digiBandit
- JetBrains (PhpStorm)
## License
ITFlow is distributed "as is" under the GPL License, WITHOUT WARRANTY OF ANY KIND. See [`LICENSE`](https://github.com/itflow-org/itflow/blob/master/LICENSE) for details.
## Security
* As of 2025, we now have a stable release of the project.
* We've had a stable release of the project since Jan 2025.
* Whilst we are confident in the safety of the code, no system is risk-free. Nearly all software has bugs. Use your best judgement before storing highly confidential information in ITFlow.
* If you have a security concern, privately report it [here](https://github.com/itflow-org/itflow/security/policy).

View File

@@ -1,24 +1,26 @@
# Security Policy
## **Please do NOT report security concerns/vulnerabilities publicly (Issues/forum)**
## **Please do NOT report security concerns/vulnerabilities publicly**
**We take security seriously**
**ITFlow stores important information, so we take security seriously**
- Whilst we are confident in the safety of the code, no system is risk-free. Nearly all software has bugs. Use your best judgement before storing highly confidential information in ITFlow.
- We attempt to follow security best practices where possible, including [automated code scanning](https://sonarcloud.io/component_measures?id=itflow-org_itflow&metric=security_rating&view=list).
- [![Security](https://sonarcloud.io/api/project_badges/measure?project=itflow-org_itflow&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=itflow-org_itflow)
- Whilst we are confident in the safety of the code, no system is risk-free. Nearly all software has bugs.
- Use your best judgement before storing highly confidential information in ITFlow.
- We attempt to follow security best practices where possible, including automated code scanning.
## Supported Versions
We operate a rolling release model. Any bug fixes will be released into latest version of ITFlow, so you must stay up-to-date.
| Version | Supported |
|---------| ------------------ |
| 26.08 | :white_check_mark: |
| 26.09 | :white_check_mark: |
## Reporting a Vulnerability via GitHub Security Advisories
**Security contact: [GitHub Security Advisories](https://github.com/itflow-org/itflow/security/advisories/new)**
If you have discovered a security issue, please **[report it](https://github.com/itflow-org/itflow/security/advisories/new)** to us in as much detail as possible, so we can fix it.
If you have discovered a security issue, please **[report it](https://github.com/itflow-org/itflow/security/advisories/new)** to us so we can fix it.
You should expect to receive an initial acknowledgement within 72 hours. If you don't receive any feedback, we may have missed the initial email from GitHub (we're human!). Please raise a forum discussion quoting ONLY the assigned GHSA ref.
You should expect to receive an initial acknowledgement within 7 days. If you don't receive any feedback, we may have missed the initial email from GitHub (we're human!). Please raise a forum discussion quoting ONLY the assigned GHSA ref.
Note: Please don't submit AI slop. AI can be useful for drafting and research, but you are responsible for reviewing and validating anything you submit. We reserve the right to restrict repository access if you repeatedly ignore this requirement.

View File

@@ -6,10 +6,12 @@ $order = "ASC";
require_once "includes/inc_all_admin.php";
$sql = mysqli_query($mysqli, "SELECT ai_model_id, ai_model_name, ai_model_prompt, ai_model_use_case, ai_provider_id,
ai_provider_name FROM ai_models LEFT JOIN ai_providers ON ai_model_ai_provider_id = ai_provider_id ORDER BY $sort $order");
$sql = mysqli_query($mysqli, "SELECT SQL_CALC_FOUND_ROWS ai_model_id, ai_model_name, ai_model_prompt, ai_model_use_case, ai_provider_id,
ai_provider_name FROM ai_models LEFT JOIN ai_providers ON ai_model_ai_provider_id = ai_provider_id
WHERE ai_model_name LIKE '%$q%' OR ai_provider_name LIKE '%$q%'
ORDER BY $sort $order LIMIT $record_from, $record_to");
$num_rows = mysqli_num_rows($sql);
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
@@ -23,95 +25,102 @@ $num_rows = mysqli_num_rows($sql);
<li class="breadcrumb-item active">AI Models</li>
</ol>
<div class="card card-dark">
<div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-robot mr-2"></i>AI Models</h3>
<div class="card">
<div class="card-header bg-dark py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-robot me-2"></i>AI Models</h3>
<div class="card-tools">
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/ai/ai_model_add.php"><i class="fas fa-plus mr-2"></i>Add Model</button>
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/ai/ai_model_add.php"><i class="fas fa-plus me-2"></i>Add Model</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover">
<thead class="text-dark <?php if ($num_rows == 0) { echo "d-none"; } ?>">
<tr>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=ai_model_name&order=<?= $disp ?>">
Model <?php if ($sort == 'ai_model_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=ai_provider_name&order=<?= $disp ?>">
Provider <?php if ($sort == 'ai_provider_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=ai_model_use_case&order=<?= $disp ?>">
Use Case<?php if ($sort == 'ai_model_use_case') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark">Prompt</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
<div class="card-header py-3">
<form autocomplete="off">
<div class="row g-2 align-items-center">
<div class="col-md-4">
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(escapeHtml($q)); } ?>" placeholder="Search AI Models">
<button class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
</form>
</div>
while ($row = mysqli_fetch_assoc($sql)) {
$provider_id = intval($row['ai_provider_id']);
$provider_name = escapeHtml($row['ai_provider_name']);
$model_id = intval($row['ai_model_id']);
$model_name = escapeHtml($row['ai_model_name']);
$use_case = escapeHtml($row['ai_model_use_case']);
$prompt = nl2br(escapeHtml($row['ai_model_prompt']));
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover mb-0">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th class="ps-3">
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=ai_model_name&order=<?= $disp ?>">
Model <?php if ($sort == 'ai_model_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=ai_provider_name&order=<?= $disp ?>">
Provider <?php if ($sort == 'ai_provider_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=ai_model_use_case&order=<?= $disp ?>">
Use Case<?php if ($sort == 'ai_model_use_case') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark">Prompt</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
?>
<tr>
<td>
<a class="text-dark text-bold ajax-modal" href="#"
data-modal-url="modals/ai/ai_model_edit.php?id=<?= $model_id ?>">
<?= $model_name ?>
</a>
</td>
<td><?= $provider_name ?></td>
<td><?= $use_case ?></td>
<td><?= $prompt ?></td>
<td>
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/ai/ai_model_edit.php?id=<?= $model_id ?>">
<i class="fas fa-fw fa-edit mr-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_ai_model=<?= $model_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
</div>
</div>
</td>
</tr>
<?php
}
if ($num_rows == 0) {
echo "<h3 class='text-secondary mt-3' style='text-align: center'>No Records Here</h3>";
}
while ($row = mysqli_fetch_assoc($sql)) {
$provider_id = intval($row['ai_provider_id']);
$provider_name = escapeHtml($row['ai_provider_name']);
$model_id = intval($row['ai_model_id']);
$model_name = escapeHtml($row['ai_model_name']);
$use_case = escapeHtml($row['ai_model_use_case']);
$prompt = nl2br(escapeHtml($row['ai_model_prompt']));
?>
<tr>
<td class="ps-3">
<a class="text-dark text-bold ajax-modal" href="#"
data-modal-url="modals/ai/ai_model_edit.php?id=<?= $model_id ?>">
<?= $model_name ?>
</a>
</td>
<td><?= $provider_name ?></td>
<td><?= $use_case ?></td>
<td><?= $prompt ?></td>
<td>
<div class="dropdown dropstart text-center">
<button class="btn btn-secondary btn-sm" type="button" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/ai/ai_model_edit.php?id=<?= $model_id ?>">
<i class="fas fa-fw fa-edit me-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_ai_model=<?= $model_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash me-2"></i>Delete
</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<?php
</div>
}
?>
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php"; ?>
</div>
<?php

View File

@@ -6,103 +6,112 @@ $order = "ASC";
require_once "includes/inc_all_admin.php";
$sql = mysqli_query($mysqli, "SELECT ai_provider_api_key, ai_provider_api_url, ai_provider_id, ai_provider_name FROM ai_providers ORDER BY $sort $order");
$sql = mysqli_query($mysqli, "SELECT SQL_CALC_FOUND_ROWS ai_provider_api_key, ai_provider_api_url, ai_provider_id, ai_provider_name FROM ai_providers
WHERE ai_provider_name LIKE '%$q%' OR ai_provider_api_url LIKE '%$q%'
ORDER BY $sort $order LIMIT $record_from, $record_to");
$num_rows = mysqli_num_rows($sql);
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="card card-dark">
<div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-robot mr-2"></i>AI Providers</h3>
<div class="card">
<div class="card-header bg-dark py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-robot me-2"></i>AI Providers</h3>
<div class="card-tools">
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/ai/ai_provider_add.php"><i class="fas fa-plus mr-2"></i>Add Provider</button>
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/ai/ai_provider_add.php"><i class="fas fa-plus me-2"></i>Add Provider</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover">
<thead class="text-dark <?php if ($num_rows == 0) { echo "d-none"; } ?>">
<tr>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=ai_provider_name&order=<?= $disp ?>">
Provider <?php if ($sort == 'ai_provider_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=ai_provider_api_url&order=<?= $disp ?>">
URL <?php if ($sort == 'ai_provider_api_url') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=ai_provider_api_key&order=<?= $disp ?>">
Key <?php if ($sort == 'ai_provider_api_key') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">
<a class="text-dark">Models</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
<div class="card-header py-3">
<form autocomplete="off">
<div class="row g-2 align-items-center">
<div class="col-md-4">
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(escapeHtml($q)); } ?>" placeholder="Search AI Providers">
<button class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
</form>
</div>
while ($row = mysqli_fetch_assoc($sql)) {
$provider_id = intval($row['ai_provider_id']);
$provider_name = escapeHtml($row['ai_provider_name']);
$url = escapeHtml($row['ai_provider_api_url']);
$key = escapeHtml($row['ai_provider_api_key']);
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover mb-0">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th class="ps-3">
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=ai_provider_name&order=<?= $disp ?>">
Provider <?php if ($sort == 'ai_provider_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=ai_provider_api_url&order=<?= $disp ?>">
URL <?php if ($sort == 'ai_provider_api_url') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=ai_provider_api_key&order=<?= $disp ?>">
Key <?php if ($sort == 'ai_provider_api_key') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">
<a class="text-dark">Models</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('ai_model_id') AS ai_model_count FROM ai_models WHERE ai_model_ai_provider_id = $provider_id"));
$ai_model_count = intval($row['ai_model_count']);
while ($row = mysqli_fetch_assoc($sql)) {
$provider_id = intval($row['ai_provider_id']);
$provider_name = escapeHtml($row['ai_provider_name']);
$url = escapeHtml($row['ai_provider_api_url']);
$key = escapeHtml($row['ai_provider_api_key']);
?>
<tr>
<td>
<a class="text-dark text-bold ajax-modal" href="#"
data-modal-url="modals/ai/ai_provider_edit.php?id=<?= $provider_id ?>">
<?= $provider_name ?>
</a>
</td>
<td><?= $url ?></td>
<td><?= $key ?></td>
<td class="text-center">
<a class="badge badge-dark badge-pill p-2" href="ai_models.php"><?= $ai_model_count ?></a>
<td>
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/ai/ai_provider_edit.php?id=<?= $provider_id ?>">
<i class="fas fa-fw fa-edit mr-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_ai_provider=<?= $provider_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
</div>
</div>
</td>
</tr>
<?php
}
if ($num_rows == 0) {
echo "<h3 class='text-secondary mt-3' style='text-align: center'>No Records Here</h3>";
}
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('ai_model_id') AS ai_model_count FROM ai_models WHERE ai_model_ai_provider_id = $provider_id"));
$ai_model_count = intval($row['ai_model_count']);
?>
<tr>
<td class="ps-3">
<a class="text-dark text-bold ajax-modal" href="#"
data-modal-url="modals/ai/ai_provider_edit.php?id=<?= $provider_id ?>">
<?= $provider_name ?>
</a>
</td>
<td><?= $url ?></td>
<td><?= $key ?></td>
<td class="text-center">
<a class="badge bg-dark rounded-pill p-2" href="ai_models.php"><?= $ai_model_count ?></a>
<td>
<div class="dropdown dropstart text-center">
<button class="btn btn-secondary btn-sm" type="button" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/ai/ai_provider_edit.php?id=<?= $provider_id ?>">
<i class="fas fa-fw fa-edit me-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_ai_provider=<?= $provider_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash me-2"></i>Delete
</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<?php
</div>
}
?>
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php"; ?>
</div>
<?php

View File

@@ -18,38 +18,36 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="card card-dark">
<div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-key mr-2"></i>API Keys</h3>
<div class="card">
<div class="card-header bg-dark py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-key me-2"></i>API Keys</h3>
<div class="card-tools">
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/api/api_key_add.php"><i class="fas fa-plus mr-2"></i>New API Key</button>
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/api/api_key_add.php"><i class="fas fa-plus me-2"></i>New API Key</button>
</div>
</div>
<div class="card-body">
<div class="card-header py-3">
<form autocomplete="off">
<div class="row">
<div class="row g-2 align-items-center">
<div class="col-md-4">
<div class="input-group mb-3 mb-md-0">
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(escapeHtml($q)); } ?>" placeholder="Search keys">
<div class="input-group-append">
<button class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
<div class="col-md-8">
<div class="btn-group float-right">
<div class="dropdown ml-2" id="bulkActionButton" hidden>
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">
<i class="fas fa-fw fa-layer-group mr-2"></i>Bulk Action (<span id="selectedCount">0</span>)
<div class="btn-group float-end">
<div class="dropdown ms-2" id="bulkActionButton" hidden>
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="fas fa-fw fa-layer-group me-2"></i>Bulk Action (<span id="selectedCount">0</span>)
</button>
<div class="dropdown-menu">
<button class="dropdown-item text-danger text-bold"
type="submit" form="bulkActions" name="bulk_delete_api_keys">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
<i class="fas fa-fw fa-trash me-2"></i>Delete
</button>
</div>
</div>
@@ -59,111 +57,110 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
</form>
<hr>
</div>
<div class="table-responsive-sm">
<div class="table-responsive-sm">
<form id="bulkActions" action="post.php" method="post">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<form id="bulkActions" action="post.php" method="post">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<table class="table table-striped table-borderless table-hover">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<table class="table table-striped table-borderless table-hover mb-0">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<td class="checkbox-column border-end">
<div class="form-check">
<input class="form-check-input" id="selectAllCheckbox" type="checkbox" onclick="checkAll(this)">
</div>
</td>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=api_key_name&order=<?= $disp ?>">
Name <?php if ($sort == 'api_key_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=api_key_user_id&order=<?= $disp ?>">
User <?php if ($sort == 'api_key_user_id') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=api_key_secret&order=<?= $disp ?>">
Secret <?php if ($sort == 'api_key_secret') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=api_key_created_at&order=<?= $disp ?>">
Created <?php if ($sort == 'api_key_created_at') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=api_key_expire&order=<?= $disp ?>">
Expires <?php if ($sort == 'api_key_expire') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_assoc($sql)) {
$api_key_id = intval($row['api_key_id']);
$api_key_name = escapeHtml($row['api_key_name']);
$api_key_secret = escapeHtml("************" . substr($row['api_key_secret'], -4));
$api_key_created_at = escapeHtml($row['api_key_created_at']);
$api_key_expire = escapeHtml($row['api_key_expire']);
if ($api_key_expire < date("Y-m-d H:i:s")) {
$api_key_expire = $api_key_expire . " (Expired)";
}
$api_key_user = !empty($row['user_name']) ? escapeHtml($row['user_name']) : "<i>None</i>";
?>
<tr>
<td class="pr-0">
<td class="checkbox-column bg-light border-end">
<div class="form-check">
<input class="form-check-input" type="checkbox" onclick="checkAll(this)">
<input class="form-check-input bulk-select" type="checkbox" name="api_key_ids[]" value="<?= $api_key_id ?>">
</div>
</td>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=api_key_name&order=<?= $disp ?>">
Name <?php if ($sort == 'api_key_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=api_key_user_id&order=<?= $disp ?>">
User <?php if ($sort == 'api_key_user_id') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=api_key_secret&order=<?= $disp ?>">
Secret <?php if ($sort == 'api_key_secret') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=api_key_created_at&order=<?= $disp ?>">
Created <?php if ($sort == 'api_key_created_at') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=api_key_expire&order=<?= $disp ?>">
Expires <?php if ($sort == 'api_key_expire') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_assoc($sql)) {
$api_key_id = intval($row['api_key_id']);
$api_key_name = escapeHtml($row['api_key_name']);
$api_key_secret = escapeHtml("************" . substr($row['api_key_secret'], -4));
$api_key_created_at = escapeHtml($row['api_key_created_at']);
$api_key_expire = escapeHtml($row['api_key_expire']);
if ($api_key_expire < date("Y-m-d H:i:s")) {
$api_key_expire = $api_key_expire . " (Expired)";
}
$api_key_user = !empty($row['user_name']) ? escapeHtml($row['user_name']) : "<i>None</i>";
?>
<tr>
<td class="pr-0">
<div class="form-check">
<input class="form-check-input bulk-select" type="checkbox" name="api_key_ids[]" value="<?= $api_key_id ?>">
</div>
</td>
<td class="text-bold"><?= $api_key_name ?></td>
<td><?= $api_key_user ?></td>
<td><?= $api_key_secret ?></td>
<td><?= $api_key_created_at ?></td>
<td><?= $api_key_expire ?></td>
<td>
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/api/api_key_edit.php?id=<?= $api_key_id ?>">
<i class="fas fa-fw fa-edit mr-2"></i>Edit
<td class="text-bold"><?= $api_key_name ?></td>
<td><?= $api_key_user ?></td>
<td><?= $api_key_secret ?></td>
<td><?= $api_key_created_at ?></td>
<td><?= $api_key_expire ?></td>
<td>
<div class="dropdown dropstart text-center">
<button class="btn btn-secondary btn-sm" type="button" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/api/api_key_edit.php?id=<?= $api_key_id ?>">
<i class="fas fa-fw fa-edit me-2"></i>Edit
</a>
<?php if ($api_key_expire > date("Y-m-d H:i:s")) { ?>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?revoke_api_key=<?= $api_key_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-times me-2"></i>Revoke
</a>
<?php if ($api_key_expire > date("Y-m-d H:i:s")) { ?>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?revoke_api_key=<?= $api_key_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-times mr-2"></i>Revoke
</a>
<?php } ?>
<?php if ($api_key_expire < date("Y-m-d H:i:s")) { ?>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_api_key=<?= $api_key_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-times mr-2"></i>Delete
</a>
<?php } ?>
</div>
<?php } ?>
<?php if ($api_key_expire < date("Y-m-d H:i:s")) { ?>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_api_key=<?= $api_key_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-times me-2"></i>Delete
</a>
<?php } ?>
</div>
</td>
</tr>
</div>
</td>
</tr>
<?php } ?>
<?php } ?>
</tbody>
</table>
</tbody>
</table>
</form>
</form>
</div>
<?php require_once "../includes/filter_footer.php"; ?>
</div>
<?php require_once "../includes/filter_footer.php"; ?>
</div>
<script src="../js/bulk_actions.js"></script>

View File

@@ -40,28 +40,26 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="card card-dark">
<div class="card-header py-3">
<h3 class="card-title"><i class="fas fa-fw fa-history mr-2"></i>App Logs</h3>
<div class="card">
<div class="card-header bg-dark py-2">
<h3 class="card-title"><i class="fas fa-fw fa-history me-2"></i>App Logs</h3>
</div>
<div class="card-body">
<div class="card-header py-3">
<form autocomplete="off">
<div class="row">
<div class="row g-2 align-items-end">
<div class="col-sm-4">
<div class="form-group">
<div>
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(escapeHtml($q)); } ?>" placeholder="Search app logs">
<div class="input-group-append">
<button class="btn btn-secondary" type="button" data-toggle="collapse" data-target="#advancedFilter"><i class="fas fa-filter"></i></button>
<button class="btn btn-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#advancedFilter"><i class="fas fa-filter"></i></button>
<button class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
</div>
<div class="col-sm-2">
<div class="form-group">
<select class="form-control select2" name="type" onchange="this.form.submit()">
<div>
<select class="form-select select2" name="type" onchange="this.form.submit()">
<option value="">- All Types -</option>
<?php
@@ -79,8 +77,8 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
<div class="col-sm-2">
<div class="form-group">
<select class="form-control select2" name="category" onchange="this.form.submit()">
<div>
<select class="form-select select2" name="category" onchange="this.form.submit()">
<option value="">- All Categories -</option>
<?php
@@ -98,10 +96,10 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
</div>
<div class="collapse mt-3 <?php if (isset($_GET['dtf']) && $_GET['dtf'] !== '1970-01-01') { echo "show"; } ?>" id="advancedFilter">
<div class="row">
<div class="row g-3">
<div class="col-md-3">
<div class="form-group">
<label>Date range</label>
<div>
<label class="form-label">Date range</label>
<input type="text" id="dateFilter" class="form-control" autocomplete="off">
<input type="hidden" name="canned_date" id="canned_date" value="<?= escapeHtml($_GET['canned_date']) ?? '' ?>">
<input type="hidden" name="dtf" id="dtf" value="<?= escapeHtml($dtf ?? '') ?>">
@@ -111,62 +109,61 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
</div>
</form>
<hr>
<div class="table-responsive-sm">
<table class="table table-sm table-striped table-borderless table-hover">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=app_log_created_at&order=<?= $disp ?>">
Timestamp <?php if ($sort == 'app_log_created_at') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=app_log_type&order=<?= $disp ?>">
Type <?php if ($sort == 'app_log_type') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=app_log_category&order=<?= $disp ?>">
Category <?php if ($sort == 'app_log_category') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=app_log_details&order=<?= $disp ?>">
Details <?php if ($sort == 'app_log_details') { echo $order_icon; } ?>
</a>
</th>
</tr>
</thead>
<tbody>
<?php
</div>
<div class="table-responsive-sm">
<table class="table table-sm table-striped table-borderless table-hover mb-0">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th class="ps-3">
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=app_log_created_at&order=<?= $disp ?>">
Timestamp <?php if ($sort == 'app_log_created_at') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=app_log_type&order=<?= $disp ?>">
Type <?php if ($sort == 'app_log_type') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=app_log_category&order=<?= $disp ?>">
Category <?php if ($sort == 'app_log_category') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=app_log_details&order=<?= $disp ?>">
Details <?php if ($sort == 'app_log_details') { echo $order_icon; } ?>
</a>
</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_assoc($sql)) {
$log_id = intval($row['app_log_id']);
$log_type = escapeHtml($row['app_log_type']);
$log_category = escapeHtml($row['app_log_category']);
$log_details = escapeHtml($row['app_log_details']);
$log_created_at = escapeHtml($row['app_log_created_at']);
while ($row = mysqli_fetch_assoc($sql)) {
$log_id = intval($row['app_log_id']);
$log_type = escapeHtml($row['app_log_type']);
$log_category = escapeHtml($row['app_log_category']);
$log_details = escapeHtml($row['app_log_details']);
$log_created_at = escapeHtml($row['app_log_created_at']);
?>
<tr>
<td class="text-monospace"><?= $log_created_at ?></td>
<td><?= $log_type ?></td>
<td><?= $log_category ?></td>
<td><?= $log_details ?></td>
</tr>
<?php
}
?>
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php";
?>
<tr>
<td class="font-monospace ps-3"><?= $log_created_at ?></td>
<td><?= $log_type ?></td>
<td><?= $log_category ?></td>
<td><?= $log_details ?></td>
</tr>
<?php
}
?>
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php";
?>
</div>
<?php

View File

@@ -46,6 +46,14 @@ if (isset($_GET['action']) & !empty($_GET['action'])) {
$action_filter = '';
}
/*
* The date filter is a half-open range rather than DATE(log_created_at) BETWEEN
* ... - wrapping the column in a function makes the comparison non-sargable, so
* KEY log_created_at could never be used and every page view scanned the whole
* table. '< dtt + 1 day' keeps the end date inclusive, which BETWEEN on a bare
* DATETIME would not: '2026-08-28' alone means midnight, and would drop that
* day's rows.
*/
$sql = mysqli_query(
$mysqli,
"SELECT SQL_CALC_FOUND_ROWS client_id, client_name, log_action, log_created_at, log_description, log_entity_id, log_id,
@@ -53,7 +61,7 @@ $sql = mysqli_query(
LEFT JOIN users ON log_user_id = user_id
LEFT JOIN clients ON log_client_id = client_id
WHERE (log_type LIKE '%$q%' OR log_action LIKE '%$q%' OR log_description LIKE '%$q%' OR log_ip LIKE '%$q%' OR log_user_agent LIKE '%$q%' OR user_name LIKE '%$q%' OR client_name LIKE '%$q%')
AND DATE(log_created_at) BETWEEN '$dtf' AND '$dtt'
AND log_created_at >= '$dtf 00:00:00' AND log_created_at < DATE_ADD('$dtt', INTERVAL 1 DAY)
$user_query
$client_query
$log_type_query
@@ -65,26 +73,24 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="card card-dark">
<div class="card-header py-3">
<h3 class="card-title"><i class="fas fa-fw fa-history mr-2"></i>Audit Logs</h3>
<div class="card">
<div class="card-header bg-dark py-2">
<h3 class="card-title"><i class="fas fa-fw fa-history me-2"></i>Audit Logs</h3>
</div>
<div class="card-body">
<form class="mb-4" autocomplete="off">
<div class="row">
<div class="card-header py-3">
<form autocomplete="off">
<div class="row g-2 align-items-end">
<div class="col-sm-4">
<div class="input-group mb-3 mb-md-0">
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(escapeHtml($q)); } ?>" placeholder="Search audit logs">
<div class="input-group-append">
<button class="btn btn-secondary" type="button" data-toggle="collapse" data-target="#advancedFilter"><i class="fas fa-filter"></i></button>
<button class="btn btn-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#advancedFilter"><i class="fas fa-filter"></i></button>
<button class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
<div class="col-sm-2">
<div class="input-group mb-3 mb-md-0">
<select class="form-control select2" name="client" onchange="this.form.submit()">
<div class="input-group">
<select class="form-select select2" name="client" onchange="this.form.submit()">
<option value="">- All Clients -</option>
<?php
@@ -103,8 +109,8 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
<div class="col-sm-2">
<div class="input-group mb-3 mb-md-0">
<select class="form-control select2" name="user" onchange="this.form.submit()">
<div class="input-group">
<select class="form-select select2" name="user" onchange="this.form.submit()">
<option value="">- All Users -</option>
<?php
@@ -123,8 +129,8 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
<div class="col-sm-2">
<div class="input-group mb-3 mb-md-0">
<select class="form-control select2" name="type" onchange="this.form.submit()">
<div class="input-group">
<select class="form-select select2" name="type" onchange="this.form.submit()">
<option value="">- All Types -</option>
<?php
@@ -142,8 +148,8 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
<div class="col-sm-2">
<div class="input-group mb-3 mb-md-0">
<select class="form-control select2" name="action" onchange="this.form.submit()">
<div class="input-group">
<select class="form-select select2" name="action" onchange="this.form.submit()">
<option value="">- All Actions -</option>
<?php
@@ -160,11 +166,11 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
</div>
</div>
<div class="collapse mt-3 <?php if (isset($_GET['dtf']) && $_GET['dtf'] !== '1970-01-01') { echo "show"; } ?>" id="advancedFilter">
<div class="row">
<div class="collapse mt-3 <?php if (isset($_GET['dtf']) && $_GET['dtf'] !== '1970-01-01') { echo"show"; } ?>" id="advancedFilter">
<div class="row g-3">
<div class="col-md-3">
<div class="form-group">
<label>Date range</label>
<div>
<label class="form-label">Date range</label>
<input type="text" id="dateFilter" class="form-control" autocomplete="off">
<input type="hidden" name="canned_date" id="canned_date" value="<?= escapeHtml($_GET['canned_date']) ?? '' ?>">
<input type="hidden" name="dtf" id="dtf" value="<?= escapeHtml($dtf ?? '') ?>">
@@ -174,109 +180,108 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
</div>
</form>
<hr>
<div class="table-responsive-sm">
<table class="table table-sm table-striped table-borderless table-hover">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?> text-nowrap">
<tr>
</div>
<div class="table-responsive-sm">
<table class="table table-sm table-striped table-borderless table-hover mb-0">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?> text-nowrap">
<tr>
<th class="ps-3">
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=log_created_at&order=<?= $disp ?>">
Timestamp <?php if ($sort == 'log_created_at') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=user_name&order=<?= $disp ?>">
User <?php if ($sort == 'user_name') { echo $order_icon; } ?>
</a>
</th>
<?php if (empty($client)) { ?>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=log_created_at&order=<?= $disp ?>">
Timestamp <?php if ($sort == 'log_created_at') { echo $order_icon; } ?>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=client_name&order=<?= $disp ?>">
Client <?php if ($sort == 'client_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=user_name&order=<?= $disp ?>">
User <?php if ($sort == 'user_name') { echo $order_icon; } ?>
</a>
</th>
<?php if (empty($client)) { ?>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=client_name&order=<?= $disp ?>">
Client <?php if ($sort == 'client_name') { echo $order_icon; } ?>
</a>
</th>
<?php } ?>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=log_type&order=<?= $disp ?>">
Type <?php if ($sort == 'log_type') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=log_action&order=<?= $disp ?>">
Action <?php if ($sort == 'log_action') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=log_description&order=<?= $disp ?>">
Description <?php if ($sort == 'log_description') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=log_ip&order=<?= $disp ?>">
IP Address <?php if ($sort == 'log_ip') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=log_user_agent&order=<?= $disp ?>">
User Agent <?php if ($sort == 'log_user_agent') { echo $order_icon; } ?>
</a>
</th>
</tr>
</thead>
<tbody>
<?php
<?php } ?>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=log_type&order=<?= $disp ?>">
Type <?php if ($sort == 'log_type') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=log_action&order=<?= $disp ?>">
Action <?php if ($sort == 'log_action') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=log_description&order=<?= $disp ?>">
Description <?php if ($sort == 'log_description') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=log_ip&order=<?= $disp ?>">
IP Address <?php if ($sort == 'log_ip') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=log_user_agent&order=<?= $disp ?>">
User Agent <?php if ($sort == 'log_user_agent') { echo $order_icon; } ?>
</a>
</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_assoc($sql)) {
$log_id = intval($row['log_id']);
$log_type = escapeHtml($row['log_type']);
$log_action = escapeHtml($row['log_action']);
$log_description = escapeHtml($row['log_description']);
$log_ip = escapeHtml($row['log_ip']);
$log_user_agent = escapeHtml($row['log_user_agent']);
$log_user_os = getOS($log_user_agent);
$log_user_browser = getWebBrowser($log_user_agent);
$log_created_at = escapeHtml($row['log_created_at']);
$user_id = intval($row['user_id']);
$user_name = escapeHtml($row['user_name']);
if (empty($user_name)) {
$user_name_display = "-";
} else {
$user_name_display = $user_name;
}
$client_name = escapeHtml($row['client_name']);
$client_id = intval($row['client_id']);
if (empty($client_name)) {
$client_name_display = "-";
} else {
$client_name_display = "<a href='../agent/client_overview.php?client_id=$client_id'>$client_name</a>";
}
$log_entity_id = intval($row['log_entity_id']);
?>
<tr>
<td class="text-monospace"><?= $log_created_at ?></td>
<td><?= $user_name_display ?></td>
<?php if(empty($client)) { ?>
<td><?= $client_name_display ?></td>
<?php } ?>
<td><?= $log_type ?></td>
<td><?= $log_action ?></td>
<td><?= $log_description ?></td>
<td class="text-monospace"><?= $log_ip ?></td>
<td><?= "$log_user_os<div class='text-secondary'>$log_user_browser</div>" ?></td>
</tr>
<?php
while ($row = mysqli_fetch_assoc($sql)) {
$log_id = intval($row['log_id']);
$log_type = escapeHtml($row['log_type']);
$log_action = escapeHtml($row['log_action']);
$log_description = escapeHtml($row['log_description']);
$log_ip = escapeHtml($row['log_ip']);
$log_user_agent = escapeHtml($row['log_user_agent']);
$log_user_os = getOS($log_user_agent);
$log_user_browser = getWebBrowser($log_user_agent);
$log_created_at = escapeHtml($row['log_created_at']);
$user_id = intval($row['user_id']);
$user_name = escapeHtml($row['user_name']);
if (empty($user_name)) {
$user_name_display = "-";
} else {
$user_name_display = $user_name;
}
$client_name = escapeHtml($row['client_name']);
$client_id = intval($row['client_id']);
if (empty($client_name)) {
$client_name_display = "-";
} else {
$client_name_display = "<a href='../agent/client_overview.php?client_id=$client_id'>$client_name</a>";
}
$log_entity_id = intval($row['log_entity_id']);
?>
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php";
?>
<tr>
<td class="font-monospace ps-3"><?= $log_created_at ?></td>
<td><?= $user_name_display ?></td>
<?php if(empty($client)) { ?>
<td><?= $client_name_display ?></td>
<?php } ?>
<td><?= $log_type ?></td>
<td><?= $log_action ?></td>
<td><?= $log_description ?></td>
<td class="font-monospace"><?= $log_ip ?></td>
<td><?= "$log_user_os<div class='text-secondary'>$log_user_browser</div>" ?></td>
</tr>
<?php
}
?>
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php";
?>
</div>
<?php

View File

@@ -33,15 +33,15 @@ if (!empty($_SESSION['backup_master_key_reveal'])) {
unset($_SESSION['backup_master_key_reveal']);
?>
<div class="alert alert-warning">
<h5><i class="fas fa-fw fa-key mr-2"></i>Master encryption key</h5>
<h5><i class="fas fa-fw fa-key me-2"></i>Master encryption key</h5>
<p class="mb-2">Shown once. Refreshing this page will not show it again.</p>
<input type="text" class="form-control text-monospace" value="<?= escapeHtml($master_key_reveal) ?>" readonly onclick="this.select();">
<input type="text" class="form-control font-monospace" value="<?= escapeHtml($master_key_reveal) ?>" readonly onclick="this.select();">
</div>
<?php } ?>
<?php if ($backup_key === '') { ?>
<div class="alert alert-danger">
<h5><i class="fas fa-fw fa-exclamation-triangle mr-2"></i>No backup encryption key</h5>
<h5><i class="fas fa-fw fa-exclamation-triangle me-2"></i>No backup encryption key</h5>
ITFlow could not write a backup encryption key to <strong>config.php</strong>, so it cannot produce an encrypted backup.
Make config.php writable by the web server user and reload this page, or add a line like
<code>$config_backup_key = '&lt;32 random characters&gt;';</code> to it yourself.
@@ -50,26 +50,26 @@ if (!empty($_SESSION['backup_master_key_reveal'])) {
<?php if (!$cron_is_running) { ?>
<div class="alert alert-danger">
<h5><i class="fas fa-fw fa-exclamation-triangle mr-2"></i>Cron is not running</h5>
<h5><i class="fas fa-fw fa-exclamation-triangle me-2"></i>Cron is not running</h5>
Backups are built by the cron dispatcher, not by your browser. Until cron is running, anything you
start here will sit in the queue. See <a href="cron.php">Maintenance &gt; Cron</a>.
</div>
<?php } elseif ($config_enable_cron == 0) { ?>
<div class="alert alert-warning">
<i class="fas fa-fw fa-exclamation-circle mr-2"></i>Cron is switched off in
<i class="fas fa-fw fa-exclamation-circle me-2"></i>Cron is switched off in
<a href="cron.php">Maintenance &gt; Cron</a>.
</div>
<?php } ?>
<div class="card card-dark mb-3">
<div class="card-header py-3">
<h3 class="card-title"><i class="fas fa-fw fa-cloud-upload-alt mr-2"></i>Create a Backup</h3>
<h3 class="card-title"><i class="fas fa-fw fa-cloud-upload-alt me-2"></i>Create a Backup</h3>
</div>
<div class="card-body">
<?php if ($pending_count > 0) { ?>
<div class="alert alert-info">
<i class="fas fa-fw fa-spinner mr-2"></i><strong><?= $pending_count ?></strong> backup<?= $pending_count == 1 ? ' is' : 's are' ?>
<i class="fas fa-fw fa-spinner me-2"></i><strong><?= $pending_count ?></strong> backup<?= $pending_count == 1 ? ' is' : 's are' ?>
queued or building. You will get a notification when ready - this page does not refresh itself.
</div>
<?php } ?>
@@ -81,7 +81,7 @@ if (!empty($_SESSION['backup_master_key_reveal'])) {
<h5>Full Backup</h5>
<p class="text-muted small">The database and everything in the uploads folder. This is the one to keep.</p>
<a class="btn btn-primary <?= $backup_key === '' ? 'disabled' : '' ?>" href="post.php?queue_backup=full&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-play mr-2"></i>Start
<i class="fas fa-fw fa-play me-2"></i>Start
</a>
</div>
</div>
@@ -91,7 +91,7 @@ if (!empty($_SESSION['backup_master_key_reveal'])) {
<h5>Database Only</h5>
<p class="text-muted small">Just the SQL dump. Much smaller and much quicker, but no attachments or documents.</p>
<a class="btn btn-primary <?= $backup_key === '' ? 'disabled' : '' ?>" href="post.php?queue_backup=database&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-play mr-2"></i>Start
<i class="fas fa-fw fa-play me-2"></i>Start
</a>
</div>
</div>
@@ -100,8 +100,8 @@ if (!empty($_SESSION['backup_master_key_reveal'])) {
<i class="fas fa-fw fa-3x fa-key text-dark mb-3"></i>
<h5>Master Key</h5>
<p class="text-muted small">The credential vault key. Only needed if every user password is lost - a normal restore recovers the vault on its own.</p>
<button type="button" class="btn btn-secondary" data-toggle="modal" data-target="#masterKeyModal">
<i class="fas fa-fw fa-key mr-2"></i>Export
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#masterKeyModal">
<i class="fas fa-fw fa-key me-2"></i>Export
</button>
</div>
</div>
@@ -112,7 +112,7 @@ if (!empty($_SESSION['backup_master_key_reveal'])) {
<div class="card card-dark mb-3">
<div class="card-header py-3">
<h3 class="card-title"><i class="fas fa-fw fa-archive mr-2"></i>Backups</h3>
<h3 class="card-title"><i class="fas fa-fw fa-archive me-2"></i>Backups</h3>
</div>
<div class="card-body p-0">
<div class="table-responsive">
@@ -124,7 +124,7 @@ if (!empty($_SESSION['backup_master_key_reveal'])) {
<th>Size</th>
<th>Source</th>
<th>Status</th>
<th class="text-right">Action</th>
<th class="text-end">Action</th>
</tr>
</thead>
<tbody>
@@ -148,12 +148,12 @@ if (!empty($_SESSION['backup_master_key_reveal'])) {
<td><?= $backup['backup_size'] > 0 ? escapeHtml(backupFormatBytes($backup['backup_size'])) : '-' ?></td>
<td><?= escapeHtml($backup['backup_source']) ?></td>
<td>
<span class="badge badge-<?= $badge ?>"><?= escapeHtml($status) ?></span>
<span class="badge text-bg-<?= $badge ?>"><?= escapeHtml($status) ?></span>
<?php if (!empty($backup['backup_error'])) { ?>
<br><small class="text-danger"><?= escapeHtml($backup['backup_error']) ?></small>
<?php } ?>
</td>
<td class="text-right">
<td class="text-end">
<?php if ($status === 'Complete') { ?>
<a class="btn btn-sm btn-primary" href="backup_download.php?backup_id=<?= $backup_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-download"></i>
@@ -173,11 +173,11 @@ if (!empty($_SESSION['backup_master_key_reveal'])) {
<div class="card card-dark mb-3">
<div class="card-header py-3">
<h3 class="card-title"><i class="fas fa-fw fa-lock mr-2"></i>Encryption Key</h3>
<h3 class="card-title"><i class="fas fa-fw fa-lock me-2"></i>Encryption Key</h3>
</div>
<div class="card-body">
<div class="alert alert-warning mb-3">
<i class="fas fa-fw fa-exclamation-triangle mr-2"></i>
<i class="fas fa-fw fa-exclamation-triangle me-2"></i>
<strong>Write this down and keep it somewhere other than this server.</strong>
Every backup is encrypted with it, and without it a backup cannot be restored - not by you,
not by anyone. It is stored in config.php and never in the database, which is what stops a
@@ -185,13 +185,11 @@ if (!empty($_SESSION['backup_master_key_reveal'])) {
</div>
<?php if ($backup_key !== '') { ?>
<div class="input-group col-md-6 px-0">
<input type="text" class="form-control text-monospace" value="<?= escapeHtml($backup_key) ?>" readonly onclick="this.select();">
<div class="input-group-append">
<div class="input-group">
<input type="text" class="form-control font-monospace" value="<?= escapeHtml($backup_key) ?>" readonly onclick="this.select();">
<button class="btn btn-secondary" type="button" onclick="navigator.clipboard.writeText('<?= escapeHtml($backup_key) ?>');">
<i class="fas fa-fw fa-copy"></i>
</button>
</div>
</div>
<?php } ?>
@@ -204,43 +202,43 @@ if (!empty($_SESSION['backup_master_key_reveal'])) {
<div class="card card-dark mb-3">
<div class="card-header py-3">
<h3 class="card-title"><i class="fas fa-fw fa-clock mr-2"></i>Scheduled Backups &amp; Retention</h3>
<h3 class="card-title"><i class="fas fa-fw fa-clock me-2"></i>Scheduled Backups &amp; Retention</h3>
</div>
<div class="card-body">
<form action="post.php" method="POST" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="form-row">
<div class="form-group col-md-4">
<div class="row g-2">
<div class="mb-3 col-md-4">
<label>Scheduled backup type</label>
<select class="form-control" name="config_backup_cron_type">
<select class="form-select" name="config_backup_cron_type">
<option <?= $config_backup_cron_type === 'full' ? 'selected' : '' ?> value="full">Full Backup</option>
<option <?= $config_backup_cron_type === 'database' ? 'selected' : '' ?> value="database">Database Only</option>
</select>
</div>
<div class="form-group col-md-4">
<div class="mb-3 col-md-4">
<label>Keep backups for (days)</label>
<input type="number" class="form-control" name="config_backup_retention_days" min="0" value="<?= intval($config_backup_retention_days) ?>">
<small class="text-muted">0 disables age-based deletion.</small>
</div>
<div class="form-group col-md-4">
<div class="mb-3 col-md-4">
<label>Keep at most (per type)</label>
<input type="number" class="form-control" name="config_backup_retention_count" min="1" value="<?= intval($config_backup_retention_count) ?>">
<small class="text-muted">Counted separately for each type. The newest of each is never deleted.</small>
</div>
</div>
<button type="submit" name="edit_backup_settings" class="btn btn-primary"><i class="fas fa-fw fa-check mr-2"></i>Save</button>
<button type="submit" name="edit_backup_settings" class="btn btn-primary"><i class="fas fa-fw fa-check me-2"></i>Save</button>
</form>
<hr>
<p class="mb-0">
<?php if (!empty($backup_job) && intval($backup_job['cron_job_enabled']) === 1) { ?>
<i class="fas fa-fw fa-check text-success mr-2"></i>Scheduled backups run daily at
<i class="fas fa-fw fa-check text-success me-2"></i>Scheduled backups run daily at
<strong><?= escapeHtml(substr((string)$backup_job['cron_job_daily_at'], 0, 5)) ?></strong>.
<?php } else { ?>
<i class="fas fa-fw fa-times text-danger mr-2"></i>Scheduled backups are switched off.
<i class="fas fa-fw fa-times text-danger me-2"></i>Scheduled backups are switched off.
<?php } ?>
Turn them on or change the time in <a href="cron.php">Maintenance &gt; Cron</a>.
</p>
@@ -253,7 +251,7 @@ if (!empty($_SESSION['backup_master_key_reveal'])) {
<div class="card card-dark">
<div class="card-header py-3">
<h3 class="card-title"><i class="fas fa-fw fa-undo mr-2"></i>Restoring</h3>
<h3 class="card-title"><i class="fas fa-fw fa-undo me-2"></i>Restoring</h3>
</div>
<div class="card-body">
<p>Restoring replaces the database and the uploads folder with what is in the archive. It cannot be done from here, on purpose - a running install is the wrong place to be dropping its own tables from a browser.</p>
@@ -267,8 +265,8 @@ if (!empty($_SESSION['backup_master_key_reveal'])) {
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-fw fa-key mr-2"></i>Export Master Key</h5>
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
<h5 class="modal-title"><i class="fas fa-fw fa-key me-2"></i>Export Master Key</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="POST" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -276,13 +274,13 @@ if (!empty($_SESSION['backup_master_key_reveal'])) {
<div class="alert alert-warning">
This key decrypts every credential in this install. It is shown on screen and is not written anywhere.
</div>
<div class="form-group">
<div class="mb-3">
<label>Confirm your account password</label>
<input type="password" class="form-control" name="password" autocomplete="new-password" required>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="backup_master_key" class="btn btn-primary"><i class="fas fa-fw fa-key mr-2"></i>Show Master Key</button>
<button type="submit" name="backup_master_key" class="btn btn-primary"><i class="fas fa-fw fa-key me-2"></i>Show Master Key</button>
</div>
</form>
</div>

134
admin/canned_responses.php Normal file
View File

@@ -0,0 +1,134 @@
<?php
// Default Column Sortby Filter
$sort = "canned_response_name";
$order = "ASC";
require_once "includes/inc_all_admin.php";
/*
* A canned response either belongs to one ticket category or, with category id 0, offers
* itself on every ticket. The LEFT JOIN is what puts the category name on the row; a
* response whose category was deleted since falls back to the general case below.
*/
$sql = mysqli_query(
$mysqli,
"SELECT SQL_CALC_FOUND_ROWS canned_response_id, canned_response_name, canned_response_body,
canned_response_category_id, category_name
FROM canned_responses
LEFT JOIN categories ON canned_response_category_id = category_id AND category_type = 'Ticket'
WHERE (canned_response_name LIKE '%$q%' OR canned_response_body LIKE '%$q%')
AND canned_response_archived_at IS NULL
ORDER BY $sort $order
LIMIT $record_from, $record_to"
);
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="card">
<div class="card-header bg-dark py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-comment-dots me-2"></i>Canned Responses</h3>
<div class="card-tools">
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/canned_response/canned_response_add.php" data-modal-size="lg"><i class="fas fa-plus me-2"></i>New Canned Response</button>
</div>
</div>
<div class="card-header py-3">
<form autocomplete="off">
<div class="row g-2 align-items-center">
<div class="col-md-4">
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if(isset($q)){ echo stripslashes(escapeHtml($q)); } ?>" placeholder="Search Canned Responses">
<button class="btn btn-dark"><i class="fa fa-search"></i></button>
</div>
</div>
<div class="col-md-8">
</div>
</div>
</form>
</div>
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover mb-0">
<thead class="text-dark <?php if($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th class="ps-3">
<a class="text-secondary" href="?<?= $url_query_strings_sort ?>&sort=canned_response_name&order=<?= $disp ?>">
Response <?php if ($sort == 'canned_response_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-secondary" href="?<?= $url_query_strings_sort ?>&sort=category_name&order=<?= $disp ?>">
Ticket Category <?php if ($sort == 'category_name') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
while($row = mysqli_fetch_assoc($sql)){
$canned_response_id = intval($row['canned_response_id']);
$canned_response_name = escapeHtml($row['canned_response_name']);
$canned_response_category_id = intval($row['canned_response_category_id']);
$canned_response_category_name = escapeHtml($row['category_name']);
// The body is HTML from TinyMCE. This is a preview, not a render - tags
// are stripped rather than escaped so the column reads as the sentence
// the agent will actually be inserting
$canned_response_preview = escapeHtml(mb_strimwidth(trim(html_entity_decode(strip_tags($row['canned_response_body']), ENT_QUOTES, 'UTF-8')), 0, 120, '...'));
?>
<tr>
<td class="ps-3">
<div class="d-flex">
<i class="fa fa-fw fa-2x fa-comment-dots me-3"></i>
<div class="flex-grow-1">
<div>
<a class="ajax-modal" href="#" data-modal-url="modals/canned_response/canned_response_edit.php?id=<?= $canned_response_id ?>" data-modal-size="lg">
<?= $canned_response_name ?>
</a>
</div>
<div><small class="text-secondary"><?= $canned_response_preview ?></small></div>
</div>
</div>
</td>
<td>
<?php if ($canned_response_category_id && $canned_response_category_name) { ?>
<span class="badge bg-secondary"><?= $canned_response_category_name ?></span>
<?php } else { ?>
<span class="badge bg-dark">All ticket categories</span>
<?php } ?>
</td>
<td>
<div class="dropdown dropstart text-center">
<button class="btn btn-secondary btn-sm" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/canned_response/canned_response_edit.php?id=<?= $canned_response_id ?>" data-modal-size="lg">
<i class="fas fa-fw fa-edit me-2"></i>Edit
</a>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_canned_response=<?= $canned_response_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash me-2"></i>Delete
</a>
</div>
</div>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php";
?>
</div>
<?php
require_once "../includes/footer.php";

View File

@@ -67,7 +67,7 @@ $archive_toggle_url = '?' . http_build_query($archive_toggle_query);
<div class="card card-dark">
<div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fa fa-fw fa-list-ul mr-2"></i>
<h3 class="card-title mt-2"><i class="fa fa-fw fa-list-ul me-2"></i>
<?= escapeHtml($category_label) ?> Categories
</h3>
<?php
@@ -75,7 +75,7 @@ $archive_toggle_url = '?' . http_build_query($archive_toggle_query);
?>
<div class="card-tools">
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/category/category_add.php?category=<?= escapeHtml($category) ?>"><i
class="fas fa-plus mr-2"></i>New <?= escapeHtml($category_label) ?> Category</button>
class="fas fa-plus me-2"></i>New <?= escapeHtml($category_label) ?> Category</button>
</div>
<?php
}
@@ -85,15 +85,15 @@ $archive_toggle_url = '?' . http_build_query($archive_toggle_query);
<div class="row">
<!-- Category types -->
<div class="col-md-3 border-right mb-3">
<div class="col-md-3 border-end mb-3">
<ul class="nav nav-pills flex-column bg-light">
<?php foreach ($category_types as $category_type => $category_type_details) { ?>
<li class="nav-item">
<a class="nav-link<?php if ($category == $category_type) {
echo ' active';
} ?>" href="?category=<?= urlencode($category_type) ?>">
<i class="fa fa-fw <?= $category_type_details['icon'] ?> mr-2"></i><?= escapeHtml($category_type_details['label']) ?>
<span class="badge badge-pill badge-dark float-right mt-1"><?= $category_type_counts[$category_type] ?? 0 ?></span>
<i class="fa fa-fw <?= $category_type_details['icon'] ?> me-2"></i><?= escapeHtml($category_type_details['label']) ?>
<span class="badge rounded-pill bg-dark float-end mt-1"><?= $category_type_counts[$category_type] ?? 0 ?></span>
</a>
</li>
<?php } ?>
@@ -115,25 +115,23 @@ $archive_toggle_url = '?' . http_build_query($archive_toggle_query);
echo stripslashes(escapeHtml($q));
} ?>"
placeholder="Search <?= escapeHtml($category_label) ?> Categories ">
<div class="input-group-append">
<button class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
<div class="col-sm-6 mb-2">
<a href="<?= $archive_toggle_url ?>"
class="btn float-right <?php if ($archived) {
class="btn float-end <?php if ($archived) {
echo 'btn-primary';
} else {
echo 'btn-default';
} ?>"><i
class="fas fa-fw fa-archive mr-2"></i>Archived</a>
class="fas fa-fw fa-archive me-2"></i>Archived</a>
</div>
</div>
</form>
<hr>
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover">
<table class="table table-striped table-borderless table-hover mb-0">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th>
@@ -165,8 +163,8 @@ $archive_toggle_url = '?' . http_build_query($archive_toggle_query);
</td>
<td><i class="fa fa-3x fa-circle" style="color:<?= $category_color ?>;"></i></td>
<td>
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
<div class="dropdown dropstart text-center">
<button class="btn btn-secondary btn-sm" type="button" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
@@ -175,22 +173,22 @@ $archive_toggle_url = '?' . http_build_query($archive_toggle_query);
?>
<a class="dropdown-item text-info confirm-link"
href="post.php?restore_category=<?= $category_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-redo mr-2"></i>Restore
<i class="fas fa-fw fa-redo me-2"></i>Restore
</a>
<a class="dropdown-item text-danger confirm-link"
href="post.php?delete_category=<?= $category_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
<i class="fas fa-fw fa-trash me-2"></i>Delete
</a>
<?php
} else {
?>
<a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/category/category_edit.php?id=<?= $category_id ?>">
<i class="fas fa-fw fa-edit mr-2"></i>Edit
<i class="fas fa-fw fa-edit me-2"></i>Edit
</a>
<a class="dropdown-item text-danger confirm-link"
href="post.php?archive_category=<?= $category_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-archive mr-2"></i>Archive
<i class="fas fa-fw fa-archive me-2"></i>Archive
</a>
<?php
}
@@ -209,10 +207,10 @@ $archive_toggle_url = '?' . http_build_query($archive_toggle_query);
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php"; ?>
</div>
</div>
</div>
<?php require_once "../includes/filter_footer.php"; ?>
</div>
<?php

View File

@@ -10,7 +10,8 @@ require_once "includes/inc_all_admin.php";
$sql = mysqli_query(
$mysqli,
"SELECT SQL_CALC_FOUND_ROWS * FROM contract_templates
WHERE contract_template_name LIKE '%$q%' OR contract_template_type LIKE '%$q%' OR contract_template_name LIKE '%$q%'
WHERE contract_template_name LIKE '%$q%' OR contract_template_type LIKE '%$q%'
OR contract_template_description LIKE '%$q%'
ORDER BY $sort $order LIMIT $record_from, $record_to"
);
@@ -18,108 +19,142 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="card card-dark">
<div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fa fa-fw fa-file-contract mr-2"></i>Contract Templates</h3>
<div class="card">
<div class="card-header bg-dark py-2">
<h3 class="card-title mt-2"><i class="fa fa-fw fa-file-contract me-2"></i>Contract Templates</h3>
<div class="card-tools">
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/contract_template/contract_template_add.php" data-modal-size="lg">
<i class="fas fa-plus mr-2"></i>New Template
<i class="fas fa-plus me-2"></i>New Template
</button>
</div>
</div>
<div class="card-body">
<div class="card-header py-3">
<form autocomplete="off">
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(escapeHtml($q)); } ?>" placeholder="Search templates">
<div class="input-group-append">
<button class="btn btn-secondary"><i class="fa fa-search"></i></button>
</div>
</div>
</form>
<hr>
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th>Template Name</th>
<th>Type</th>
<th>Update Frequency</th>
<th>SLA (L/M/H Response)</th>
<th>SLA (L/M/H Resolution)</th>
<th>Hourly Rate</th>
<th>After Hours Rate</th>
<th>Support Hours</th>
<th>Net Terms</th>
<th>Created</th>
<th>Updated</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_assoc($sql)) {
$id = intval($row['contract_template_id']);
$name = escapeHtml($row['contract_template_name']);
$type = escapeHtml($row['contract_template_type']);
$freq = escapeHtml($row['contract_template_renewal_frequency']);
$sla_low_resp = escapeHtml($row['contract_template_sla_low_response_time']);
$sla_med_resp = escapeHtml($row['contract_template_sla_medium_response_time']);
$sla_high_resp = escapeHtml($row['contract_template_sla_high_response_time']);
$sla_low_res = escapeHtml($row['contract_template_sla_low_resolution_time']);
$sla_med_res = escapeHtml($row['contract_template_sla_medium_resolution_time']);
$sla_high_res = escapeHtml($row['contract_template_sla_high_resolution_time']);
$hourly_rate = escapeHtml($row['contract_template_rate_standard']);
$after_hours = escapeHtml($row['contract_template_rate_after_hours']);
$support_hours = escapeHtml($row['contract_template_support_hours']);
$net_terms = escapeHtml($row['contract_template_net_terms']);
$created = escapeHtml($row['contract_template_created_at']);
$updated = escapeHtml($row['contract_template_updated_at']);
?>
<tr>
<td>
<a class="text-bold" href="contract_template_details.php?contract_template_id=<?= $id ?>">
<i class="fas fa-fw fa-file-alt text-dark"></i> <?= $name ?>
</a>
<div class="mt-1 text-secondary"><?= escapeHtml($row['contract_template_description']) ?></div>
</td>
<td><?= $type ?></td>
<td><?= $freq ?></td>
<td><?= "$sla_low_resp / $sla_med_resp / $sla_high_resp" ?></td>
<td><?= "$sla_low_res / $sla_med_res / $sla_high_res" ?></td>
<td><?= $hourly_rate ?></td>
<td><?= $after_hours ?></td>
<td><?= $support_hours ?></td>
<td><?= $net_terms ?></td>
<td><?= $created ?></td>
<td><?= $updated ?></td>
<td>
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#"
data-modal-size="xl"
data-modal-url="modals/contract_template/contract_template_edit.php?id=<?= $id ?>">
<i class="fas fa-fw fa-edit mr-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold" href="post.php?delete_contract_template=<?= $id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
</div>
</div>
</td>
</tr>
<?php } ?>
</tbody>
</table>
<br>
</div>
<?php require_once "../includes/filter_footer.php"; ?>
</div>
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover mb-0">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th class="ps-3">
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=contract_template_name&order=<?= $disp ?>">
Template Name <?php if ($sort == 'contract_template_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=contract_template_type&order=<?= $disp ?>">
Type <?php if ($sort == 'contract_template_type') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=contract_template_renewal_frequency&order=<?= $disp ?>">
Update Frequency <?php if ($sort == 'contract_template_renewal_frequency') { echo $order_icon; } ?>
</a>
</th>
<th>SLA (L/M/H Response)</th>
<th>SLA (L/M/H Resolution)</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=contract_template_rate_standard&order=<?= $disp ?>">
Hourly Rate <?php if ($sort == 'contract_template_rate_standard') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=contract_template_rate_after_hours&order=<?= $disp ?>">
After Hours Rate <?php if ($sort == 'contract_template_rate_after_hours') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=contract_template_support_hours&order=<?= $disp ?>">
Support Hours <?php if ($sort == 'contract_template_support_hours') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=contract_template_net_terms&order=<?= $disp ?>">
Net Terms <?php if ($sort == 'contract_template_net_terms') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=contract_template_created_at&order=<?= $disp ?>">
Created <?php if ($sort == 'contract_template_created_at') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=contract_template_updated_at&order=<?= $disp ?>">
Updated <?php if ($sort == 'contract_template_updated_at') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_assoc($sql)) {
$id = intval($row['contract_template_id']);
$name = escapeHtml($row['contract_template_name']);
$type = escapeHtml($row['contract_template_type']);
$freq = escapeHtml($row['contract_template_renewal_frequency']);
$sla_low_resp = escapeHtml($row['contract_template_sla_low_response_time']);
$sla_med_resp = escapeHtml($row['contract_template_sla_medium_response_time']);
$sla_high_resp = escapeHtml($row['contract_template_sla_high_response_time']);
$sla_low_res = escapeHtml($row['contract_template_sla_low_resolution_time']);
$sla_med_res = escapeHtml($row['contract_template_sla_medium_resolution_time']);
$sla_high_res = escapeHtml($row['contract_template_sla_high_resolution_time']);
$hourly_rate = escapeHtml($row['contract_template_rate_standard']);
$after_hours = escapeHtml($row['contract_template_rate_after_hours']);
$support_hours = escapeHtml($row['contract_template_support_hours']);
$net_terms = escapeHtml($row['contract_template_net_terms']);
$created = escapeHtml($row['contract_template_created_at']);
$updated = escapeHtml($row['contract_template_updated_at']);
?>
<tr>
<td class="ps-3">
<a class="text-bold ajax-modal" href="#"
data-modal-size="xl"
data-modal-url="modals/contract_template/contract_template_edit.php?id=<?= $id ?>">
<i class="fas fa-fw fa-file-alt text-dark"></i> <?= $name ?>
</a>
<div class="mt-1 text-secondary"><?= escapeHtml($row['contract_template_description']) ?></div>
</td>
<td><?= $type ?></td>
<td><?= $freq ?></td>
<td><?= "$sla_low_resp / $sla_med_resp / $sla_high_resp" ?></td>
<td><?= "$sla_low_res / $sla_med_res / $sla_high_res" ?></td>
<td><?= $hourly_rate ?></td>
<td><?= $after_hours ?></td>
<td><?= $support_hours ?></td>
<td><?= $net_terms ?></td>
<td><?= $created ?></td>
<td><?= $updated ?></td>
<td>
<div class="dropdown dropstart text-center">
<button class="btn btn-secondary btn-sm" type="button" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#"
data-modal-size="xl"
data-modal-url="modals/contract_template/contract_template_edit.php?id=<?= $id ?>">
<i class="fas fa-fw fa-edit me-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold" href="post.php?delete_contract_template=<?= $id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash me-2"></i>Delete
</a>
</div>
</div>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php"; ?>
</div>
<?php require_once "../includes/footer.php"; ?>

View File

@@ -35,13 +35,13 @@ while ($job_row = mysqli_fetch_assoc($sql)) {
<div class="card card-dark">
<div class="card-header py-3">
<h3 class="card-title"><i class="fas fa-fw fa-clock mr-2"></i>Cron</h3>
<h3 class="card-title"><i class="fas fa-fw fa-clock me-2"></i>Cron</h3>
</div>
<div class="card-body">
<?php if (!$cron_is_running) { ?>
<div class="alert alert-danger">
<h5><i class="fas fa-fw fa-exclamation-triangle mr-2"></i>Cron is not running</h5>
<h5><i class="fas fa-fw fa-exclamation-triangle me-2"></i>Cron is not running</h5>
ITFlow last heard from cron <strong><?= escapeHtml(strtolower(cronJobTimeAgo($cron_last_dispatch_at))) ?></strong>.
Nothing below will run - no mail is being sent, no email is being turned into tickets, and invoices are not being generated.
Add this line to the crontab of the user that owns the ITFlow files:
@@ -49,26 +49,26 @@ while ($job_row = mysqli_fetch_assoc($sql)) {
</div>
<?php } else { ?>
<div class="alert alert-success">
<i class="fas fa-fw fa-check mr-2"></i>Cron last checked in <strong><?= escapeHtml(strtolower(cronJobTimeAgo($cron_last_dispatch_at))) ?></strong>.
<span class="text-muted ml-2"><?= escapeHtml($cron_command) ?></span>
<i class="fas fa-fw fa-check me-2"></i>Cron last checked in <strong><?= escapeHtml(strtolower(cronJobTimeAgo($cron_last_dispatch_at))) ?></strong>.
<span class="text-muted ms-2"><?= escapeHtml($cron_command) ?></span>
</div>
<?php } ?>
<?php if ($config_enable_cron == 0) { ?>
<div class="alert alert-warning">
<div class="float-right">
<div class="float-end">
<a class="btn btn-sm btn-warning" href="post.php?enable_cron=1&amp;csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-power-off mr-2"></i>Turn cron on
<i class="fas fa-fw fa-power-off me-2"></i>Turn cron on
</a>
</div>
<h5><i class="fas fa-fw fa-exclamation-circle mr-2"></i>Cron is switched off</h5>
<h5><i class="fas fa-fw fa-exclamation-circle me-2"></i>Cron is switched off</h5>
The dispatcher is running, but every job below stops itself immediately while this is off -
no mail is sent, no email becomes a ticket, and nothing is invoiced.
</div>
<?php } else { ?>
<p class="text-muted">
<small>
<i class="fas fa-fw fa-power-off mr-1"></i>The master switch is <strong>on</strong>. Turning it off
<i class="fas fa-fw fa-power-off me-1"></i>The master switch is <strong>on</strong>. Turning it off
stops every job at once without touching their schedules, which is what you want on a restored
backup or a staging clone - those come up with every job enabled and will otherwise email clients
and charge cards. Switching back on returns you to exactly this configuration.
@@ -112,24 +112,24 @@ while ($job_row = mysqli_fetch_assoc($sql)) {
$next_run = $job_row ? cronJobNextRun($job_row) : null;
if ($run_now) {
$status_badge = '<span class="badge badge-warning">Queued</span>';
$status_badge = '<span class="badge bg-warning text-dark">Queued</span>';
} elseif ($last_status === 'Running') {
$status_badge = '<span class="badge badge-info">Running</span>';
$status_badge = '<span class="badge bg-info text-dark">Running</span>';
} elseif ($last_status === 'Completed') {
$status_badge = '<span class="badge badge-success">Completed</span>';
$status_badge = '<span class="badge bg-success">Completed</span>';
} elseif ($last_status === 'Failed') {
$status_badge = '<span class="badge badge-danger">Failed</span>';
$status_badge = '<span class="badge bg-danger">Failed</span>';
} elseif ($last_status !== null) {
$status_badge = '<span class="badge badge-secondary">Stopped</span>';
$status_badge = '<span class="badge bg-secondary">Stopped</span>';
} else {
$status_badge = '<span class="badge badge-light">Never run</span>';
$status_badge = '<span class="badge bg-light text-dark">Never run</span>';
}
?>
<tr class="<?= $enabled ? '' : 'text-muted' ?>">
<td>
<strong><?= escapeHtml($job['label']) ?></strong>
<?php if (!$enabled) { ?><span class="badge badge-secondary ml-1">Disabled</span><?php } ?>
<?php if (!$enabled) { ?><span class="badge bg-secondary ms-1">Disabled</span><?php } ?>
<br><small class="text-secondary"><?= escapeHtml($job['description']) ?></small>
<br><small class="text-muted"><code>cron/<?= escapeHtml($job['script']) ?></code></small>
</td>
@@ -147,25 +147,25 @@ while ($job_row = mysqli_fetch_assoc($sql)) {
</td>
<td><?= $next_run === null ? '-' : escapeHtml(cronJobTimeAgo($next_run)) ?></td>
<td class="text-center">
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
<div class="dropdown dropstart text-center">
<button class="btn btn-secondary btn-sm" type="button" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item <?= $cron_job_id === 0 ? 'disabled' : '' ?>" href="post.php?run_cron_job=<?= $cron_job_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-play mr-2"></i>Run Now
<i class="fas fa-fw fa-play me-2"></i>Run Now
</a>
<button class="dropdown-item ajax-modal <?= $cron_job_id === 0 ? 'disabled' : '' ?>" type="button" data-toggle="ajax-modal"
data-modal-url="modals/cron/cron_edit.php?id=<?= $cron_job_id ?>">
<i class="fas fa-fw fa-edit mr-2"></i>Edit Schedule
<i class="fas fa-fw fa-edit me-2"></i>Edit Schedule
</button>
<?php if ($enabled) { ?>
<a class="dropdown-item text-danger" href="post.php?disable_cron_job=<?= $cron_job_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-pause mr-2"></i>Disable
<i class="fas fa-fw fa-pause me-2"></i>Disable
</a>
<?php } else { ?>
<a class="dropdown-item text-success" href="post.php?enable_cron_job=<?= $cron_job_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-play-circle mr-2"></i>Enable
<i class="fas fa-fw fa-play-circle me-2"></i>Enable
</a>
<?php } ?>
</div>
@@ -176,13 +176,13 @@ while ($job_row = mysqli_fetch_assoc($sql)) {
<tr>
<td colspan="7" class="pt-0">
<div class="alert alert-danger mb-0 py-2">
<div class="float-right">
<div class="float-end">
<a class="text-danger" href="post.php?clear_cron_error=<?= $cron_job_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>" title="Dismiss">
<i class="fas fa-fw fa-times"></i>
</a>
</div>
<strong><i class="fas fa-fw fa-exclamation-triangle mr-2"></i>Last error</strong>
<small class="text-muted ml-2"><?= escapeHtml(cronJobTimeAgo($last_error_at)) ?></small>
<strong><i class="fas fa-fw fa-exclamation-triangle me-2"></i>Last error</strong>
<small class="text-muted ms-2"><?= escapeHtml(cronJobTimeAgo($last_error_at)) ?></small>
<div class="mt-1"><small><?= escapeHtml($last_error) ?></small></div>
</div>
</td>
@@ -195,7 +195,7 @@ while ($job_row = mysqli_fetch_assoc($sql)) {
<p class="text-muted mb-0">
<small>
<i class="fas fa-fw fa-info-circle mr-1"></i>Run Now does not start the job in your browser - it asks the
<i class="fas fa-fw fa-info-circle me-1"></i>Run Now does not start the job in your browser - it asks the
dispatcher to pick it up on its next pass, so a job starts within a minute and still runs on the command
line with the same locking as a scheduled run. Detailed per-job output is in
<a href="app_logs.php">App Logs</a>.

View File

@@ -18,132 +18,126 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="card card-dark">
<div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-external-link-alt mr-2"></i>Custom Links</h3>
<div class="card-tools">
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/custom_link/custom_link_add.php"><i class="fas fa-plus mr-2"></i>New Link</button>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-sm-4 mb-2">
<form autocomplete="off">
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(escapeHtml($q)); } ?>" placeholder="Search Links">
<div class="input-group-append">
<button class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</form>
</div>
<div class="col-sm-8">
</div>
</div>
<hr>
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=custom_link_name&order=<?= $disp ?>">
Name <?php if ($sort == 'custom_link_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=custom_link_order&order=<?= $disp ?>">
Order <?php if ($sort == 'custom_link_order') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=custom_link_uri&order=<?= $disp ?>">
URI / <span class="text-secondary">New Tab</span> <?php if ($sort == 'custom_link_uri') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=custom_link_location&order=<?= $disp ?>">
Location <?php if ($sort == 'custom_link_location') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_assoc($sql)) {
$custom_link_id = intval($row['custom_link_id']);
$custom_link_name = escapeHtml($row['custom_link_name']);
$custom_link_uri = escapeHtml($row['custom_link_uri']);
$custom_link_icon = escapeHtml($row['custom_link_icon']);
$custom_link_new_tab = intval($row['custom_link_new_tab']);
if ($custom_link_new_tab == 1 ) {
$custom_link_new_tab_display = "<i class='fas fa-fw fa-checkmark'></i>";
} else {
$custom_link_new_tab_display = "";
}
$custom_link_order = intval($row['custom_link_order']);
if ($custom_link_order == 0 ) {
$custom_link_order_display = "-";
} else {
$custom_link_order_display = $custom_link_order;
}
$custom_link_location = intval($row['custom_link_location']);
if ($custom_link_location == 1) {
$custom_link_location_display = "Main Side Nav";
} elseif ($custom_link_location == 2) {
$custom_link_location_display = "Top Nav";
} elseif ($custom_link_location == 3) {
$custom_link_location_display = "Client Portal Nav";
} elseif ($custom_link_location == 4) {
$custom_link_location_display = "Admin Nav";
} elseif ($custom_link_location == 5) {
$custom_link_location_display = "Reports Nav";
}
?>
<tr>
<td>
<a class="ajax-modal" href="#"
data-modal-url="modals/custom_link/custom_link_edit.php?id=<?= $custom_link_id ?>">
<i class="fa fa-fw fa-<?= $custom_link_icon ?> mr-2"></i><?= $custom_link_name ?>
</a>
</td>
<td><?= $custom_link_order_display ?></td>
<td><?= "$custom_link_uri $custom_link_new_tab_display" ?></td>
<td><?= $custom_link_location_display ?></td>
<td>
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/custom_link/custom_link_edit.php?id=<?= $custom_link_id ?>">
<i class="fas fa-fw fa-edit mr-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_custom_link=<?= $custom_link_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
</div>
</div>
</td>
</tr>
<?php
}
?>
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php";
?>
<div class="card">
<div class="card-header bg-dark py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-external-link-alt me-2"></i>Custom Links</h3>
<div class="card-tools">
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/custom_link/custom_link_add.php"><i class="fas fa-plus me-2"></i>New Link</button>
</div>
</div>
<div class="card-header py-3">
<form autocomplete="off">
<div class="row g-2 align-items-center">
<div class="col-md-4">
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(escapeHtml($q)); } ?>" placeholder="Search Links">
<button class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
</form>
</div>
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover mb-0">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th class="ps-3">
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=custom_link_name&order=<?= $disp ?>">
Name <?php if ($sort == 'custom_link_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=custom_link_order&order=<?= $disp ?>">
Order <?php if ($sort == 'custom_link_order') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=custom_link_uri&order=<?= $disp ?>">
URI / <span class="text-secondary">New Tab</span> <?php if ($sort == 'custom_link_uri') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=custom_link_location&order=<?= $disp ?>">
Location <?php if ($sort == 'custom_link_location') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_assoc($sql)) {
$custom_link_id = intval($row['custom_link_id']);
$custom_link_name = escapeHtml($row['custom_link_name']);
$custom_link_uri = escapeHtml($row['custom_link_uri']);
$custom_link_icon = escapeHtml($row['custom_link_icon']);
$custom_link_new_tab = intval($row['custom_link_new_tab']);
if ($custom_link_new_tab == 1 ) {
$custom_link_new_tab_display = "<i class='fas fa-fw fa-checkmark'></i>";
} else {
$custom_link_new_tab_display = "";
}
$custom_link_order = intval($row['custom_link_order']);
if ($custom_link_order == 0 ) {
$custom_link_order_display = "-";
} else {
$custom_link_order_display = $custom_link_order;
}
$custom_link_location = intval($row['custom_link_location']);
if ($custom_link_location == 1) {
$custom_link_location_display = "Main Side Nav";
} elseif ($custom_link_location == 2) {
$custom_link_location_display = "Top Nav";
} elseif ($custom_link_location == 3) {
$custom_link_location_display = "Client Portal Nav";
} elseif ($custom_link_location == 4) {
$custom_link_location_display = "Admin Nav";
} elseif ($custom_link_location == 5) {
$custom_link_location_display = "Reports Nav";
}
?>
<tr>
<td class="ps-3">
<a class="ajax-modal" href="#"
data-modal-url="modals/custom_link/custom_link_edit.php?id=<?= $custom_link_id ?>">
<i class="fa fa-fw fa-<?= $custom_link_icon ?> me-2"></i><?= $custom_link_name ?>
</a>
</td>
<td><?= $custom_link_order_display ?></td>
<td><?= "$custom_link_uri $custom_link_new_tab_display" ?></td>
<td><?= $custom_link_location_display ?></td>
<td>
<div class="dropdown dropstart text-center">
<button class="btn btn-secondary btn-sm" type="button" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/custom_link/custom_link_edit.php?id=<?= $custom_link_id ?>">
<i class="fas fa-fw fa-edit me-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_custom_link=<?= $custom_link_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash me-2"></i>Delete
</a>
</div>
</div>
</td>
</tr>
<?php
}
?>
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php"; ?>
</div>
<?php
require_once "../includes/footer.php";

View File

@@ -18,7 +18,7 @@
*/
// Check if our database versions are defined
// If undefined, the file is probably being accessed directly rather than called via post.php?update_db or update_cli.php
// If undefined, the file is probably being accessed directly rather than called via update_cli.php
if (!defined("LATEST_DATABASE_VERSION") || !defined("CURRENT_DATABASE_VERSION") || !isset($mysqli)) {
echo "Cannot access this file directly.";
exit();
@@ -27,7 +27,7 @@ if (!defined("LATEST_DATABASE_VERSION") || !defined("CURRENT_DATABASE_VERSION")
// Migration files include-guard against this constant
define("FROM_DB_UPDATER", true);
// Outputs for the caller (post/update.php, update_cli.php)
// Outputs for the caller (update_cli.php)
$database_updates_applied = []; // Versions successfully applied this run
$database_updates_error = null; // "version: error message" if a migration failed

View File

@@ -0,0 +1,20 @@
<?php
/*
* ITFlow - Database update to version 2.6.8 (from 2.6.7)
* Included by admin/database_updates.php - do not access directly
*/
defined('FROM_DB_UPDATER') || die("Direct file access is not allowed");
// Maintenance > Update can now hand an update to cron rather than running it inside the
// request: the page stamps this column and cron/app_update.php takes it and runs
// scripts/update_cli.php in its own process. NULL means nothing is queued, which is why
// the job cannot update an install that did not ask for one.
mysqli_query($mysqli, "ALTER TABLE `settings` ADD COLUMN IF NOT EXISTS `config_update_queued_at` datetime DEFAULT NULL");
// Seeded here as well as by the dispatcher, which only creates rows on its next pass -
// without this, an update queued in the minutes after an upgrade would have no row to
// set cron_job_run_now on and would wait for that pass instead of starting.
mysqli_query($mysqli, "INSERT IGNORE INTO cron_jobs SET cron_job_name = 'app_update', cron_job_enabled = 0, cron_job_schedule = 'Daily', cron_job_daily_at = '05:00'");

View File

@@ -0,0 +1,27 @@
<?php
/*
* ITFlow - Database update to version 2.6.9 (from 2.6.8)
* Included by admin/database_updates.php - do not access directly
*/
defined('FROM_DB_UPDATER') || die("Direct file access is not allowed");
// Canned responses for ticket replies, managed from Admin > Canned Responses and
// picked from a dropdown on the ticket reply form.
//
// canned_response_category_id is a ticket category (categories.category_id where
// category_type = 'Ticket'), or 0 for one that offers itself on every ticket
// whatever its category.
mysqli_query($mysqli, "CREATE TABLE IF NOT EXISTS `canned_responses` (
`canned_response_id` int(11) NOT NULL AUTO_INCREMENT,
`canned_response_name` varchar(200) NOT NULL,
`canned_response_body` longtext DEFAULT NULL,
`canned_response_category_id` int(11) NOT NULL DEFAULT 0,
`canned_response_created_at` datetime NOT NULL DEFAULT current_timestamp(),
`canned_response_updated_at` datetime DEFAULT NULL ON UPDATE current_timestamp(),
`canned_response_archived_at` datetime DEFAULT NULL,
PRIMARY KEY (`canned_response_id`),
KEY `canned_response_category_id` (`canned_response_category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci");

View File

@@ -0,0 +1,39 @@
<?php
/*
* ITFlow - Database update to version 2.7.0 (from 2.6.9)
* Included by admin/database_updates.php - do not access directly
*/
defined('FROM_DB_UPDATER') || die("Direct file access is not allowed");
// The five built-in ticket statuses now have fixed SLA clock behaviour, matching
// getTicketStatusSlaLock() in functions/app.php: On Hold, Resolved and Closed
// pause the resolution clock, New and Open run it. Admin > Ticket Statuses no
// longer offers the choice on these five - only on custom statuses.
//
// On Hold shipped with the column defaulting to 0, so a stock install breached
// the resolution SLA on tickets parked waiting for a client or a vendor. Every
// install had to find the toggle themselves; this sets it for them.
//
// Resolved and Closed are set for consistency across the SLA surfaces, not to
// change behaviour - both already stopped the clocks via ticket_resolved_at and
// ticket_closed_at, which is what the cron and syncTicketSlaClock() actually read.
mysqli_query($mysqli, "UPDATE ticket_statuses SET ticket_status_pauses_sla = 1 WHERE ticket_status_id IN (3, 4, 5)");
mysqli_query($mysqli, "UPDATE ticket_statuses SET ticket_status_pauses_sla = 0 WHERE ticket_status_id IN (1, 2)");
/*
* Tickets already parked in On Hold are still holding an open sla_history
* interval, so their paused time would keep counting as consumed. Closing those
* intervals needs business-hours maths in the app timezone, and this file also
* runs from scripts/update_cli.php, which sets neither - so the first
* cron/ticket_sla.php run after the upgrade reconciles them instead, where the
* timezone is set. Nothing is lost by waiting a minute: the same cron no longer
* warns or breaches a paused ticket the moment the flag above lands.
*
* Breach verdicts already recorded against on-hold tickets are deliberately left
* alone. They were recorded by a clock that really was running under the old
* rules, and rewriting a stored miss is exactly what the reopen-integrity rules
* in functions/sla.php exist to prevent. Re-stamp an individual ticket (change
* its priority or its SLA) if you want it re-judged.
*/

View File

@@ -0,0 +1,17 @@
<?php
/*
* ITFlow - Database update to version 2.7.1 (from 2.7.0)
* Included by admin/database_updates.php - do not access directly
*/
defined('FROM_DB_UPDATER') || die("Direct file access is not allowed");
// Which client record, if any, is the MSP's own company. Most installs create one to
// hold their internal tickets, documentation, assets and credentials, but nothing in
// the app knew that record was special. Admin > Settings > Company Details sets it.
//
// 0 means no client is designated, which is every existing install - nothing changes
// until an administrator chooses one.
mysqli_query($mysqli, "ALTER TABLE `settings`
ADD COLUMN `config_internal_client_id` int(11) NOT NULL DEFAULT 0");

View File

@@ -0,0 +1,27 @@
<?php
/*
* ITFlow - Database update to version 2.7.2 (from 2.7.1)
* Included by admin/database_updates.php - do not access directly
*/
defined('FROM_DB_UPDATER') || die("Direct file access is not allowed");
// Documented IP addresses, hanging off the network (subnet) they belong to.
// Addresses are stored canonicalised (inet_ntop form) so the UNIQUE key
// below actually catches the same address entered two different ways.
//
// The foreign key means deleting a network takes its addresses with it -
// agent/post/network.php's delete handler needs no change.
mysqli_query($mysqli, "CREATE TABLE IF NOT EXISTS `network_ips` (
`ip_id` int(11) NOT NULL AUTO_INCREMENT,
`ip_address` varchar(45) NOT NULL,
`ip_hostname` varchar(200) DEFAULT NULL,
`ip_description` varchar(200) DEFAULT NULL,
`ip_created_at` datetime NOT NULL DEFAULT current_timestamp(),
`ip_updated_at` datetime DEFAULT NULL ON UPDATE current_timestamp(),
`ip_network_id` int(11) NOT NULL,
PRIMARY KEY (`ip_id`),
UNIQUE KEY `ip_network_address` (`ip_network_id`,`ip_address`),
CONSTRAINT `network_ips_ibfk_1` FOREIGN KEY (`ip_network_id`) REFERENCES `networks` (`network_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci");

View File

@@ -0,0 +1,26 @@
<?php
/*
* ITFlow - Database update to version 2.7.3 (from 2.7.2)
* Included by admin/database_updates.php - do not access directly
*/
defined('FROM_DB_UPDATER') || die("Direct file access is not allowed");
// Maintenance > Update no longer runs git itself. cron/update_check.php does the fetch
// on its own schedule and parks the answer here; the page and the nightly notification
// both read these rather than shelling out in a web request.
//
// config_update_checked_at is written only after a check SUCCEEDS, so "last checked"
// never claims freshness for a reading that a failed fetch left stale. The cron_jobs row
// holds the attempt time and the error.
mysqli_query($mysqli, "ALTER TABLE `settings`
ADD COLUMN IF NOT EXISTS `config_update_latest_commit` varchar(40) DEFAULT NULL,
ADD COLUMN IF NOT EXISTS `config_update_pending_commits` text DEFAULT NULL,
ADD COLUMN IF NOT EXISTS `config_update_checked_at` datetime DEFAULT NULL");
// Seeded here as well as by the dispatcher, which only creates rows on its next pass -
// without this, a Check Now pressed in the minutes after an upgrade would have no row to
// set cron_job_run_now on and would wait for that pass instead of starting.
mysqli_query($mysqli, "INSERT IGNORE INTO cron_jobs SET cron_job_name = 'update_check', cron_job_enabled = 1, cron_job_schedule = 'Daily', cron_job_daily_at = '02:30'");

View File

@@ -0,0 +1,26 @@
<?php
/*
* ITFlow - Database update to version 2.7.4 (from 2.7.3)
* Included by admin/database_updates.php - do not access directly
*/
defined('FROM_DB_UPDATER') || die("Direct file access is not allowed");
// Closure days for the SLA business calendar. A date listed here yields no
// business minutes at all, exactly like a weekday that is not a business
// day - holidays are a calendar concept, not a per-ticket pause, so they
// never touch sla_history.
//
// holiday_date is UNIQUE so the US federal holiday generator can be run
// repeatedly (and over a year that was partly entered by hand) without
// creating duplicates.
mysqli_query($mysqli, "CREATE TABLE IF NOT EXISTS `business_holidays` (
`holiday_id` int(11) NOT NULL AUTO_INCREMENT,
`holiday_date` date NOT NULL,
`holiday_name` varchar(200) NOT NULL,
`holiday_created_at` datetime NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`holiday_id`),
UNIQUE KEY `holiday_date` (`holiday_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci");

View File

@@ -0,0 +1,88 @@
<?php
/*
* ITFlow - Database update to version 2.7.5 (from 2.7.4)
* Included by admin/database_updates.php - do not access directly
*/
defined('FROM_DB_UPDATER') || die("Direct file access is not allowed");
// Index the client-scoped lookups.
//
// Every one of these columns was unindexed, which is why the client
// context was slow: agent/includes/inc_all_client.php runs a count per
// table to fill in the side nav badges, and with no index each one is a
// full table scan before the page renders a byte. The same columns are
// what clientScopeSql() filters on, so every client-scoped list page in
// the app was paying for it too.
//
// Shape: (<entity>_client_id, <entity>_archived_at) where the query pairs
// the two, otherwise the client column alone. The archived column is
// deliberately the TRAILING part and never an index of its own - on a
// healthy instance almost every row has it NULL, so it is not selective
// enough to lead and an index on it alone would be ignored.
//
// Tables whose queries carry no archived filter get a single-column index:
// recurring_tickets, services, invoices, recurring_invoices,
// calendar_events. invoices is in that group because the money total there
// spans archived rows as well, so the scan cannot be narrowed by it.
//
// payments.payment_invoice_id and transfers.transfer_revenue_id are join
// keys rather than client scoping, but they are on the same page's
// critical path (amount paid, and the income count's transfer exclusion).
//
// InnoDB builds these online, but on a mature instance this is the slowest
// step of the upgrade - it is proportional to table size, not row count
// returned.
$itflow_indexes = [
['contacts', 'contact_client_id', ['contact_client_id', 'contact_archived_at']],
['locations', 'location_client_id', ['location_client_id', 'location_archived_at']],
['assets', 'asset_client_id', ['asset_client_id', 'asset_archived_at']],
['tickets', 'ticket_client_id', ['ticket_client_id', 'ticket_archived_at']],
['recurring_tickets', 'recurring_ticket_client_id', ['recurring_ticket_client_id']],
['projects', 'project_client_id', ['project_client_id', 'project_archived_at']],
['services', 'service_client_id', ['service_client_id']],
['vendors', 'vendor_client_id', ['vendor_client_id', 'vendor_archived_at']],
['credentials', 'credential_client_id', ['credential_client_id', 'credential_archived_at']],
['networks', 'network_client_id', ['network_client_id', 'network_archived_at']],
['racks', 'rack_client_id', ['rack_client_id', 'rack_archived_at']],
['domains', 'domain_client_id', ['domain_client_id', 'domain_archived_at']],
['certificates', 'certificate_client_id', ['certificate_client_id', 'certificate_archived_at']],
['software', 'software_client_id', ['software_client_id', 'software_archived_at']],
['invoices', 'invoice_client_id', ['invoice_client_id']],
['quotes', 'quote_client_id', ['quote_client_id', 'quote_archived_at']],
['recurring_invoices', 'recurring_invoice_client_id', ['recurring_invoice_client_id']],
['revenues', 'revenue_client_id', ['revenue_client_id', 'revenue_archived_at']],
['files', 'file_client_id', ['file_client_id', 'file_archived_at']],
['documents', 'document_client_id', ['document_client_id', 'document_archived_at']],
['calendar_events', 'event_client_id', ['event_client_id']],
['trips', 'trip_client_id', ['trip_client_id', 'trip_archived_at']],
['payments', 'payment_invoice_id', ['payment_invoice_id']],
['transfers', 'transfer_revenue_id', ['transfer_revenue_id']],
];
foreach ($itflow_indexes as $itflow_index) {
list($itflow_index_table, $itflow_index_name, $itflow_index_columns) = $itflow_index;
// MySQL has no ADD INDEX IF NOT EXISTS, and re-adding one is an error
// rather than a no-op, so check first. This also makes the migration
// safe on an instance where someone added the index by hand.
$itflow_index_exists = mysqli_query($mysqli, "SELECT 1 FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '$itflow_index_table'
AND INDEX_NAME = '$itflow_index_name'
LIMIT 1");
if ($itflow_index_exists && mysqli_num_rows($itflow_index_exists) > 0) {
continue;
}
$itflow_index_column_list = '`' . implode('`, `', $itflow_index_columns) . '`';
mysqli_query($mysqli, "ALTER TABLE `$itflow_index_table`
ADD KEY `$itflow_index_name` ($itflow_index_column_list)");
}
unset($itflow_indexes, $itflow_index, $itflow_index_table, $itflow_index_name,
$itflow_index_columns, $itflow_index_exists, $itflow_index_column_list);

View File

@@ -0,0 +1,81 @@
<?php
/*
* ITFlow - Database update to version 2.7.6 (from 2.7.5)
* Included by admin/database_updates.php - do not access directly
*/
defined('FROM_DB_UPDATER') || die("Direct file access is not allowed");
// Second indexing pass. 2.7.5 covered client scoping - the columns every
// list page and the client side nav filter on. This one covers the other
// two shapes that were still scanning whole tables:
//
// 1. "give me the child rows of this one parent" - the queries a detail
// page fires. Opening a ticket cost three full table scans
// (ticket_history, tasks, ticket_watchers) and opening an invoice cost
// two (invoice_items, history). ticket_replies and sla_history were
// already indexed by ticket, so this finishes that set.
//
// 2. the worker loop. cron/mail_queue.php runs
// WHERE email_status = 0 AND email_queued_at <= NOW() every minute -
// 1440 scans a day of a table that keeps sent rows for 90 days.
//
// Same shape rule as 2.7.5: the selective column leads and anything
// low-cardinality trails. email_status leads because pending is a small
// slice of a queue that is almost entirely sent rows, and
// notification_dismissed_at trails because almost every live row is NULL.
//
// history gets two single-column indexes rather than one composite: the
// table serves invoices and quotes from the same rows, and the two detail
// pages each filter on their own column alone.
//
// NOT included, deliberately, and the reasoning is worth keeping:
// - categories.category_type and tags.tag_type look like the busiest
// columns in the codebase, but both tables hold a few dozen rows. A
// scan in the buffer pool beats an index lookup.
// - ticket_status / invoice_status have five or six distinct values, so
// the optimizer scans regardless.
// - the join keys on the big side of a join (tickets.ticket_status and
// friends) never get looked up - the lookup lands on the small table's
// primary key.
// - payments/expenses/revenues .*_account_id are real but serve one
// screen, so they are not worth the write amplification yet.
$itflow_indexes = [
['email_queue', 'email_status', ['email_status', 'email_queued_at']],
['ticket_history', 'ticket_history_ticket_id', ['ticket_history_ticket_id']],
['tasks', 'task_ticket_id', ['task_ticket_id']],
['ticket_watchers', 'watcher_ticket_id', ['watcher_ticket_id']],
['invoice_items', 'item_invoice_id', ['item_invoice_id']],
['quote_items', 'item_quote_id', ['item_quote_id']],
['history', 'history_invoice_id', ['history_invoice_id']],
['history', 'history_quote_id', ['history_quote_id']],
['notifications', 'notification_user_id', ['notification_user_id', 'notification_dismissed_at']],
['records', 'record_domain_id', ['record_domain_id']],
];
foreach ($itflow_indexes as $itflow_index) {
list($itflow_index_table, $itflow_index_name, $itflow_index_columns) = $itflow_index;
// MySQL has no ADD INDEX IF NOT EXISTS, and re-adding one is an error
// rather than a no-op, so check first. This also makes the migration
// safe on an instance where someone added the index by hand.
$itflow_index_exists = mysqli_query($mysqli, "SELECT 1 FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '$itflow_index_table'
AND INDEX_NAME = '$itflow_index_name'
LIMIT 1");
if ($itflow_index_exists && mysqli_num_rows($itflow_index_exists) > 0) {
continue;
}
$itflow_index_column_list = '`' . implode('`, `', $itflow_index_columns) . '`';
mysqli_query($mysqli, "ALTER TABLE `$itflow_index_table`
ADD KEY `$itflow_index_name` ($itflow_index_column_list)");
}
unset($itflow_indexes, $itflow_index, $itflow_index_table, $itflow_index_name,
$itflow_index_columns, $itflow_index_exists, $itflow_index_column_list);

View File

@@ -0,0 +1,20 @@
<?php
/*
* ITFlow - Database update to version 2.7.7 (from 2.7.6)
* Included by admin/database_updates.php - do not access directly
*/
defined('FROM_DB_UPDATER') || die("Direct file access is not allowed");
// history_description was varchar(200). That was enough while every entry
// was a fixed sentence ("Invoice created by <user>"), but the send-email
// and mark-sent flows now record who the document actually went to - a
// "Name <email>" pair per recipient - and the mark-sent note the agent
// types. Five billing contacts alone blows past 200 characters, and under
// strict mode an over-long value is an error, not a truncation, so the
// history row would be lost and the send would 500.
//
// text rather than a bigger varchar: there is no index on this column and
// no length worth defending, so a cap would only be a future bug.
mysqli_query($mysqli, "ALTER TABLE `history` MODIFY `history_description` text NOT NULL");

View File

@@ -0,0 +1,38 @@
<?php
/*
* ITFlow - Database update to version 2.7.8 (from 2.7.7)
* Included by admin/database_updates.php - do not access directly
*/
defined('FROM_DB_UPDATER') || die("Direct file access is not allowed");
// The logs table only ever had KEY log_created_at, which is the column the
// one page that reads it does NOT filter on usefully. Two shapes now do:
//
// 1. The client portal. profile.php shows a contact their recent sign-ins
// and recent actions, and activity.php lists the lot - all filtered on
// log_user_id + log_client_id. That is two full scans on every profile
// view of a table that grows forever and is never pruned.
//
// 2. admin/audit_logs.php, which filters a date range. It had an index
// available the whole time and could not use it, because wrapping the
// column in DATE() makes the comparison non-sargable. That query is
// rewritten as a half-open range in the same commit as this migration,
// so KEY log_created_at finally does its job.
//
// log_user_id leads the composite: a single user is a small slice of the
// table, whereas one client can account for most of it on a single-client
// install. Same selectivity rule as the 2.7.5 and 2.7.6 passes.
$itflow_index_exists = mysqli_query($mysqli, "SELECT 1 FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'logs'
AND INDEX_NAME = 'log_user_id'
LIMIT 1");
if (!$itflow_index_exists || mysqli_num_rows($itflow_index_exists) === 0) {
mysqli_query($mysqli, "ALTER TABLE `logs` ADD KEY `log_user_id` (`log_user_id`, `log_client_id`)");
}
unset($itflow_index_exists);

View File

@@ -6,11 +6,10 @@ require_once "../config.php";
$checks = [];
// Execute the git command to get the latest commit hash
$commitHash = shell_exec('git log -1 --format=%H');
// Read straight out of .git - no shell needed, and it still answers on a hardened host
$commitHash = gitCurrentCommit();
// Get branch info
$gitBranch = shell_exec('git rev-parse --abbrev-ref HEAD');
$gitBranch = gitCurrentBranch();
// Section: System Information
$systemInfo = [];
@@ -114,12 +113,12 @@ $phpConfig[] = [
'value' => $post_max_size,
];
// PHP Memory Limit >= 128M
// PHP Memory Limit >= 512M
$memoryLimit = ini_get('memory_limit');
$memoryLimitBytes = toBytes($memoryLimit);
$memoryLimitPassed = $memoryLimitBytes >= (128 * 1024 * 1024);
$memoryLimitPassed = $memoryLimitBytes >= (512 * 1024 * 1024);
$phpConfig[] = [
'name' => 'PHP Memory Limit >= 128M',
'name' => 'PHP Memory Limit >= 512M',
'passed' => $memoryLimitPassed,
'value' => $memoryLimit,
];
@@ -146,27 +145,15 @@ $phpConfig[] = [
// Section: Shell Commands
$shellCommands = [];
if ($shell_exec_enabled) {
$commands = ['git'];
foreach ($commands as $command) {
$which = trim(shell_exec("which $command 2>/dev/null"));
$exists = !empty($which);
$shellCommands[] = [
'name' => "Command '$command' available",
'passed' => $exists,
'value' => $exists ? $which : 'Not Found',
];
}
} else {
// If shell_exec is disabled, mark commands as unavailable
foreach (['git'] as $command) {
$shellCommands[] = [
'name' => "Command '$command' available",
'passed' => false,
'value' => 'shell_exec Disabled',
];
}
// Located by walking PATH rather than by running `which`, so this reports the truth on a
// host with shell_exec disabled instead of reporting the host's php.ini back at itself
foreach (['git'] as $command) {
$path = commandPath($command);
$shellCommands[] = [
'name' => "Command '$command' available",
'passed' => $path !== '',
'value' => $path !== '' ? $path : 'Not Found',
];
}
// Section: SSL Checks
@@ -508,7 +495,7 @@ $mysqli->close();
<div class="card card-dark">
<div class="card-header py-3">
<h3 class="card-title"><i class="fas fa-fw fa-bug mr-2"></i>Debug</h3>
<h3 class="card-title"><i class="fas fa-fw fa-bug me-2"></i>Debug</h3>
</div>
<div class="card-body">
@@ -531,11 +518,11 @@ $mysqli->close();
</tr>
<tr>
<td>Current Code Commit</td>
<td><?= $commitHash ?></td>
<td><?= $commitHash === '' ? 'Not a git checkout' : escapeHtml($commitHash) ?></td>
</tr>
<tr>
<td>Current Branch</td>
<td><?= $gitBranch ?></td>
<td><?= $gitBranch === '' ? 'Not a git checkout' : escapeHtml($gitBranch) ?></td>
</tr>
</table>
</div>

View File

@@ -44,19 +44,19 @@ $document_template_updated_at = escapeHtml($row['document_template_updated_at'])
<li class="breadcrumb-item">
<a href="document_templates.php">Document Templates</a>
</li>
<li class="breadcrumb-item active"><i class="fas fa-file-alt mr-2"></i><?= $document_template_name ?></li>
<li class="breadcrumb-item active"><i class="fas fa-file-alt me-2"></i><?= $document_template_name ?></li>
</ol>
<div class="card card-dark">
<div class="card-header">
<h3 class="card-title mt-1"><i class="fa fa-fw fa-file-alt mr-2"></i><?= $document_template_name ?></h3>
<h3 class="card-title mt-1"><i class="fa fa-fw fa-file-alt me-2"></i><?= $document_template_name ?></h3>
<div class="card-tools">
<button type="button" class="btn btn-tool ajax-modal"
data-modal-size="xl"
data-modal-url="modals/document_template/document_template_edit.php?id=<?= $document_template_id ?>">
<i class="fas fa-edit mr-2"></i>
<i class="fas fa-edit me-2"></i>
</button>
</div>
</div>

View File

@@ -19,120 +19,117 @@
?>
<div class="card card-dark">
<div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fa fa-fw fa-file-alt mr-2"></i>Document Templates</h3>
<div class="card">
<div class="card-header bg-dark py-2">
<h3 class="card-title mt-2"><i class="fa fa-fw fa-file-alt me-2"></i>Document Templates</h3>
<div class="card-tools">
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/document_template/document_template_add.php" data-modal-size="xl">
<i class="fas fa-plus mr-2"></i>New Template
<i class="fas fa-plus me-2"></i>New Template
</button>
</div>
</div>
<div class="card-body">
<div class="card-header py-3">
<form autocomplete="off">
<div class="input-group">
<input type="search" class="form-control " name="q" value="<?php if (isset($q)) { echo stripslashes(escapeHtml($q)); } ?>" placeholder="Search templates">
<div class="input-group-append">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(escapeHtml($q)); } ?>" placeholder="Search templates">
<button class="btn btn-secondary"><i class="fa fa-search"></i></button>
</div>
</div>
</form>
<hr>
</div>
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th>
<a class="text-secondary" href="?<?= $url_query_strings_sort ?>&sort=document_template_name&order=<?= $disp ?>">
Template Name <?php if ($sort == 'document_template_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-secondary" href="?<?= $url_query_strings_sort ?>&sort=document_template_created_at&order=<?= $disp ?>">
Created <?php if ($sort == 'document_template_created_at') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-secondary" href="?<?= $url_query_strings_sort ?>&sort=document_template_updated_at&order=<?= $disp ?>">
Updated <?php if ($sort == 'document_template_updated_at') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">
Action
</th>
</tr>
</thead>
<tbody>
<?php
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover mb-0">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th class="ps-3">
<a class="text-secondary" href="?<?= $url_query_strings_sort ?>&sort=document_template_name&order=<?= $disp ?>">
Template Name <?php if ($sort == 'document_template_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-secondary" href="?<?= $url_query_strings_sort ?>&sort=document_template_created_at&order=<?= $disp ?>">
Created <?php if ($sort == 'document_template_created_at') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-secondary" href="?<?= $url_query_strings_sort ?>&sort=document_template_updated_at&order=<?= $disp ?>">
Updated <?php if ($sort == 'document_template_updated_at') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">
Action
</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_assoc($sql)) {
$document_template_id = intval($row['document_template_id']);
$document_template_name = escapeHtml($row['document_template_name']);
$document_template_description = escapeHtml($row['document_template_description']);
$document_template_content = escapeHtml($row['document_template_content']);
$document_template_created_by_name = escapeHtml($row['user_name']);
$document_template_created_at = escapeHtml($row['document_template_created_at']);
$document_template_updated_at = escapeHtml($row['document_template_updated_at']) ?: '-';
while ($row = mysqli_fetch_assoc($sql)) {
$document_template_id = intval($row['document_template_id']);
$document_template_name = escapeHtml($row['document_template_name']);
$document_template_description = escapeHtml($row['document_template_description']);
$document_template_content = escapeHtml($row['document_template_content']);
$document_template_created_by_name = escapeHtml($row['user_name']);
$document_template_created_at = escapeHtml($row['document_template_created_at']);
$document_template_updated_at = escapeHtml($row['document_template_updated_at']) ?: '-';
?>
?>
<tr>
<td>
<a class="text-dark ajax-modal" href="#"
data-modal-size="xl"
data-modal-url="modals/document_template/document_template_edit.php?id=<?= $document_template_id ?>">
<div class="media">
<i class="fas fa-fw fa-2x fa-file-alt mr-2"></i>
<div class="media-body">
<div><?= $document_template_name ?></div>
<div><small class="text-secondary"><?= $document_template_description ?></small></div>
</div>
</div>
</a>
</td>
<td>
<?= $document_template_created_at ?>
<div class="text-secondary"><?= $document_template_created_by_name ?></div>
</td>
<td><?= $document_template_updated_at ?></td>
<td>
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="document_template.php?document_template_id=<?= $document_template_id ?>">
<i class="fas fa-fw fa-eye mr-2"></i>View
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item ajax-modal" href="#"
data-modal-size="xl"
data-modal-url="modals/document_template/document_template_edit.php?id=<?= $document_template_id ?>">
<i class="fas fa-fw fa-edit mr-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold" href="post.php?delete_document_template=<?= $document_template_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
<tr>
<td class="ps-3">
<a class="text-dark ajax-modal" href="#"
data-modal-size="xl"
data-modal-url="modals/document_template/document_template_edit.php?id=<?= $document_template_id ?>">
<div class="d-flex">
<i class="fas fa-fw fa-2x fa-file-alt me-2"></i>
<div class="flex-grow-1">
<div><?= $document_template_name ?></div>
<div><small class="text-secondary"><?= $document_template_description ?></small></div>
</div>
</div>
</td>
</tr>
</a>
</td>
<td>
<?= $document_template_created_at ?>
<div class="text-secondary"><?= $document_template_created_by_name ?></div>
</td>
<td><?= $document_template_updated_at ?></td>
<td>
<div class="dropdown dropstart text-center">
<button class="btn btn-secondary btn-sm" type="button" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="document_template.php?document_template_id=<?= $document_template_id ?>">
<i class="fas fa-fw fa-eye me-2"></i>View
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item ajax-modal" href="#"
data-modal-size="xl"
data-modal-url="modals/document_template/document_template_edit.php?id=<?= $document_template_id ?>">
<i class="fas fa-fw fa-edit me-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold" href="post.php?delete_document_template=<?= $document_template_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash me-2"></i>Delete
</a>
</div>
</div>
</td>
</tr>
<?php
<?php
}
}
?>
?>
</tbody>
</table>
<br>
</div>
<?php require_once "../includes/filter_footer.php"; ?>
</tbody>
</table>
<br>
</div>
<?php require_once "../includes/filter_footer.php"; ?>
</div>
<?php require_once "../includes/footer.php";

View File

@@ -4,7 +4,7 @@ require_once "includes/inc_all_admin.php";
<div class="card card-dark">
<div class="card-header py-3">
<h3 class="card-title"><i class="fas fa-fw fa-fingerprint mr-2"></i>Identity Providers</h3>
<h3 class="card-title"><i class="fas fa-fw fa-fingerprint me-2"></i>Identity Providers</h3>
</div>
<div class="card-body">
<form action="post.php" method="post" autocomplete="off">
@@ -12,13 +12,11 @@ require_once "includes/inc_all_admin.php";
<h4>Client Portal SSO via Microsoft Entra</h4>
<div class="form-group">
<div class="mb-3">
<label>Identity Provider <small class='text-secondary'>(Currently only works with Microsoft Entra ID/AAD)</small></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-fingerprint"></i></span>
</div>
<select class="form-control select2" readonly>
<select class="form-select select2" readonly>
<option <?php if (empty($config_azure_client_id)) { echo "selected"; } ?>>Disabled</option>
<option <?php if ($config_azure_client_id) { echo "selected"; } ?>>Microsoft Entra</option>
<option>Google (WIP)</option>
@@ -27,29 +25,25 @@ require_once "includes/inc_all_admin.php";
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>MS Entra OAuth App (Client) ID</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user"></i></span>
</div>
<input type="text" class="form-control" name="azure_client_id" placeholder="e721e3b6-01d6-50e8-7f22-c84d951a52e7" maxlength="200" value="<?= escapeHtml($config_azure_client_id) ?>">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>MS Entra OAuth Secret</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-key"></i></span>
</div>
<input type="password" class="form-control" name="azure_client_secret" placeholder="Auto-generated from App Registration" maxlength="200" value="<?= escapeHtml($config_azure_client_secret) ?>" autocomplete="new-password">
</div>
</div>
<hr>
<button type="submit" name="edit_identity_provider" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>
<button type="submit" name="edit_identity_provider" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Save</button>
</form>
</div>

View File

@@ -1,19 +1,17 @@
<!-- Main Sidebar Container -->
<aside class="main-sidebar sidebar-dark-<?= escapeHtml($config_theme) ?> d-print-none">
<a class="brand-link pb-1 mt-1" href="/agent/<?= $config_start_page ?>">
<p class="h6">
<i class="nav-icon fas fa-arrow-left ml-3 mr-2"></i>
<span class="brand-text">
Back | <strong>Administration</strong>
</span>
</p>
</a>
<aside class="app-sidebar shadow d-print-none" data-bs-theme="dark">
<div class="sidebar-brand">
<a class="brand-link" href="/agent/<?= $config_start_page ?>">
<i class="fas fa-arrow-left me-2"></i>
<span class="brand-text h6 mb-0">Back | <strong>Administration</strong></span>
</a>
</div>
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-wrapper">
<!-- Sidebar Menu -->
<nav>
<ul class="nav nav-pills nav-sidebar flex-column mt-2" data-widget="treeview" data-accordion="false">
<ul class="nav nav-pills sidebar-menu flex-column mt-2" data-lte-toggle="treeview" data-accordion="false">
<li class="nav-header">ACCESS</li>
<li class="nav-item">
<a href="/admin/users.php" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "users.php") {echo "active";} ?>">
@@ -126,6 +124,12 @@
<p>Ticket Templates</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/canned_responses.php" class="nav-link <?= (basename($_SERVER['PHP_SELF']) == 'canned_responses.php' ? 'active' : '') ?>">
<i class="nav-icon fas fa-comment-dots"></i>
<p>Canned Responses</p>
</a>
</li>
<?php } ?>
<?php if ($config_module_enable_itdoc) { ?>
<!-- 2025-11-16 JQ - Hide Contracts not yet ready
@@ -211,7 +215,7 @@
</li>
<!-- SETTINGS Section -->
<li class="nav-item has-treeview mt-2 <?= (in_array(basename($_SERVER['PHP_SELF']), ['settings_company.php', 'settings_localization.php', 'settings_theme.php', 'settings_security.php', 'settings_mail.php', 'settings_notification.php', 'settings_default.php', 'settings_invoice.php', 'settings_quote.php', 'settings_online_payment.php', 'settings_online_payment_clients.php', 'settings_project.php', 'settings_ticket.php', 'settings_ai.php', 'identity_providers.php', 'settings_telemetry.php', 'settings_module.php']) ? 'menu-open' : '') ?>">
<li class="nav-item mt-2 <?= (in_array(basename($_SERVER['PHP_SELF']), ['settings_company.php', 'settings_localization.php', 'settings_theme.php', 'settings_security.php', 'settings_mail.php', 'settings_notification.php', 'settings_default.php', 'settings_invoice.php', 'settings_quote.php', 'settings_online_payment.php', 'settings_online_payment_clients.php', 'settings_project.php', 'settings_ticket.php', 'settings_ai.php', 'identity_providers.php', 'settings_telemetry.php', 'settings_module.php']) ? 'menu-open' : '') ?>">
<a href="#" class="nav-link">
<p>
SETTINGS
@@ -336,7 +340,7 @@
<a href="<?= $custom_link_uri ?>" <?= $target ?> class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == basename($custom_link_uri)) { echo "active"; } ?>">
<i class="fas fa-<?= $custom_link_icon ?> nav-icon"></i>
<p><?= $custom_link_name ?></p>
<i class="fas fa-angle-right nav-icon float-right"></i>
<i class="fas fa-angle-right nav-icon float-end"></i>
</a>
</li>

View File

@@ -19,46 +19,44 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="card card-dark">
<div class="card-header py-3">
<h3 class="card-title"><i class="fas fa-fw fa-inbox mr-2"></i>Email Queue</h3>
<div class="card">
<div class="card-header bg-dark py-2">
<h3 class="card-title"><i class="fas fa-fw fa-inbox me-2"></i>Email Queue</h3>
</div>
<div class="card-body">
<form class="mb-4" autocomplete="off">
<div class="row">
<div class="card-header py-3">
<form autocomplete="off">
<div class="row g-2 align-items-end">
<div class="col-sm-4">
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(escapeHtml($q)); } ?>" placeholder="Search mail queue">
<div class="input-group-append">
<button class="btn btn-secondary" type="button" data-toggle="collapse" data-target="#advancedFilter"><i class="fas fa-filter"></i></button>
<button class="btn btn-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#advancedFilter"><i class="fas fa-filter"></i></button>
<button class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
<div class="col-sm-8">
<div class="dropdown float-right" id="bulkActionButton" hidden>
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">
<i class="fas fa-fw fa-layer-group mr-2"></i>Bulk Action (<span id="selectedCount">0</span>)
<div class="dropdown float-end" id="bulkActionButton" hidden>
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="fas fa-fw fa-layer-group me-2"></i>Bulk Action (<span id="selectedCount">0</span>)
</button>
<div class="dropdown-menu">
<button class="dropdown-item"
type="submit" form="bulkActions" name="bulk_cancel_emails">
<i class="fas fa-fw fa-ban mr-2"></i>Cancel
<i class="fas fa-fw fa-ban me-2"></i>Cancel
</button>
<div class="dropdown-divider"></div>
<button class="dropdown-item text-danger text-bold"
type="submit" form="bulkActions" name="bulk_delete_emails">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
<i class="fas fa-fw fa-trash me-2"></i>Delete
</button>
</div>
</div>
</div>
</div>
<div class="collapse mt-3 <?php if (isset($_GET['dtf']) && $_GET['dtf'] !== '1970-01-01') { echo "show"; } ?>" id="advancedFilter">
<div class="row">
<div class="collapse mt-3 <?php if (isset($_GET['dtf']) && $_GET['dtf'] !== '1970-01-01') { echo"show"; } ?>" id="advancedFilter">
<div class="row g-3">
<div class="col-md-3">
<div class="form-group">
<label>Date range</label>
<div>
<label class="form-label">Date range</label>
<input type="text" id="dateFilter" class="form-control" autocomplete="off">
<input type="hidden" name="canned_date" id="canned_date" value="<?= escapeHtml($_GET['canned_date']) ?? '' ?>">
<input type="hidden" name="dtf" id="dtf" value="<?= escapeHtml($dtf ?? '') ?>">
@@ -68,124 +66,123 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
</div>
</form>
<hr>
<form id="bulkActions" action="post.php" method="post">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
</div>
<form id="bulkActions" action="post.php" method="post">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="table-responsive-sm">
<table class="table table-sm table-striped table-borderless table-hover">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<td class="bg-light pr-0">
<div class="form-check">
<input class="form-check-input" id="selectAllCheckbox" type="checkbox" onclick="checkAll(this)">
</div>
</td>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=email_queued_at&order=<?= $disp ?>">
Queued <?php if ($sort == 'email_queued_at') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=email_from&order=<?= $disp ?>">
From <?php if ($sort == 'email_from') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=email_recipient&order=<?= $disp ?>">
To <?php if ($sort == 'email_recipient') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=email_subject&order=<?= $disp ?>">
Subject <?php if ($sort == 'email_subject') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=email_status&order=<?= $disp ?>">
Status <?php if ($sort == 'email_status') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=email_attempts&order=<?= $disp ?>">
Attempts <?php if ($sort == 'email_attempts') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
<div class="table-responsive-sm">
<table class="table table-sm table-striped table-borderless table-hover mb-0">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<td class="checkbox-column border-end">
<div class="form-check">
<input class="form-check-input" id="selectAllCheckbox" type="checkbox" onclick="checkAll(this)">
</div>
</td>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=email_queued_at&order=<?= $disp ?>">
Queued <?php if ($sort == 'email_queued_at') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=email_from&order=<?= $disp ?>">
From <?php if ($sort == 'email_from') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=email_recipient&order=<?= $disp ?>">
To <?php if ($sort == 'email_recipient') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=email_subject&order=<?= $disp ?>">
Subject <?php if ($sort == 'email_subject') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=email_status&order=<?= $disp ?>">
Status <?php if ($sort == 'email_status') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=email_attempts&order=<?= $disp ?>">
Attempts <?php if ($sort == 'email_attempts') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_assoc($sql)) {
$email_id = intval($row['email_id']);
$email_from = escapeHtml($row['email_from']);
$email_from_name = escapeHtml($row['email_from_name']);
$email_recipient = escapeHtml($row['email_recipient']);
$email_recipient_name = escapeHtml($row['email_recipient_name']);
$email_subject = escapeHtml($row['email_subject']);
$email_attempts = intval($row['email_attempts']);
$email_queued_at = escapeHtml($row['email_queued_at']);
$email_failed_at = escapeHtml($row['email_failed_at']);
$email_sent_at = escapeHtml($row['email_sent_at']);
$email_status = intval($row['email_status']);
if ($email_status == 0) {
$email_status_display = "<div class='text-primary'>Queued</div>";
} elseif($email_status == 1) {
$email_status_display = "<div class='text-warning'>Sending</div>";
} elseif($email_status == 2) {
$email_status_display = "<div class='text-danger'>Failed</div><small class='text-secondary text-monospace'>$email_failed_at</small>";
} else {
$email_status_display = "<div class='text-success'>Sent</div><small class='text-secondary text-monospace'>$email_sent_at</small>";
}
?>
<tr>
<td class="pr-0 bg-light">
<?php if ($email_status !== 3) { ?>
<div class="form-check">
<input class="form-check-input bulk-select" type="checkbox" name="email_ids[]" value="<?= $email_id ?>">
</div>
<?php } ?>
</td>
<td class="text-monospace"><?= $email_queued_at ?></td>
<td><?= "$email_from<br><small class='text-secondary'>$email_from_name</small>" ?></td>
<td><?= "$email_recipient<br><small class='text-secondary'>$email_recipient_name</small>" ?></td>
<td><?= $email_subject ?></td>
<td><?= $email_status_display ?></td>
<td><?= $email_attempts ?></td>
<td class="text-center">
<a class="btn btn-sm btn-secondary ajax-modal" href="#"
data-modal-size="lg"
data-modal-url="modals/mail_queue/mail_queue_message_view.php?id=<?= $email_id ?>">
<i class="fas fa-fw fa-eye"></i>
</a>
<!-- Show force resend if all retries have failed -->
<?php if ($email_status == 2 && $email_attempts > 3) { ?>
<a class="btn btn-sm btn-success" href="post.php?send_failed_mail=<?= $email_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>"><i class="fas fa-fw fa-paper-plane"></i></a>
<?php } ?>
<!-- Allow cancelling a message if it hasn't yet been picked up (e.g. stuck/bugged) -->
<?php if ($email_status !== 3) { ?>
<a class="btn btn-sm btn-danger confirm-link" href="post.php?cancel_mail=<?= $email_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>"><i class="fas fa-fw fa-trash"></i></a>
<?php } ?>
</td>
</tr>
<?php
while ($row = mysqli_fetch_assoc($sql)) {
$email_id = intval($row['email_id']);
$email_from = escapeHtml($row['email_from']);
$email_from_name = escapeHtml($row['email_from_name']);
$email_recipient = escapeHtml($row['email_recipient']);
$email_recipient_name = escapeHtml($row['email_recipient_name']);
$email_subject = escapeHtml($row['email_subject']);
$email_attempts = intval($row['email_attempts']);
$email_queued_at = escapeHtml($row['email_queued_at']);
$email_failed_at = escapeHtml($row['email_failed_at']);
$email_sent_at = escapeHtml($row['email_sent_at']);
$email_status = intval($row['email_status']);
if ($email_status == 0) {
$email_status_display = "<div class='text-primary'>Queued</div>";
} elseif($email_status == 1) {
$email_status_display = "<div class='text-warning'>Sending</div>";
} elseif($email_status == 2) {
$email_status_display = "<div class='text-danger'>Failed</div><small class='text-secondary font-monospace'>$email_failed_at</small>";
} else {
$email_status_display = "<div class='text-success'>Sent</div><small class='text-secondary font-monospace'>$email_sent_at</small>";
}
?>
</tbody>
</table>
</div>
</form>
<tr>
<td class="checkbox-column bg-light border-end">
<?php if ($email_status !== 3) { ?>
<div class="form-check">
<input class="form-check-input bulk-select" type="checkbox" name="email_ids[]" value="<?= $email_id ?>">
</div>
<?php } ?>
</td>
<td class="font-monospace"><?= $email_queued_at ?></td>
<td><?= "$email_from<br><small class='text-secondary'>$email_from_name</small>" ?></td>
<td><?= "$email_recipient<br><small class='text-secondary'>$email_recipient_name</small>" ?></td>
<td><?= $email_subject ?></td>
<td><?= $email_status_display ?></td>
<td><?= $email_attempts ?></td>
<td class="text-center">
<a class="btn btn-sm btn-secondary ajax-modal" href="#"
data-modal-size="lg"
data-modal-url="modals/mail_queue/mail_queue_message_view.php?id=<?= $email_id ?>">
<i class="fas fa-fw fa-eye"></i>
</a>
<?php require_once "../includes/filter_footer.php"; ?>
</div>
<!-- Show force resend if all retries have failed -->
<?php if ($email_status == 2 && $email_attempts > 3) { ?>
<a class="btn btn-sm btn-success" href="post.php?send_failed_mail=<?= $email_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>"><i class="fas fa-fw fa-paper-plane"></i></a>
<?php } ?>
<!-- Allow cancelling a message if it hasn't yet been picked up (e.g. stuck/bugged) -->
<?php if ($email_status !== 3) { ?>
<a class="btn btn-sm btn-danger confirm-link" href="post.php?cancel_mail=<?= $email_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>"><i class="fas fa-fw fa-trash"></i></a>
<?php } ?>
</td>
</tr>
<?php
}
?>
</tbody>
</table>
</div>
</form>
<?php require_once "../includes/filter_footer.php"; ?>
</div>
<script src="../js/bulk_actions.js"></script>

View File

@@ -7,23 +7,19 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-robot mr-2"></i>Add AI Model</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-robot me-2"></i>Add AI Model</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Provider <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-robot"></i></span>
</div>
<select class="form-control select2" name="provider" required>
<select class="form-select select2" name="provider" required>
<option value="">- Select an AI Provider -</option>
<?php
$sql_ai_providers = mysqli_query($mysqli, "SELECT ai_provider_id, ai_provider_name FROM ai_providers");
@@ -38,23 +34,19 @@ ob_start();
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Model Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-robot"></i></span>
</div>
<input type="text" class="form-control" name="model" placeholder="ex gpt-4" maxlength="200">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Use Case <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-th-list"></i></span>
</div>
<select class="form-control select2" name="use_case">
<select class="form-select select2" name="use_case">
<option>General</option>
<option>Tickets</option>
<option>Documentation</option>
@@ -62,25 +54,23 @@ ob_start();
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Temperature</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-thermometer-half"></i></span>
</div>
<input type="number" class="form-control" name="temperature" step="0.1" min="0" max="2" value="" placeholder="Provider default">
</div>
<small class="form-text text-muted">Optional. Leave blank to let the provider use its default - some newer models reject every other value.</small>
</div>
<div class="form-group">
<div class="mb-3">
<textarea class="form-control" rows="8" name="prompt" placeholder="Enter a model prompt:"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_ai_model" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_ai_model" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Create</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -19,10 +19,8 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-robot mr-2"></i>Editing: <strong><?= $model_name ?></strong></h5>
<button type="button" class="close text-light" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-robot me-2"></i>Editing: <strong><?= $model_name ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -30,13 +28,11 @@ ob_start();
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Provider <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-robot"></i></span>
</div>
<select class="form-control select2" name="provider" required>
<select class="form-select select2" name="provider" required>
<option value="">- Select an AI Provider -</option>
<?php
$sql_ai_providers = mysqli_query($mysqli, "SELECT ai_provider_id, ai_provider_name FROM ai_providers");
@@ -51,23 +47,19 @@ ob_start();
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Model Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-robot"></i></span>
</div>
<input type="text" class="form-control" name="model" value="<?= $model_name ?>" placeholder="ex gpt-4" maxlength="200">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Use Case <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-th-list"></i></span>
</div>
<select class="form-control select2" name="use_case">
<select class="form-select select2" name="use_case">
<option <?php if ($use_case == 'General') { echo "selected"; } ?>>General</option>
<option <?php if ($use_case == 'Tickets') { echo "selected"; } ?>>Tickets</option>
<option <?php if ($use_case == 'Documentation') { echo "selected"; } ?>>Documentation</option>
@@ -75,24 +67,22 @@ ob_start();
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Temperature</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-thermometer-half"></i></span>
</div>
<input type="number" class="form-control" name="temperature" step="0.1" min="0" max="2" value="<?= $temperature ?>" placeholder="Provider default">
</div>
<small class="form-text text-muted">Optional. Leave blank to let the provider use its default - some newer models reject every other value.</small>
</div>
<div class="form-group">
<div class="mb-3">
<textarea class="form-control" rows="8" name="prompt" placeholder="Enter a model prompt:"><?= $prompt ?></textarea>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_ai_model" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_ai_model" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -7,50 +7,42 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-robot mr-2"></i>New AI Provider</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-robot me-2"></i>New AI Provider</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Provider Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-robot"></i></span>
</div>
<input type="text" class="form-control" name="provider" placeholder="ex OpenAI" maxlength="200">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>URL <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-globe"></i></span>
</div>
<input type="url" class="form-control" name="url" placeholder="ex https://ai.company.ext/api" maxlength="200">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>API Key</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-key"></i></span>
</div>
<input type="text" class="form-control" name="api_key" placeholder="Enter API key here" maxlength="200">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_ai_provider" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_ai_provider" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Create</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -16,10 +16,8 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-robot mr-2"></i>Editing: <strong><?= $provider_name ?></strong></h5>
<button type="button" class="close text-light" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-robot me-2"></i>Editing: <strong><?= $provider_name ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -27,40 +25,34 @@ ob_start();
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Provider Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-robot"></i></span>
</div>
<input type="text" class="form-control" name="provider" value="<?= $provider_name ?>" placeholder="ex OpenAI" maxlength="200">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>URL <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-globe"></i></span>
</div>
<input type="url" class="form-control" name="url" value="<?= $url ?>" placeholder="ex https://ai.company.ext/api" maxlength="200">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>API Key</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-key"></i></span>
</div>
<input type="text" class="form-control" name="api_key" value="<?= $key ?>" placeholder="Enter API key here" maxlength="200">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_ai_provider" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_ai_provider" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -10,20 +10,18 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-key mr-2"></i>New Key</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-key me-2"></i>New Key</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<div class="modal-body">
<ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#pills-api-details">Details</a>
<a class="nav-link active" data-bs-toggle="pill" href="#pills-api-details">Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-api-keys">Keys</a>
<a class="nav-link" data-bs-toggle="pill" href="#pills-api-keys">Keys</a>
</li>
</ul>
<hr>
@@ -35,33 +33,27 @@ ob_start();
<input type="hidden" name="key" value="<?= $key ?>">
<input type="hidden" name="password" value="<?= $decryptPW ?>">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-sticky-note"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Key Name" maxlength="255" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Expiration Date <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
</div>
<input type="date" class="form-control" name="expire" min="<?= date('Y-m-d') ?>" max="2999-12-31" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Run as User <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user-shield"></i></span>
</div>
<select class="form-control select2" name="run_as_user" required>
<select class="form-select select2" name="run_as_user" required>
<option value="">- Select a user -</option>
<?php
$sql_run_users = mysqli_query($mysqli, "SELECT user_id, user_name FROM users WHERE user_type = 1 AND user_status = 1 AND user_archived_at IS NULL ORDER BY user_name ASC");
@@ -77,38 +69,28 @@ ob_start();
</div>
<div class="tab-pane fade" id="pills-api-keys">
<div class="form-group">
<div class="mb-3">
<label>API Key <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-key"></i></span>
</div>
<input type="text" class="form-control" value="<?= $key ?>" required disabled>
<div class="input-group-append">
<button class="btn btn-default clipboardjs" type="button" data-clipboard-text="<?= $key ?>"><i class="fa fa-fw fa-copy"></i></button>
</div>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Login credential decryption password <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-unlock-alt"></i></span>
</div>
<input type="text" class="form-control" value="<?= $decryptPW ?>" required disabled>
<div class="input-group-append">
<button class="btn btn-default clipboardjs" type="button" data-clipboard-text="<?= $decryptPW ?>"><i class="fa fa-fw fa-copy"></i></button>
</div>
</div>
</div>
<br>
<div class="form-group">
<div class="mb-3">
<label>I have made a copy of the key(s)<strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<input type="checkbox" name="ack" value="1" required>
</div>
<input class="form-check-input" type="checkbox" name="ack" value="1" required>
</div>
</div>
</div>
@@ -117,8 +99,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="add_api_key" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_api_key" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Create</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -14,11 +14,9 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-key mr-2"></i>Editing API Key:
<h5 class="modal-title"><i class="fas fa-fw fa-key me-2"></i>Editing API Key:
<strong><?= $api_key_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<div class="modal-body">
@@ -26,35 +24,29 @@ ob_start();
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="api_key_id" value="<?= $api_key_id ?>">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-sticky-note"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Key Name" maxlength="255"
value="<?= $api_key_name ?>" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Expiration Date <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
</div>
<input type="date" class="form-control" name="expire" min="<?= date('Y-m-d') ?>" max="2999-12-31"
value="<?= $api_key_expire ?>" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Run as User <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user-shield"></i></span>
</div>
<select class="form-control select2" name="run_as_user" required>
<select class="form-select select2" name="run_as_user" required>
<option value="">- Select a user -</option>
<?php
$sql_run_users = mysqli_query($mysqli, "SELECT user_id, user_name FROM users WHERE user_type = 1 AND user_status = 1 AND user_archived_at IS NULL ORDER BY user_name ASC");
@@ -70,8 +62,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="edit_api_key" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_api_key" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -0,0 +1,58 @@
<?php
require_once '../../includes/modal_header.php';
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-comment-dots me-2"></i>New Canned Response</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-comment-dots"></i></span>
<input type="text" class="form-control" name="name" placeholder="What an agent picks it by" maxlength="200" required autofocus>
</div>
</div>
<div class="mb-3">
<label>Ticket Category</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-tag"></i></span>
<select class="form-select select2" name="category">
<option value="0">- All ticket categories -</option>
<?php
$sql_categories = mysqli_query($mysqli, "SELECT category_id, category_name FROM categories WHERE category_type = 'Ticket' AND category_archived_at IS NULL ORDER BY category_name ASC");
while ($row = mysqli_fetch_assoc($sql_categories)) {
$category_id_select = intval($row['category_id']);
$category_name_select = escapeHtml($row['category_name']); ?>
<option value="<?= $category_id_select ?>"><?= $category_name_select ?></option>
<?php } ?>
</select>
</div>
<small class="text-secondary">Leave this on all categories for a response that should be offered on every ticket.</small>
</div>
<div class="mb-3">
<label>Response</label>
<textarea class="form-control tinymceTicket" name="body"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_canned_response" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Create Canned Response</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>
<?php
require_once '../../../includes/modal_footer.php';

View File

@@ -0,0 +1,72 @@
<?php
require_once '../../includes/modal_header.php';
$canned_response_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT canned_response_name, canned_response_body, canned_response_category_id
FROM canned_responses WHERE canned_response_id = $canned_response_id LIMIT 1");
$row = mysqli_fetch_assoc($sql);
$canned_response_name = escapeHtml($row['canned_response_name']);
$canned_response_category_id = intval($row['canned_response_category_id']);
// Into a textarea, so escaped rather than purified - it is markup being edited, not rendered
$canned_response_body = escapeHtml($row['canned_response_body']);
// Generate the HTML form content using output buffering.
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-comment-dots me-2"></i>Editing Canned Response: <strong><?= $canned_response_name ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="canned_response_id" value="<?= $canned_response_id ?>">
<div class="modal-body">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-comment-dots"></i></span>
<input type="text" class="form-control" name="name" maxlength="200" value="<?= $canned_response_name ?>" required>
</div>
</div>
<div class="mb-3">
<label>Ticket Category</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-tag"></i></span>
<select class="form-select select2" name="category">
<option value="0" <?php if ($canned_response_category_id == 0) { echo "selected"; } ?>>- All ticket categories -</option>
<?php
$sql_categories = mysqli_query($mysqli, "SELECT category_id, category_name FROM categories WHERE category_type = 'Ticket' AND category_archived_at IS NULL ORDER BY category_name ASC");
while ($category_row = mysqli_fetch_assoc($sql_categories)) {
$category_id_select = intval($category_row['category_id']);
$category_name_select = escapeHtml($category_row['category_name']); ?>
<option value="<?= $category_id_select ?>" <?php if ($canned_response_category_id == $category_id_select) { echo "selected"; } ?>><?= $category_name_select ?></option>
<?php } ?>
</select>
</div>
<small class="text-secondary">Leave this on all categories for a response that should be offered on every ticket.</small>
</div>
<div class="mb-3">
<label>Response</label>
<textarea class="form-control tinymceTicket" name="body"><?= $canned_response_body ?></textarea>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_canned_response" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>
<?php
require_once '../../../includes/modal_footer.php';

View File

@@ -9,10 +9,8 @@ $category_types_array = ['Expense', 'Income', 'Referral', 'Ticket'];
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-list-ul mr-2"></i>New <strong><?= escapeHtml(ucwords(str_replace('_', ' ', $category))); ?></strong> Category</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-list-ul me-2"></i>New <strong><?= escapeHtml(ucwords(str_replace('_', ' ', $category))); ?></strong> Category</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -23,13 +21,11 @@ $category_types_array = ['Expense', 'Income', 'Referral', 'Ticket'];
<input type="hidden" name="type" value="<?= $category ?>">
<?php } else { ?>
<div class="form-group">
<div class="mb-3">
<label>Type <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-tag"></i></span>
</div>
<select class="form-control select2" name="type" required>
<select class="form-select select2" name="type" required>
<option value="">- Select Type -</option>
<?php foreach ($category_types_array as $type_select) { ?>
<option><?= $type_select ?></option>
@@ -40,40 +36,34 @@ $category_types_array = ['Expense', 'Income', 'Referral', 'Ticket'];
<?php } ?>
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-list-ul"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Category name" maxlength="200" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Color <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-paint-brush"></i></span>
</div>
<input type="color" class="form-control col-3" name="color" required>
<input type="color" class="form-control form-control-color" name="color" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-align-left"></i></span>
</div>
<input type="text" class="form-control" name="description" placeholder="Enter a description" maxlength="200">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_category" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create Category</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_category" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Create Category</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -16,10 +16,8 @@ $category_type = escapeHtml($row['category_type']);
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-list-ul mr-2"></i>Editing category: <strong><?= $category_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-list-ul me-2"></i>Editing category: <strong><?= $category_name ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -27,40 +25,34 @@ ob_start();
<input type="hidden" name="type" value="<?= $category_type ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-list-ul"></i></span>
</div>
<input type="text" class="form-control" name="name" maxlength="200" value="<?= $category_name ?>" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Color <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-paint-brush"></i></span>
</div>
<input type="color" class="form-control col-3" name="color" value="<?= $category_color ?>" required>
<input type="color" class="form-control form-control-color" name="color" value="<?= $category_color ?>" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-align-left"></i></span>
</div>
<input type="text" class="form-control" name="description" placeholder="Enter a description" maxlength="200" value="<?= $category_description ?>">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_category" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_category" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -9,23 +9,23 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-file-contract mr-2"></i>New Contract Template</h5>
<button type="button" class="close text-white" data-dismiss="modal"><span>&times;</span></button>
<h5 class="modal-title"><i class="fa fa-fw fa-file-contract me-2"></i>New Contract Template</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<!-- Tabs Navigation -->
<ul class="modal-header nav nav-pills nav-justified">
<li class="nav-item">
<a class="nav-link active" id="general-tab" data-toggle="tab" href="#general" role="tab">General Info</a>
<a class="nav-link active" id="general-tab" data-bs-toggle="tab" href="#general" role="tab">General Info</a>
</li>
<li class="nav-item">
<a class="nav-link" id="sla-tab" data-toggle="tab" href="#sla" role="tab">SLA</a>
<a class="nav-link" id="sla-tab" data-bs-toggle="tab" href="#sla" role="tab">SLA</a>
</li>
<li class="nav-item">
<a class="nav-link" id="rates-tab" data-toggle="tab" href="#rates" role="tab">Rates & Support</a>
<a class="nav-link" id="rates-tab" data-bs-toggle="tab" href="#rates" role="tab">Rates & Support</a>
</li>
<li class="nav-item">
<a class="nav-link" id="details-tab" data-toggle="tab" href="#details" role="tab">Details</a>
<a class="nav-link" id="details-tab" data-bs-toggle="tab" href="#details" role="tab">Details</a>
</li>
</ul>
@@ -37,34 +37,28 @@ ob_start();
<!-- General Info Tab -->
<div class="tab-pane fade show active" id="general" role="tabpanel">
<div class="form-group">
<div class="mb-3">
<label>Template Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-file-contract"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Contract Template Name" maxlength="200" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Template Description <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-align-left"></i></span>
</div>
<input type="text" class="form-control" name="description"
placeholder="Contract Template Description" maxlength="200" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Contract Type <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-list"></i></span>
</div>
<select class="form-control select2" name="type" required>
<select class="form-select select2" name="type" required>
<option value="">- Select Type -</option>
<?php foreach ($contract_types_array as $type) { ?>
<option><?= $type ?></option>
@@ -73,13 +67,11 @@ ob_start();
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Renewal Frequency</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-sync-alt"></i></span>
</div>
<select class="form-control select2" name="renewal_frequency">
<select class="form-select select2" name="renewal_frequency">
<option value="">- Select Frequency -</option>
<?php foreach ($renewal_frequency_array as $renewal_frequency) { ?>
<option><?= $renewal_frequency ?></option>
@@ -91,64 +83,52 @@ ob_start();
<!-- SLA Tab -->
<div class="tab-pane fade" id="sla" role="tabpanel">
<div class="form-row">
<div class="form-group col-md-6">
<div class="row g-2">
<div class="mb-3 col-md-6">
<label>Low Priority Response (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-clock"></i></span>
</div>
<input type="number" class="form-control" name="sla_low_response_time" placeholder="e.g., 24">
</div>
</div>
<div class="form-group col-md-6">
<div class="mb-3 col-md-6">
<label>Low Priority Resolution (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-hourglass-half"></i></span>
</div>
<input type="number" class="form-control" name="sla_low_resolution_time" placeholder="e.g., 48">
</div>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<div class="row g-2">
<div class="mb-3 col-md-6">
<label>Medium Priority Response (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-clock"></i></span>
</div>
<input type="number" class="form-control" name="sla_medium_response_time" placeholder="e.g., 12">
</div>
</div>
<div class="form-group col-md-6">
<div class="mb-3 col-md-6">
<label>Medium Priority Resolution (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-hourglass-half"></i></span>
</div>
<input type="number" class="form-control" name="sla_medium_resolution_time" placeholder="e.g., 24">
</div>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<div class="row g-2">
<div class="mb-3 col-md-6">
<label>High Priority Response (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-bolt"></i></span>
</div>
<input type="number" class="form-control" name="sla_high_response_time" placeholder="e.g., 1">
</div>
</div>
<div class="form-group col-md-6">
<div class="mb-3 col-md-6">
<label>High Priority Resolution (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-stopwatch"></i></span>
</div>
<input type="number" class="form-control" name="sla_high_resolution_time" placeholder="e.g., 4">
</div>
</div>
@@ -157,42 +137,34 @@ ob_start();
<!-- Rates & Support Tab -->
<div class="tab-pane fade" id="rates" role="tabpanel">
<div class="form-group">
<div class="mb-3">
<label>Standard Hourly Rate</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-dollar-sign"></i></span>
</div>
<input type="text" class="form-control" name="rate_standard" placeholder="e.g., 100">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>After Hours Hourly Rate</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-moon"></i></span>
</div>
<input type="text" class="form-control" name="rate_after_hours" placeholder="e.g., 150">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Support Hours</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
</div>
<input type="text" class="form-control" name="support_hours" placeholder="e.g., Mon-Fri 9am-5pm" maxlength="100">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Net Terms</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-file-invoice-dollar"></i></span>
</div>
<input type="text" class="form-control" name="net_terms" placeholder="e.g., Net 30" maxlength="50">
</div>
</div>
@@ -200,7 +172,7 @@ ob_start();
<!-- Details Tab -->
<div class="tab-pane fade" id="details" role="tabpanel">
<div class="form-group">
<div class="mb-3">
<textarea class="form-control tinymce" rows="6" name="details" placeholder="Enter Contract Details"></textarea>
</div>
</div>
@@ -210,10 +182,10 @@ ob_start();
<div class="modal-footer">
<button type="submit" name="add_contract_template" class="btn btn-primary text-bold">
<i class="fa fa-check mr-2"></i>Create Template
<i class="fa fa-check me-2"></i>Create Template
</button>
<button type="button" class="btn btn-light" data-dismiss="modal">
<i class="fa fa-times mr-2"></i>Cancel
<button type="button" class="btn btn-light" data-bs-dismiss="modal">
<i class="fa fa-times me-2"></i>Cancel
</button>
</div>
</form>

View File

@@ -37,23 +37,23 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-file-contract mr-2"></i>Edit Contract Template</h5>
<button type="button" class="close text-white" data-dismiss="modal"><span>&times;</span></button>
<h5 class="modal-title"><i class="fa fa-fw fa-file-contract me-2"></i>Edit Contract Template</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<!-- Tabs Navigation -->
<ul class="modal-header nav nav-pills nav-justified">
<li class="nav-item">
<a class="nav-link active" id="general-tab" data-toggle="tab" href="#general" role="tab">General Info</a>
<a class="nav-link active" id="general-tab" data-bs-toggle="tab" href="#general" role="tab">General Info</a>
</li>
<li class="nav-item">
<a class="nav-link" id="sla-tab" data-toggle="tab" href="#sla" role="tab">SLA</a>
<a class="nav-link" id="sla-tab" data-bs-toggle="tab" href="#sla" role="tab">SLA</a>
</li>
<li class="nav-item">
<a class="nav-link" id="rates-tab" data-toggle="tab" href="#rates" role="tab">Rates & Support</a>
<a class="nav-link" id="rates-tab" data-bs-toggle="tab" href="#rates" role="tab">Rates & Support</a>
</li>
<li class="nav-item">
<a class="nav-link" id="details-tab" data-toggle="tab" href="#details" role="tab">Details</a>
<a class="nav-link" id="details-tab" data-bs-toggle="tab" href="#details" role="tab">Details</a>
</li>
</ul>
@@ -66,37 +66,31 @@ ob_start();
<!-- General Info Tab -->
<div class="tab-pane fade show active" id="general" role="tabpanel">
<div class="form-group">
<div class="mb-3">
<label>Template Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-file-contract"></i></span>
</div>
<input type="text" class="form-control" name="name"
placeholder="Contract Template Name" maxlength="200" required autofocus
value="<?= $name ?>">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Template Description <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-align-left"></i></span>
</div>
<input type="text" class="form-control" name="description"
placeholder="Contract Template Description" maxlength="200" required
value="<?= $description ?>">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Contract Type <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-list"></i></span>
</div>
<select class="form-control select2" name="type" required>
<select class="form-select select2" name="type" required>
<option value="">- Select Type -</option>
<?php foreach ($contract_types_array as $type_select) { ?>
<option <?php if ($type == $type_select) { echo "selected"; } ?>><?= $type_select ?></option>
@@ -105,13 +99,11 @@ ob_start();
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Renewal Frequency</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-sync-alt"></i></span>
</div>
<select class="form-control select2" name="renewal_frequency">
<select class="form-select select2" name="renewal_frequency">
<option value="">- Select Frequency -</option>
<?php foreach ($renewal_frequency_array as $renewal_frequency_select) { ?>
<option <?php if ($renewal_frequency == $renewal_frequency_select) { echo "selected"; } ?>><?= $renewal_frequency_select ?></option>
@@ -123,69 +115,57 @@ ob_start();
<!-- SLA Tab -->
<div class="tab-pane fade" id="sla" role="tabpanel">
<div class="form-row">
<div class="form-group col-md-6">
<div class="row g-2">
<div class="mb-3 col-md-6">
<label>Low Priority Response (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-clock"></i></span>
</div>
<input type="number" class="form-control" name="sla_low_response_time" placeholder="e.g., 24"
value="<?= $sla_low_resp ?>">
</div>
</div>
<div class="form-group col-md-6">
<div class="mb-3 col-md-6">
<label>Low Priority Resolution (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-hourglass-half"></i></span>
</div>
<input type="number" class="form-control" name="sla_low_resolution_time" placeholder="e.g., 48"
value="<?= $sla_low_res ?>">
</div>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<div class="row g-2">
<div class="mb-3 col-md-6">
<label>Medium Priority Response (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-clock"></i></span>
</div>
<input type="number" class="form-control" name="sla_medium_response_time" placeholder="e.g., 12"
value="<?= $sla_med_resp ?>">
</div>
</div>
<div class="form-group col-md-6">
<div class="mb-3 col-md-6">
<label>Medium Priority Resolution (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-hourglass-half"></i></span>
</div>
<input type="number" class="form-control" name="sla_medium_resolution_time" placeholder="e.g., 24"
value="<?= $sla_med_res ?>">
</div>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<div class="row g-2">
<div class="mb-3 col-md-6">
<label>High Priority Response (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-bolt"></i></span>
</div>
<input type="number" class="form-control" name="sla_high_response_time" placeholder="e.g., 1"
value="<?= $sla_high_resp ?>">
</div>
</div>
<div class="form-group col-md-6">
<div class="mb-3 col-md-6">
<label>High Priority Resolution (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-stopwatch"></i></span>
</div>
<input type="number" class="form-control" name="sla_high_resolution_time" placeholder="e.g., 4"
value="<?= $sla_high_res ?>">
</div>
@@ -195,45 +175,37 @@ ob_start();
<!-- Rates & Support Tab -->
<div class="tab-pane fade" id="rates" role="tabpanel">
<div class="form-group">
<div class="mb-3">
<label>Standard Hourly Rate</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-dollar-sign"></i></span>
</div>
<input type="text" class="form-control" name="rate_standard" placeholder="e.g., 100"
value="<?= $rate_standard ?>">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>After Hours Hourly Rate</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-moon"></i></span>
</div>
<input type="text" class="form-control" name="rate_after_hours" placeholder="e.g., 150"
value="<?= $rate_after_hours ?>">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Support Hours</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
</div>
<input type="text" class="form-control" name="support_hours" placeholder="e.g., Mon-Fri 9am-5pm" maxlength="100"
value="<?= $support_hours ?>">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Net Terms</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-file-invoice-dollar"></i></span>
</div>
<input type="text" class="form-control" name="net_terms" placeholder="e.g., Net 30" maxlength="50"
value="<?= $net_terms ?>">
</div>
@@ -242,12 +214,10 @@ ob_start();
<!-- Details Tab -->
<div class="tab-pane fade" id="details" role="tabpanel">
<div class="form-group">
<div class="mb-3">
<label>Contract Details</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-align-left"></i></span>
</div>
<textarea class="form-control tinymce" rows="6" name="details"
placeholder="Enter Contract Details"><?= $details ?></textarea>
</div>
@@ -259,10 +229,10 @@ ob_start();
<div class="modal-footer">
<button type="submit" name="edit_contract_template" class="btn btn-primary text-bold">
<i class="fa fa-check mr-2"></i>Save Changes
<i class="fa fa-check me-2"></i>Save Changes
</button>
<button type="button" class="btn btn-light" data-dismiss="modal">
<i class="fa fa-times mr-2"></i>Cancel
<button type="button" class="btn btn-light" data-bs-dismiss="modal">
<i class="fa fa-times me-2"></i>Cancel
</button>
</div>
</form>

View File

@@ -27,31 +27,27 @@ if (empty($cron_job_daily_at)) {
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-clock mr-2"></i>Editing: <strong><?= $cron_job_label ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-clock me-2"></i>Editing: <strong><?= $cron_job_label ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="cron_job_id" value="<?= $cron_job_id ?>">
<div class="modal-body">
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" name="enabled" value="1" id="cronJobEnabledSwitch" <?= $cron_job_enabled == 1 ? 'checked' : '' ?>>
<label class="custom-control-label" for="cronJobEnabledSwitch">Enabled</label>
<div class="mb-3">
<div class="form-check form-switch">
<input type="checkbox" class="form-check-input" name="enabled" value="1" id="cronJobEnabledSwitch" <?= $cron_job_enabled == 1 ? 'checked' : '' ?>>
<label class="form-check-label" for="cronJobEnabledSwitch">Enabled</label>
</div>
<small class="text-muted">A disabled job never runs on its schedule, but Run Now still works.</small>
</div>
<div class="form-group">
<div class="mb-3">
<label>Schedule</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
</div>
<select class="form-control" name="schedule" id="cronJobSchedule">
<select class="form-select" name="schedule" id="cronJobSchedule">
<?php if ($cron_job_interval_safe) { ?>
<option value="Interval" <?= $cron_job_schedule === 'Interval' ? 'selected' : '' ?>>Every so many minutes</option>
<?php } ?>
@@ -63,26 +59,20 @@ ob_start();
<?php } ?>
</div>
<div class="form-group" id="cronJobIntervalGroup">
<div class="mb-3" id="cronJobIntervalGroup">
<label>Run every</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-redo"></i></span>
</div>
<input type="number" class="form-control" name="interval_minutes" value="<?= $cron_job_interval_minutes ?>" min="1" max="1440">
<div class="input-group-append">
<span class="input-group-text">minutes</span>
</div>
</div>
<small class="text-muted">Cron wakes once a minute, so 1 is as often as anything can run.</small>
</div>
<div class="form-group" id="cronJobDailyGroup">
<div class="mb-3" id="cronJobDailyGroup">
<label>Run at</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-clock"></i></span>
</div>
<input type="time" class="form-control" name="daily_at" value="<?= $cron_job_daily_at ?>">
</div>
<small class="text-muted">Your ITFlow timezone. A run missed because the server was off happens at the next opportunity instead of waiting a day.</small>
@@ -90,8 +80,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="edit_cron_job" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_cron_job" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -2,10 +2,8 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-th-list mr-2"></i>Create <?= escapeHtml($table) ?> field</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-th-list me-2"></i>Create <?= escapeHtml($table) ?> field</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -13,18 +11,16 @@
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Label <strong class="text-danger">*</strong></label>
<input type="text" class="form-control" name="label" placeholder="Enter a custom field label" maxlength="255" required autofocus>
</div>
<div class="form-group">
<div class="mb-3">
<label>Type <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-th"></i></span>
</div>
<select class="form-control select2" name="type" required>
<select class="form-select select2" name="type" required>
<option value="">- Select a field type -</option>
<option>Text</option>
</select>
@@ -33,8 +29,8 @@
</div>
<div class="modal-footer">
<button type="submit" name="create_custom_field" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="create_custom_field" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Create</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>
</div>

View File

@@ -2,28 +2,24 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-th-list mr-2"></i>Editing custom field: <strong><?= $custom_field_label ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-th-list me-2"></i>Editing custom field: <strong><?= $custom_field_label ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="custom_field_id" value="<?= $custom_field_id ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Label <strong class="text-danger">*</strong></label>
<input type="text" class="form-control" name="label" maxlength="255" value="<?= $custom_field_label ?>" required>
</div>
<div class="form-group">
<div class="mb-3">
<label>Type <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-th"></i></span>
</div>
<select class="form-control select2" name="type" required>
<select class="form-select select2" name="type" required>
<option value="">- Select a field type -</option>
<option <?php if ($custom_field_type == 'text') { echo "selected"; } ?> value="text">Text</option>
</select>
@@ -32,8 +28,8 @@
</div>
<div class="modal-footer">
<button type="submit" name="edit_custom_field" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_custom_field" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>
</div>

View File

@@ -7,68 +7,54 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-external-link-alt mr-2"></i>New Custom Link</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-external-link-alt me-2"></i>New Custom Link</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-list-ul"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Link name" maxlength="200" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Order</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-sort-numeric-down"></i></span>
</div>
<input type="number" class="form-control" name="order" placeholder="Leave blank for no order">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>URI <strong class="text-danger">*</strong></label> / <span class="text-secondary">Open New Tab</span>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-external-link-alt"></i></span>
</div>
<input type="text" class="form-control" name="uri" placeholder="Enter Link" maxlength="500" required>
<div class="input-group-append">
<div class="input-group-text">
<input type="checkbox" name="new_tab" value="1">
<input class="form-check-input" type="checkbox" name="new_tab" value="1">
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Icon</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-image"></i></span>
</div>
<input type="text" class="form-control" name="icon" placeholder="Icon ex handshake" maxlength="200">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Location <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-home"></i></span>
</div>
<select class="form-control select2" name="location" required>
<select class="form-select select2" name="location" required>
<option value="1">Main Side Nav</option>
<option value="2">Top Nav (Icon Required)</option>
<option value="3">Client Portal Nav</option>
@@ -80,8 +66,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="add_custom_link" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_custom_link" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Create</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -19,10 +19,8 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-external-link-alt mr-2"></i>Editing link: <strong><?= $custom_link_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-external-link-alt me-2"></i>Editing link: <strong><?= $custom_link_name ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -30,58 +28,46 @@ ob_start();
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-list-ul"></i></span>
</div>
<input type="text" class="form-control" name="name" value="<?= $custom_link_name ?>" maxlength="200" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Order</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-sort-numeric-down"></i></span>
</div>
<input type="number" class="form-control" name="order" placeholder="Leave blank for no order" value="<?= $custom_link_order ?>">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>URI <strong class="text-danger">*</strong></label> / <span class="text-secondary">Open New Tab</span>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-external-link-alt"></i></span>
</div>
<input type="text" class="form-control" name="uri" placeholder="Enter Link" maxlength="500" value="<?= $custom_link_uri ?>" required>
<div class="input-group-append">
<div class="input-group-text">
<input type="checkbox" name="new_tab" value="1" <?php if ($custom_link_new_tab == 1) { echo "checked"; } ?>>
<input class="form-check-input" type="checkbox" name="new_tab" value="1" <?php if ($custom_link_new_tab == 1) { echo "checked"; } ?>>
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Icon</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-image"></i></span>
</div>
<input type="text" class="form-control" name="icon" placeholder="Icon ex handshake" maxlength="200" value="<?= $custom_link_icon ?>">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Location <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-home"></i></span>
</div>
<select class="form-control select2" name="location" required>
<select class="form-select select2" name="location" required>
<option value="1" <?php if ($custom_link_location === 1) { echo "selected"; } ?> >Main Side Nav</option>
<option value="2" <?php if ($custom_link_location === 2) { echo "selected"; } ?> >Top Nav (Icon Required)</option>
<option value="3" <?php if ($custom_link_location === 3) { echo "selected"; } ?> >Client Portal Nav</option>
@@ -93,8 +79,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="edit_custom_link" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_custom_link" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -7,38 +7,34 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-file-alt mr-2"></i>Creating Document Template</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-file-alt me-2"></i>Creating Document Template</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<input type="text" class="form-control" name="name" placeholder="Template name" maxlength="200">
</div>
<div class="form-group">
<div class="mb-3">
<label>Enter a prompt for the type of IT documentation you want to generate:</label>
<div class="input-group mb-3">
<input type="text" class="form-control" id="aiPrompt" placeholder="e.g. 'A network troubleshooting guide for junior technicians'">
<div class="input-group-append">
<button class="btn btn-info" type="button" id="generateAIContent">
<i class="fa fa-fw fa-magic mr-1"></i>Generate with AI
<i class="fa fa-fw fa-magic me-1"></i>Generate with AI
</button>
</div>
</div>
</div>
<!-- TinyMCE Content -->
<div class="form-group">
<div class="mb-3">
<textarea class="form-control tinymce" name="content"></textarea>
</div>
<div class="form-group">
<div class="mb-3">
<input type="text" class="form-control" name="description" placeholder="Enter a short summary">
</div>
@@ -46,41 +42,55 @@ ob_start();
<div class="modal-footer">
<button type="submit" name="add_document_template" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save template</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_document_template" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Save template</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>
<script>
$(document).ready(function(){
itflowReady(function () {
$('#generateAIContent').on('click', function(){
var prompt = $('#aiPrompt').val().trim();
if(prompt === '') {
var button = document.getElementById('generateAIContent');
var promptField = document.getElementById('aiPrompt');
if (!button || !promptField) {
return;
}
button.addEventListener('click', function () {
var prompt = promptField.value.trim();
if (prompt === '') {
alert('Please enter a prompt.');
return;
}
$('#generateAIContent').prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Generating...');
button.disabled = true;
button.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Generating...';
$.ajax({
url: '/agent/ajax.php?ai_create_document_template', // The PHP script that calls the OpenAI API
fetch('/agent/ajax.php?ai_create_document_template', {
method: 'POST',
data: { prompt: prompt },
dataType: 'html',
success: function(response) {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
credentials: 'same-origin',
body: new URLSearchParams({ prompt: prompt }).toString()
})
.then(function (res) {
if (!res.ok) {
throw new Error('HTTP ' + res.status);
}
return res.text();
})
.then(function (response) {
// Assuming you have exactly one TinyMCE instance on the page
// and it's targeting the .tinymce textarea:
tinymce.activeEditor.setContent(response);
},
error: function() {
})
.catch(function () {
alert('Error generating content. Please try again.');
},
complete: function() {
$('#generateAIContent').prop('disabled', false).html('<i class="fa fa-fw fa-magic mr-1"></i>Generate with AI');
}
});
})
.finally(function () {
button.disabled = false;
button.innerHTML = '<i class="fa fa-fw fa-magic me-1"></i>Generate with AI';
});
});
});
</script>

View File

@@ -14,10 +14,8 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-file-alt mr-2"></i>Editing template: <strong><?= $document_template_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-file-alt me-2"></i>Editing template: <strong><?= $document_template_name ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -25,22 +23,22 @@ ob_start();
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<input type="text" class="form-control" name="name" maxlength="200" value="<?= $document_template_name ?>" placeholder="Name" required>
</div>
<div class="form-group">
<div class="mb-3">
<textarea class="form-control tinymce" name="content"><?= $document_template_content ?></textarea>
</div>
<div class="form-group">
<div class="mb-3">
<input type="text" class="form-control" name="description" value="<?= $document_template_description ?>" placeholder="Short summary">
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_document_template" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save template</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_document_template" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Save template</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -42,10 +42,8 @@ if ($email_status == 0) {
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class='fas fa-fw fa-envelope-open mr-2'></i><strong><?= $email_subject ?></strong></h5>
<button type="button" class="close text-light" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class='fas fa-fw fa-envelope-open me-2'></i><strong><?= $email_subject ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row">

View File

@@ -7,34 +7,30 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-credit-card mr-2"></i>Creating: <strong>Payment Method</strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-credit-card me-2"></i>Creating: <strong>Payment Method</strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-credit-card"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Payment method name" maxlength="200" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<textarea class="form-control" rows="3" name="description" placeholder="Enter a description..." maxlength="250"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_payment_method" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_payment_method" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Create</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -15,34 +15,30 @@ $payment_method_description = escapeHtml($row['payment_method_description']);
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-credit-card mr-2"></i>Editing: <strong><?= $payment_method_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-credit-card me-2"></i>Editing: <strong><?= $payment_method_name ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="payment_method_id" value="<?= $payment_method_id ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-credit-card"></i></span>
</div>
<input type="text" class="form-control" name="name" value="<?= $payment_method_name ?>" placeholder="Payment method name" maxlength="200" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<textarea class="form-control" rows="3" name="description" placeholder="Enter a description..." maxlength="250"><?= $payment_method_description ?></textarea>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_payment_method" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_payment_method" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -6,10 +6,8 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-credit-card mr-2"></i>Add Payment Provider</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-credit-card me-2"></i>Add Payment Provider</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -18,10 +16,10 @@ ob_start();
<ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#pills-details">Details</a>
<a class="nav-link active" data-bs-toggle="pill" href="#pills-details">Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-expense">Expense</a>
<a class="nav-link" data-bs-toggle="pill" href="#pills-expense">Expense</a>
</li>
</ul>
@@ -31,45 +29,37 @@ ob_start();
<div class="tab-pane fade show active" id="pills-details">
<div class="form-group">
<div class="mb-3">
<label>Provider <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-credit-card"></i></span>
</div>
<select class="form-control select2" name="provider">
<select class="form-select select2" name="provider">
<option>Stripe</option>
</select>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Publishable key <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-eye"></i></span>
</div>
<input type="text" class="form-control" name="public_key" placeholder="Publishable API Key (pk_...)" maxlength="250">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Secret key <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-key"></i></span>
</div>
<input type="text" class="form-control" name="private_key" placeholder="Secret API Key (sk_...)" maxlength="250">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Income / Expense Account <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-piggy-bank"></i></span>
</div>
<select class="form-control select2" name="account" required>
<select class="form-select select2" name="account" required>
<option value="">- Select an Account -</option>
<?php
@@ -88,12 +78,10 @@ ob_start();
<small class="form-text text-muted">Should have a seperate account created off the payment provider's name e.g. Stripe</small>
</div>
<div class="form-group">
<div class="mb-3">
<label>Threshold</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
</div>
<input type="text" class="form-control" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,2}" name="threshold" placeholder="1000.00">
</div>
<small class="form-text text-muted">Will not show as an option at Checkout if invoice amount is above this number, 0 disables the threshold check.</small>
@@ -107,20 +95,18 @@ ob_start();
Payment Processing Fee Expenses get reconciled nighly via the cron
</div>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" name="enable_expense" checked value="1" id="enableExpenseSwitch">
<label class="custom-control-label" for="enableExpenseSwitch">Enable Expense</label>
<div class="mb-3">
<div class="form-check form-switch">
<input type="checkbox" class="form-check-input" name="enable_expense" checked value="1" id="enableExpenseSwitch">
<label class="form-check-label" for="enableExpenseSwitch">Enable Expense</label>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Payment Provider Vendor <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
</div>
<select class="form-control select2" name="expense_vendor" required>
<select class="form-select select2" name="expense_vendor" required>
<option value="0">Expense Disabled</option>
<?php
@@ -139,13 +125,11 @@ ob_start();
<small class="form-text text-muted">Payment Privider name e.g. Stripe</small>
</div>
<div class="form-group">
<div class="mb-3">
<label>Expense Category <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-list"></i></span>
</div>
<select class="form-control select2" name="expense_category" required>
<select class="form-select select2" name="expense_category" required>
<option value="">- Select a Category -</option>
<?php
@@ -160,12 +144,10 @@ ob_start();
}
?>
</select>
<div class="input-group-append">
<button class="btn btn-secondary ajax-modal" type="button"
data-modal-url="../admin/modals/category/category_add.php?category=Expense">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
<small class="form-text text-muted">Processing Fee, Credit Card Fee etc</small>
</div>
@@ -174,8 +156,8 @@ ob_start();
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_payment_provider" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Add</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_payment_provider" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Add</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -22,23 +22,22 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-credit-card mr-2"></i>Editing: <strong><?= $provider_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-credit-card me-2"></i>Editing: <strong><?= $provider_name ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="provider_id" value="<?= $provider_id ?>">
<input type="hidden" name="provider" value="<?= $provider_name ?>">
<div class="modal-body">
<ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#pills-details">Details</a>
<a class="nav-link active" data-bs-toggle="pill" href="#pills-details">Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-expense">Expense</a>
<a class="nav-link" data-bs-toggle="pill" href="#pills-expense">Expense</a>
</li>
</ul>
@@ -48,33 +47,27 @@ ob_start();
<div class="tab-pane fade show active" id="pills-details">
<div class="form-group">
<div class="mb-3">
<label>Publishable key <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-eye"></i></span>
</div>
<input type="text" class="form-control" name="public_key" placeholder="Publishable API Key (pk_...)" maxlength="250" value="<?= $public_key ?>">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Secret key <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-key"></i></span>
</div>
<input type="text" class="form-control" name="private_key" placeholder="Secret API Key (sk_...)" maxlength="250" value="<?= $private_key ?>">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Income / Expense Account <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-piggy-bank"></i></span>
</div>
<select class="form-control select2" name="account" required>
<select class="form-select select2" name="account" required>
<option value="">- Select an Account -</option>
<?php
@@ -93,12 +86,10 @@ ob_start();
<small class="form-text text-muted">Should have a seperate account created off the payment provider's name e.g. Stripe</small>
</div>
<div class="form-group">
<div class="mb-3">
<label>Threshold</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
</div>
<input type="text" class="form-control" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,2}" name="threshold" placeholder="1000.00" value="<?= $threshold ?>">
</div>
<small class="form-text text-muted">Will not show as an option at Checkout if above this number</small>
@@ -112,13 +103,11 @@ ob_start();
Payment Processing Fee Expenses get reconciled nighly via the cron
</div>
<div class="form-group">
<div class="mb-3">
<label>Payment Provider Vendor <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
</div>
<select class="form-control select2" name="expense_vendor" required>
<select class="form-select select2" name="expense_vendor" required>
<option value="0">Expense Disabled</option>
<?php
@@ -139,13 +128,11 @@ ob_start();
<small class="form-text text-muted">Payment Privider name e.g. Stripe</small>
</div>
<div class="form-group">
<div class="mb-3">
<label>Expense Category <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-list"></i></span>
</div>
<select class="form-control select2" name="expense_category" required>
<select class="form-select select2" name="expense_category" required>
<option value="">- Select a Category -</option>
<?php
@@ -160,12 +147,10 @@ ob_start();
}
?>
</select>
<div class="input-group-append">
<button class="btn btn-secondary ajax-modal" type="button"
data-modal-url="../admin/modals/category/category_add.php?category=Expense">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
<small class="form-text text-muted">Processing Fee, Credit Card Fee etc</small>
</div>
@@ -173,8 +158,8 @@ ob_start();
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_payment_provider" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_payment_provider" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -6,39 +6,33 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-project-diagram mr-2"></i>Creating Project Template</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-project-diagram me-2"></i>Creating Project Template</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Project Template Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-project-diagram"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Project Template Name" maxlength="255" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
<input type="text" class="form-control" name="description" placeholder="Description">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_project_template" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_project_template" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Create</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -14,32 +14,26 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-project-diagram mr-2"></i>Editing Project Template: <strong><?= $project_template_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-project-diagram me-2"></i>Editing Project Template: <strong><?= $project_template_name ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="project_template_id" value="<?= $project_template_id ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Project Template Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-project-diagram"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Project Template Name" maxlength="255" value="<?= $project_template_name ?>" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
<input type="text" class="form-control" name="description" placeholder="Description" value="<?= $project_template_description ?>">
</div>
</div>
@@ -47,8 +41,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="edit_project_template" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_project_template" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -9,10 +9,8 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-life-ring mr-2"></i>Adding Ticket Template</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-life-ring me-2"></i>Adding Ticket Template</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -20,13 +18,11 @@ ob_start();
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Ticket Template <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-life-ring"></i></span>
</div>
<select class="form-control select2" name="ticket_template_id" required>
<select class="form-select select2" name="ticket_template_id" required>
<option value="">- Select a Ticket Template -</option>
<?php
@@ -51,12 +47,10 @@ ob_start();
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Order</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-sort-numeric-down"></i></span>
</div>
<input type="text" class="form-control" name="order" value="1">
</div>
</div>
@@ -64,8 +58,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="add_ticket_template_to_project_template" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Add</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_ticket_template_to_project_template" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Add</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -6,10 +6,8 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-user-shield mr-2"></i>New Role</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-user-shield me-2"></i>New Role</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" enctype="multipart/form-data" autocomplete="off">
@@ -19,10 +17,10 @@ ob_start();
<ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#pills-role-details">Details</a>
<a class="nav-link active" data-bs-toggle="pill" href="#pills-role-details">Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-role-permissions">Permissions</a>
<a class="nav-link" data-bs-toggle="pill" href="#pills-role-permissions">Permissions</a>
</li>
</ul>
@@ -33,39 +31,35 @@ ob_start();
<!-- DETAILS TAB -->
<div class="tab-pane fade show active" id="pills-role-details">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user-shield"></i></span>
</div>
<input type="text" class="form-control" name="role_name" placeholder="Role Name" maxlength="200" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Description <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-chevron-right"></i></span>
</div>
<input type="text" class="form-control" name="role_description" placeholder="Role Description" maxlength="200" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Admin Access <strong class="text-danger">*</strong></label>
<div class="custom-control custom-radio mb-2">
<input type="radio" class="custom-control-input" id="admin_no" name="role_is_admin" value="0" checked required>
<label class="custom-control-label" for="admin_no">
<div class="form-check mb-2">
<input type="radio" class="form-check-input" id="admin_no" name="role_is_admin" value="0" checked required>
<label class="form-check-label" for="admin_no">
No - use permissions on the next tab
</label>
</div>
<div class="custom-control custom-radio">
<input type="radio" class="custom-control-input" id="admin_yes" name="role_is_admin" value="1" required>
<label class="custom-control-label" for="admin_yes">
<div class="form-check">
<input type="radio" class="form-check-input" id="admin_yes" name="role_is_admin" value="1" required>
<label class="form-check-label" for="admin_yes">
Yes - this role should have full admin access
</label>
</div>
@@ -98,14 +92,13 @@ ob_start();
$group_id = "perm_group_$module_id";
?>
<div class="form-group">
<div class="mb-3">
<label><?= $module_name_display_safe ?> <strong class="text-danger">*</strong></label>
<div class="btn-group btn-group-toggle btn-block" data-toggle="buttons" role="group"
<div class="btn-group w-100" role="group"
aria-label="Permissions for <?= $module_name_display_safe ?>">
<label class="btn btn-outline-secondary btn-sm active" title="No Access">
<input
<input class="btn-check"
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_0"
@@ -114,41 +107,34 @@ ob_start();
checked
required
>
None
</label>
<label class="btn btn-outline-secondary btn-sm" title="No Access" for="<?= $group_id ?>_0">None</label>
<label class="btn btn-outline-primary btn-sm" title="Viewing Only">
<input
<input class="btn-check"
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_1"
value="1"
autocomplete="off"
>
<i class="fas fa-fw fa-eye mr-1"></i>Read
</label>
<label class="btn btn-outline-primary btn-sm" title="Viewing Only" for="<?= $group_id ?>_1"><i class="fas fa-fw fa-eye me-1"></i>Read</label>
<label class="btn btn-outline-warning btn-sm" title="Read, Edit, Archive">
<input
<input class="btn-check"
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_2"
value="2"
autocomplete="off"
>
<i class="fas fa-fw fa-edit mr-1"></i>Modify
</label>
<label class="btn btn-outline-warning btn-sm" title="Read, Edit, Archive" for="<?= $group_id ?>_2"><i class="fas fa-fw fa-edit me-1"></i>Modify</label>
<label class="btn btn-outline-danger btn-sm" title="Read, Edit, Archive, Delete">
<input
<input class="btn-check"
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_3"
value="3"
autocomplete="off"
>
<i class="fas fa-fw fa-trash mr-1"></i>Full
</label>
<label class="btn btn-outline-danger btn-sm" title="Read, Edit, Archive, Delete" for="<?= $group_id ?>_3"><i class="fas fa-fw fa-trash me-1"></i>Full</label>
</div>
@@ -164,10 +150,10 @@ ob_start();
<div class="modal-footer">
<button type="submit" name="add_role" class="btn btn-primary text-bold">
<i class="fas fa-check mr-2"></i>Create
<i class="fas fa-check me-2"></i>Create
</button>
<button type="button" class="btn btn-light" data-dismiss="modal">
<i class="fas fa-times mr-2"></i>Cancel
<button type="button" class="btn btn-light" data-bs-dismiss="modal">
<i class="fas fa-times me-2"></i>Cancel
</button>
</div>
</form>

View File

@@ -35,11 +35,9 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-user-shield mr-2"></i>Editing role:
<h5 class="modal-title"><i class="fas fa-fw fa-user-shield me-2"></i>Editing role:
<strong><?= $role_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" enctype="multipart/form-data" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -47,11 +45,11 @@ ob_start();
<div class="modal-body">
<ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#pills-role-details">Details</a>
<a class="nav-link active" data-bs-toggle="pill" href="#pills-role-details">Details</a>
</li>
<?php if (!$role_admin) { ?>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-role-permissions">Permissions</a>
<a class="nav-link" data-bs-toggle="pill" href="#pills-role-permissions">Permissions</a>
</li>
<?php } ?>
</ul>
@@ -62,41 +60,37 @@ ob_start();
<div class="tab-pane fade show active" id="pills-role-details">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user-shield"></i></span>
</div>
<input type="text" class="form-control" name="role_name" placeholder="Role Name" maxlength="200" value="<?= $role_name ?>" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Description <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-chevron-right"></i></span>
</div>
<input type="text" class="form-control" name="role_description" placeholder="Role Description" maxlength="200" value="<?= $role_description ?>" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Admin Access <strong class="text-danger">*</strong></label>
<div class="custom-control custom-radio mb-2">
<input type="radio" class="custom-control-input" id="admin_yes" name="role_is_admin" value="1"
<div class="form-check mb-2">
<input type="radio" class="form-check-input" id="admin_yes" name="role_is_admin" value="1"
<?php if ($role_admin) { echo 'checked'; } ?> required>
<label class="custom-control-label" for="admin_yes">
<label class="form-check-label" for="admin_yes">
Yes - this role should have full admin access
</label>
</div>
<div class="custom-control custom-radio">
<input type="radio" class="custom-control-input" id="admin_no" name="role_is_admin" value="0"
<div class="form-check">
<input type="radio" class="form-check-input" id="admin_no" name="role_is_admin" value="0"
<?php if (!$role_admin) { echo 'checked'; } ?> required>
<label class="custom-control-label" for="admin_no">
<label class="form-check-label" for="admin_no">
No - use permissions on the next tab
</label>
</div>
@@ -128,17 +122,16 @@ ob_start();
}
?>
<div class="form-group">
<div class="mb-3">
<label> <?= $module_name_display ?> <strong class="text-danger">*</strong></label>
<?php
$field_name = "$module_id##$module_name";
$group_id = "perm_group_$module_id";
?>
<div class="btn-group btn-group-toggle btn-block" data-toggle="buttons" role="group" aria-label="Permissions for <?= $module_name_display ?>">
<div class="btn-group w-100" role="group" aria-label="Permissions for <?= $module_name_display ?>">
<label class="btn btn-outline-secondary btn-sm <?php if ($module_permission == 0) { echo 'active'; } ?>" title="No Access">
<input
<input class="btn-check"
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_0"
@@ -147,11 +140,9 @@ ob_start();
<?php if ($module_permission == 0) { echo 'checked'; } ?>
required
>
None
</label>
<label class="btn btn-outline-secondary btn-sm" title="No Access" for="<?= $group_id ?>_0">None</label>
<label class="btn btn-outline-primary btn-sm <?php if ($module_permission == 1) { echo 'active'; } ?>" title="Viewing Only">
<input
<input class="btn-check"
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_1"
@@ -159,11 +150,9 @@ ob_start();
autocomplete="off"
<?php if ($module_permission == 1) { echo 'checked'; } ?>
>
<i class="fas fa-fw fa-eye mr-1"></i>Read
</label>
<label class="btn btn-outline-primary btn-sm" title="Viewing Only" for="<?= $group_id ?>_1"><i class="fas fa-fw fa-eye me-1"></i>Read</label>
<label class="btn btn-outline-warning btn-sm <?php if ($module_permission == 2) { echo 'active'; } ?>" title="Read, Edit, Archive">
<input
<input class="btn-check"
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_2"
@@ -171,11 +160,9 @@ ob_start();
autocomplete="off"
<?php if ($module_permission == 2) { echo 'checked'; } ?>
>
<i class="fas fa-fw fa-edit mr-1"></i>Modify
</label>
<label class="btn btn-outline-warning btn-sm" title="Read, Edit, Archive" for="<?= $group_id ?>_2"><i class="fas fa-fw fa-edit me-1"></i>Modify</label>
<label class="btn btn-outline-danger btn-sm <?php if ($module_permission == 3) { echo 'active'; } ?>" title="Read, Edit, Archive, Delete">
<input
<input class="btn-check"
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_3"
@@ -183,8 +170,7 @@ ob_start();
autocomplete="off"
<?php if ($module_permission == 3) { echo 'checked'; } ?>
>
<i class="fas fa-fw fa-trash mr-1"></i>Full
</label>
<label class="btn btn-outline-danger btn-sm" title="Read, Edit, Archive, Delete" for="<?= $group_id ?>_3"><i class="fas fa-fw fa-trash me-1"></i>Full</label>
</div>
@@ -201,8 +187,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="edit_role" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_role" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -0,0 +1,40 @@
<?php
require_once '../../includes/modal_header.php';
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-calendar-times me-2"></i>New Closure Day</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="mb-3">
<label>Date <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-calendar-day"></i></span>
<input type="date" class="form-control" name="holiday_date" required autofocus>
</div>
</div>
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-tag"></i></span>
<input type="text" class="form-control" name="holiday_name" placeholder="e.g. Christmas Day, Office closed for move" maxlength="200" required>
</div>
</div>
<small class="text-muted">SLA clocks do not run on this day at all, the same as a day outside your business days. Open tickets have their targets recalculated when you save.</small>
</div>
<div class="modal-footer">
<button type="submit" name="add_holiday" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Add</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>
<?php
require_once '../../../includes/modal_footer.php';

View File

@@ -4,59 +4,49 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-stopwatch mr-2"></i>New SLA</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-stopwatch me-2"></i>New SLA</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-stopwatch"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="SLA name" maxlength="200" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-comment"></i></span>
</div>
<input type="text" class="form-control" name="description" placeholder="Short description" maxlength="500">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Response target (business minutes) <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-reply"></i></span>
</div>
<input type="number" class="form-control" name="response_minutes" min="1" placeholder="e.g. 60" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Resolution target (business minutes)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-flag-checkered"></i></span>
</div>
<input type="number" class="form-control" name="resolution_minutes" min="1" placeholder="Leave empty for response-only">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_sla" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_sla" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Create</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -14,52 +14,42 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-stopwatch mr-2"></i>Editing SLA: <strong><?= $sla_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-stopwatch me-2"></i>Editing SLA: <strong><?= $sla_name ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="sla_id" value="<?= $sla_id ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-stopwatch"></i></span>
</div>
<input type="text" class="form-control" name="name" value="<?= $sla_name ?>" maxlength="200" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-comment"></i></span>
</div>
<input type="text" class="form-control" name="description" value="<?= $sla_description ?>" maxlength="500">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Response target (business minutes) <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-reply"></i></span>
</div>
<input type="number" class="form-control" name="response_minutes" min="1" value="<?= $sla_response_minutes ?>" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Resolution target (business minutes)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-flag-checkered"></i></span>
</div>
<input type="number" class="form-control" name="resolution_minutes" min="1" value="<?= $sla_resolution_minutes ?: '' ?>" placeholder="Leave empty for response-only">
</div>
<small class="text-muted">Saving recalculates targets on open tickets using this SLA.</small>
@@ -67,8 +57,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="edit_sla" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_sla" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -17,53 +17,43 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-cube mr-2"></i>New License Template</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-cube me-2"></i>New License Template</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Template Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-cube"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Software name" maxlength="200" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Version</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-cube"></i></span>
</div>
<input type="text" class="form-control" name="version" placeholder="Software version" maxlength="200">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
<input type="text" class="form-control" name="description" placeholder="Short description">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Type <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-tag"></i></span>
</div>
<select class="form-control select2" name="type" required>
<select class="form-select select2" name="type" required>
<option value="">- Select Type -</option>
<<?php
$sql_software_types_select = mysqli_query($mysqli, "
@@ -81,13 +71,11 @@ ob_start();
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>License Type</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-cube"></i></span>
</div>
<select class="form-control select2" name="license_type">
<select class="form-select select2" name="license_type">
<option value="">- Select a License Type -</option>
<?php foreach($license_types_array as $license_type) { ?>
<option><?= $license_type ?></option>
@@ -100,8 +88,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="add_software_template" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_software_template" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Create</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -29,10 +29,8 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-cube mr-2"></i>Editing template: <strong><?= $software_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-cube me-2"></i>Editing template: <strong><?= $software_name ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -40,43 +38,35 @@ ob_start();
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Template Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-cube"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Software name" maxlength="200" value="<?= $software_name ?>" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Version</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-cube"></i></span>
</div>
<input type="text" class="form-control" name="version" placeholder="Software version" maxlength="200" value="<?= $software_version ?>">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
<input type="text" class="form-control" name="description" placeholder="Short description" value="<?= $software_description ?>">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Type <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-tag"></i></span>
</div>
<select class="form-control select2" name="type" required>
<select class="form-select select2" name="type" required>
<option value="">- Select Type -</option>
<<?php
$sql_software_types_select = mysqli_query($mysqli, "
@@ -96,13 +86,11 @@ ob_start();
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>License Type</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-cube"></i></span>
</div>
<select class="form-control select2" name="license_type">
<select class="form-select select2" name="license_type">
<option value="">- Select a License Type -</option>
<?php foreach($license_types_array as $license_type_select) { ?>
<option <?php if($license_type_select == $software_license_type){ echo "selected"; } ?>><?= $license_type_select ?></option>
@@ -115,8 +103,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="edit_software_template" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_software_template" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -24,22 +24,18 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-tag mr-2"></i>New <strong><?= $type_display ?></strong> Tag</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-tag me-2"></i>New <strong><?= $type_display ?></strong> Tag</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="type" value="<?= $type ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-tag"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Tag name" maxlength="200" required autofocus>
</div>
</div>
@@ -50,13 +46,11 @@ ob_start();
<?php } else { ?>
<div class="form-group">
<div class="mb-3">
<label>Type <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-th"></i></span>
</div>
<select class="form-control select2" name="type" required>
<select class="form-select select2" name="type" required>
<option value="">- Type -</option>
<option value="1">Client Tag</option>
<option value="2">Location Tag</option>
@@ -69,30 +63,26 @@ ob_start();
<?php } ?>
<div class="form-group">
<div class="mb-3">
<label>Color <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-paint-brush"></i></span>
</div>
<input type="color" class="form-control col-3" name="color" required>
<input type="color" class="form-control form-control-color" name="color" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Icon</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-image"></i></span>
</div>
<input type="text" class="form-control" name="icon" placeholder="Icon ex handshake" maxlength="200">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_tag" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Create Tag</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_tag" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Create Tag</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -29,10 +29,8 @@ if ($tag_type == 1) {
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-tag mr-2"></i><?= $tag_type_display ?> Tag: <strong><?= $tag_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-tag me-2"></i><?= $tag_type_display ?> Tag: <strong><?= $tag_name ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -40,40 +38,34 @@ ob_start();
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-tag"></i></span>
</div>
<input type="text" class="form-control" name="name" maxlength="200" value="<?= $tag_name ?>" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Color <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-paint-brush"></i></span>
</div>
<input type="color" class="form-control col-3" name="color" value="<?= $tag_color ?>" required>
<input type="color" class="form-control form-control-color" name="color" value="<?= $tag_color ?>" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Icon</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-image"></i></span>
</div>
<input type="text" class="form-control" name="icon" placeholder="Icon ex handshake" maxlength="200" value="<?= $tag_icon ?>">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_tag" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Save changes</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_tag" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Save changes</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -7,27 +7,25 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-balance-scale mr-2"></i>New Tax</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-balance-scale me-2"></i>New Tax</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<input type="text" class="form-control" name="name" placeholder="Tax name" maxlength="200" required autofocus>
</div>
<div class="form-group">
<div class="mb-3">
<label>Percent <strong class="text-danger">*</strong></label>
<input type="number" min="0" step="any" class="form-control col-md-4" name="percent">
<input type="number" min="0" step="any" class="form-control w-25" name="percent">
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_tax" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_tax" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Create</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -14,10 +14,8 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-balance-scale mr-2"></i>Editing tax: <strong><?= $tax_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-balance-scale me-2"></i>Editing tax: <strong><?= $tax_name ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -25,20 +23,20 @@ ob_start();
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<input type="text" class="form-control" name="name" maxlength="200" value="<?= $tax_name ?>" required>
</div>
<div class="form-group">
<div class="mb-3">
<label>Percent <strong class="text-danger">*</strong></label>
<input type="number" min="0" step="any" class="form-control col-md-4" name="percent" value="<?= $tax_percent ?>">
<input type="number" min="0" step="any" class="form-control w-25" name="percent" value="<?= $tax_percent ?>">
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_tax" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_tax" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -4,43 +4,35 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-info-circle mr-2"></i>New Ticket Status</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-info-circle me-2"></i>New Ticket Status</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-info-circle"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Ticket Status name" maxlength="200" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Color <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-paint-brush"></i></span>
</div>
<input type="color" class="form-control col-3" name="color" required>
<input type="color" class="form-control form-control-color" name="color" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>SLA</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-stopwatch"></i></span>
</div>
<select class="form-control select2" name="pauses_sla">
<select class="form-select select2" name="pauses_sla">
<option value="0">Resolution clock keeps running</option>
<option value="1">Pause the resolution clock</option>
</select>
@@ -49,8 +41,8 @@ ob_start();
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_ticket_status" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_ticket_status" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Create</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -12,16 +12,16 @@ $ticket_status_color = escapeHtml($row['ticket_status_color']);
$ticket_status_order = intval($row['ticket_status_order']);
$ticket_status_active = intval($row['ticket_status_active']);
$ticket_status_pauses_sla = intval($row['ticket_status_pauses_sla']);
// Null for a custom status (the admin picks), 1 or 0 for the five built-in ones
$ticket_status_sla_lock = getTicketStatusSlaLock($ticket_status_id);
// Generate the HTML form content using output buffering.
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-info-circle mr-2"></i>Editing Ticket Status: <strong><?= $ticket_status_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-info-circle me-2"></i>Editing Ticket Status: <strong><?= $ticket_status_name ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -29,67 +29,67 @@ ob_start();
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-tag"></i></span>
</div>
<input type="text" class="form-control" name="name" maxlength="200" value="<?= $ticket_status_name ?>" required <?php if ($ticket_status_id <= 5) { echo "readonly"; } ?>>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Color <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-paint-brush"></i></span>
</div>
<input type="color" class="form-control col-3" name="color" value="<?= $ticket_status_color ?>" required>
<input type="color" class="form-control form-control-color" name="color" value="<?= $ticket_status_color ?>" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Order</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-sort-numeric-down"></i></span>
</div>
<input type="number" class="form-control" name="order" placeholder="Leave blank for no order" value="<?= $ticket_status_order ?>">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Status <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-info-circle"></i></span>
</div>
<select class="form-control select2" name="status" required>
<select class="form-select select2" name="status" required>
<option <?php if ($ticket_status_active == 1) { echo "selected"; } ?> value="1">Active</option>
<option <?php if ($ticket_status_active == 0) { echo "selected"; } ?> value="0" <?php if ($ticket_status_id <= 5) { echo "disabled"; } ?>>Inactive</option>
</select>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>SLA</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-stopwatch"></i></span>
</div>
<select class="form-control select2" name="pauses_sla">
<option <?php if ($ticket_status_pauses_sla == 0) { echo "selected "; } ?>value="0">Resolution clock keeps running</option>
<option <?php if ($ticket_status_pauses_sla == 1) { echo "selected "; } ?>value="1">Pause the resolution clock</option>
</select>
<?php if (is_null($ticket_status_sla_lock)) { ?>
<select class="form-select select2" name="pauses_sla">
<option <?php if ($ticket_status_pauses_sla == 0) { echo "selected "; } ?>value="0">Resolution clock keeps running</option>
<option <?php if ($ticket_status_pauses_sla == 1) { echo "selected "; } ?>value="1">Pause the resolution clock</option>
</select>
<?php } else { ?>
<input type="text" class="form-control" value="<?= $ticket_status_sla_lock ? 'Pause the resolution clock' : 'Resolution clock keeps running' ?>" readonly>
<?php } ?>
</div>
<small class="text-muted">Tickets sitting in a paused status never warn or breach on resolution. Time already spent is kept and the deadline moves out when the ticket comes back.</small>
<?php if (is_null($ticket_status_sla_lock)) { ?>
<small class="text-muted">Tickets sitting in a paused status never warn or breach on resolution. Time already spent is kept and the deadline moves out when the ticket comes back.</small>
<?php } elseif ($ticket_status_sla_lock) { ?>
<small class="text-muted">Fixed for built-in statuses. Tickets here never warn or breach on resolution - time already spent is kept and the deadline moves out when the ticket comes back.</small>
<?php } else { ?>
<small class="text-muted">Fixed for built-in statuses. Tickets here are being worked, so their resolution clock runs.</small>
<?php } ?>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_ticket_status" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_ticket_status" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -6,57 +6,47 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-life-ring mr-2"></i>New Ticket Template</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-life-ring me-2"></i>New Ticket Template</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Template Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-life-ring"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Template name" maxlength="200" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Subject</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
<input type="text" class="form-control" name="subject" placeholder="Subject" maxlength="500">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<textarea class="form-control tinymceTicket" name="details"></textarea>
</div>
<div class="form-group">
<div class="mb-3">
<label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
<input type="text" class="form-control" name="description" placeholder="Short description">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Add it to a Project Template?</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-project-diagram"></i></span>
</div>
<select class="form-control select2" name="project_template">
<select class="form-select select2" name="project_template">
<option value="0">- No -</option>
<?php
@@ -72,8 +62,8 @@ ob_start();
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_ticket_template" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create Template</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_ticket_template" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Create Template</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -3,10 +3,8 @@
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-life-ring mr-2"></i>Editing Ticket Template: <?= $ticket_template_name ?></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-life-ring me-2"></i>Editing Ticket Template: <?= $ticket_template_name ?></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -14,44 +12,38 @@
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Template Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-life-ring"></i></span>
</div>
<input type="text" class="form-control" name="name" maxlength="200" value="<?= $ticket_template_name ?>" placeholder="Template name" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Subject</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
<input type="text" class="form-control" name="subject" maxlength="500" value="<?= $ticket_template_subject ?>" placeholder="Subject">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<textarea class="form-control tinymceTicket" name="details"><?= $ticket_template_details ?></textarea>
</div>
<div class="form-group">
<div class="mb-3">
<label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
<input type="text" class="form-control" name="description" value="<?= $ticket_template_description ?>" placeholder="Short description">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_ticket_template" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_ticket_template" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>
</div>

View File

@@ -18,10 +18,8 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-tasks mr-2"></i>Editing task</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fa fa-fw fa-tasks me-2"></i>Editing task</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -29,22 +27,18 @@ ob_start();
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-tag"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Name the task" maxlength="255" value="<?= $task_template_name ?>" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Estimated Completion Time <span class="text-secondary">(Minutes)</span></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-clock"></i></span>
</div>
<input type="number" class="form-control" name="completion_estimate" placeholder="Estimated time to complete task in mins" value="<?= $task_template_completion_estimate ?>">
</div>
</div>
@@ -52,8 +46,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="edit_ticket_template_task" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_ticket_template_task" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -6,10 +6,8 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-user-plus mr-2"></i>New User</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-user-plus me-2"></i>New User</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" enctype="multipart/form-data" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -17,10 +15,10 @@ ob_start();
<ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#pills-user-details">Details</a>
<a class="nav-link active" data-bs-toggle="pill" href="#pills-user-details">Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-user-access">Restrict Access</a>
<a class="nav-link" data-bs-toggle="pill" href="#pills-user-access">Restrict Access</a>
</li>
</ul>
@@ -30,49 +28,37 @@ ob_start();
<div class="tab-pane fade show active" id="pills-user-details">
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Full Name" maxlength="200" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Email <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-envelope"></i></span>
</div>
<input type="email" class="form-control" name="email" placeholder="Email Address" maxlength="200" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Password <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-lock"></i></span>
</div>
<input type="password" class="form-control" data-toggle="password" name="password" id="password" placeholder="Enter a Password" autocomplete="new-password" minlength="8" required>
<div class="input-group-append">
<span class="input-group-text"><i class="fa fa-fw fa-eye"></i></span>
</div>
<div class="input-group-append">
<span class="btn btn-default"><i class="fa fa-fw fa-question" onclick="generatePassword()"></i></span>
</div>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Role <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user-shield"></i></span>
</div>
<select class="form-control select2" name="role" required>
<select class="form-select select2" name="role" required>
<option value="">- Role -</option>
<?php
$sql_user_roles = mysqli_query($mysqli, "SELECT role_id, role_name FROM user_roles WHERE role_archived_at IS NULL");
@@ -87,24 +73,24 @@ ob_start();
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Avatar</label>
<input type="file" class="form-control-file" accept="image/*" name="file">
<input type="file" class="form-control" accept="image/*" name="file">
</div>
<div class="form-group" <?php if(empty($config_smtp_host)) { echo "hidden"; } ?>>
<div class="custom-control custom-checkbox">
<input class="custom-control-input" type="checkbox" id="sendEmailCheckBox" name="send_email" value="" checked>
<label for="sendEmailCheckBox" class="custom-control-label">
<div class="mb-3" <?php if(empty($config_smtp_host)) { echo "hidden"; } ?>>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="sendEmailCheckBox" name="send_email" value="" checked>
<label for="sendEmailCheckBox" class="form-check-label">
Send user e-mail with login details?
</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input class="custom-control-input" type="checkbox" id="forceMFACheckBox" name="force_mfa" value=1>
<label for="forceMFACheckBox" class="custom-control-label">
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="forceMFACheckBox" name="force_mfa" value=1>
<label for="forceMFACheckBox" class="form-check-label">
Force MFA
</label>
</div>
@@ -119,7 +105,7 @@ ob_start();
</div>
<div class="mb-2">
<small class="text-muted mr-2">Set all:</small>
<small class="text-muted me-2">Set all:</small>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="document.querySelectorAll('#accessTable .perm-none').forEach(r => r.checked = true);">No Rule</button>
<button type="button" class="btn btn-sm btn-outline-success" onclick="document.querySelectorAll('#accessTable .perm-allow').forEach(r => r.checked = true);">Allow</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="document.querySelectorAll('#accessTable .perm-deny').forEach(r => r.checked = true);">Deny</button>
@@ -147,13 +133,13 @@ ob_start();
<tr>
<td class="align-middle"><?= $client_name ?></td>
<td class="text-center align-middle">
<input type="radio" class="perm-none" name="client_permission[<?= $client_id ?>]" value="" checked>
<input type="radio" class="form-check-input perm-none" name="client_permission[<?= $client_id ?>]" value="" checked>
</td>
<td class="text-center align-middle">
<input type="radio" class="perm-allow" name="client_permission[<?= $client_id ?>]" value="allow">
<input type="radio" class="form-check-input perm-allow" name="client_permission[<?= $client_id ?>]" value="allow">
</td>
<td class="text-center align-middle">
<input type="radio" class="perm-deny" name="client_permission[<?= $client_id ?>]" value="deny">
<input type="radio" class="form-check-input perm-deny" name="client_permission[<?= $client_id ?>]" value="deny">
</td>
</tr>
@@ -169,15 +155,15 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="add_user" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_user" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Create</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>
<script>
function generatePassword() {
jQuery.get(
itflowGet(
"/agent/ajax.php", {
get_readable_pass: 'true'
},

View File

@@ -18,16 +18,14 @@ ob_start();
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="row col-7 offset-4">
<div class="input-group">
<div class="input-group-prepend">
<input type="password" class="form-control" placeholder="Enter your account password to continue" name="admin_password" required>
</div>
</div>
</div>
<br>
<button class="btn btn-danger" type="submit" name="ir_reset_user_password"><i class="fas fa-fw fa-key mr-2"></i>Reset passwords</button>
<button class="btn btn-danger" type="submit" name="ir_reset_user_password"><i class="fas fa-fw fa-key me-2"></i>Reset passwords</button>
</form>
</div>
<button type="button" class="btn btn-outline-secondary btn-lg px-5 mr-4" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-secondary btn-lg px-5 me-4" data-bs-dismiss="modal">Cancel</button>
</div>

View File

@@ -26,11 +26,9 @@ $recurring_ticket_count = mysqli_num_rows($sql_related_recurring_tickets);
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-user-slash mr-2"></i>Archiving user:
<h5 class="modal-title"><i class="fas fa-fw fa-user-slash me-2"></i>Archiving user:
<strong><?= $user_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -50,13 +48,11 @@ ob_start();
</center>
<div class="form-group">
<div class="mb-3">
<label>Reassign <?= $ticket_count ?> Open Tickets and <?= $recurring_ticket_count ?> Recurring Tickets To:</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user"></i></span>
</div>
<select class="form-control select2" name="ticket_assign" required>
<select class="form-select select2" name="ticket_assign" required>
<option value="0">No one</option>
<?php
$sql_users = mysqli_query($mysqli, "SELECT user_id, user_name FROM users WHERE user_type = 1 AND user_archived_at IS NULL");
@@ -74,8 +70,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="archive_user" class="btn btn-danger text-bold"><i class="fas fa-archive mr-2"></i>Archive</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="archive_user" class="btn btn-danger text-bold"><i class="fas fa-archive me-2"></i>Archive</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -35,11 +35,9 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-user-edit mr-2"></i>Editing user:
<h5 class="modal-title"><i class="fas fa-fw fa-user-edit me-2"></i>Editing user:
<strong><?= $user_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" enctype="multipart/form-data" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -48,10 +46,10 @@ ob_start();
<ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#pills-user-details<?= $user_id ?>">Details</a>
<a class="nav-link active" data-bs-toggle="pill" href="#pills-user-details<?= $user_id ?>">Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-user-access<?= $user_id ?>">Restrict Access</a>
<a class="nav-link" data-bs-toggle="pill" href="#pills-user-access<?= $user_id ?>">Restrict Access</a>
</li>
</ul>
@@ -72,52 +70,40 @@ ob_start();
<?php } ?>
</center>
<div class="form-group">
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Full Name" maxlength="200"
value="<?= $user_name ?>" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Email <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-envelope"></i></span>
</div>
<input type="email" class="form-control" name="email" placeholder="Email Address" maxlength="200"
value="<?= $user_email ?>" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>New Password</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-lock"></i></span>
</div>
<input type="password" class="form-control" data-toggle="password" name="new_password" id="password"
placeholder="Leave Blank For No Password Change" autocomplete="new-password">
<div class="input-group-append">
<span class="input-group-text"><i class="fa fa-fw fa-eye"></i></span>
</div>
<div class="input-group-append">
<span class="btn btn-default"><i class="fa fa-fw fa-question" onclick="generatePassword()"></i></span>
</div>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Role <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user-shield"></i></span>
</div>
<select class="form-control select2" name="role" required>
<select class="form-select select2" name="role" required>
<?php
$sql_user_roles = mysqli_query($mysqli, "SELECT role_id, role_name FROM user_roles WHERE role_archived_at IS NULL");
while ($row = mysqli_fetch_assoc($sql_user_roles)) {
@@ -132,15 +118,15 @@ ob_start();
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Avatar</label>
<input type="file" class="form-control-file" accept="image/*" name="file">
<input type="file" class="form-control" accept="image/*" name="file">
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input class="custom-control-input" type="checkbox" id="forceMFACheckBox<?= $user_id ?>" name="force_mfa" value="1" <?php if($user_config_force_mfa == 1){ echo "checked"; } ?>>
<label for="forceMFACheckBox<?= $user_id ?>" class="custom-control-label">
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="forceMFACheckBox<?= $user_id ?>" name="force_mfa" value="1" <?php if($user_config_force_mfa == 1){ echo "checked"; } ?>>
<label for="forceMFACheckBox<?= $user_id ?>" class="form-check-label">
Force MFA
</label>
</div>
@@ -148,13 +134,11 @@ ob_start();
<?php if (!empty($user_token)) { ?>
<div class="form-group">
<div class="mb-3">
<label>2FA</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-id-card"></i></span>
</div>
<select class="form-control" name="2fa">
<select class="form-select" name="2fa">
<option value="">Keep enabled</option>
<option value="disable">Disable</option>
</select>
@@ -171,7 +155,7 @@ ob_start();
</div>
<div class="mb-2">
<small class="text-muted mr-2">Set all:</small>
<small class="text-muted me-2">Set all:</small>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="document.querySelectorAll('#accessTable<?= $user_id ?> .perm-none').forEach(r => r.checked = true);">No Rule</button>
<button type="button" class="btn btn-sm btn-outline-success" onclick="document.querySelectorAll('#accessTable<?= $user_id ?> .perm-allow').forEach(r => r.checked = true);">Allow</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="document.querySelectorAll('#accessTable<?= $user_id ?> .perm-deny').forEach(r => r.checked = true);">Deny</button>
@@ -201,13 +185,13 @@ ob_start();
<tr>
<td class="align-middle"><?= $client_name_select ?></td>
<td class="text-center align-middle">
<input type="radio" class="perm-none" name="client_permission[<?= $client_id_select ?>]" value="" <?php if (!$client_is_allow && !$client_is_deny) { echo "checked"; } ?>>
<input type="radio" class="form-check-input perm-none" name="client_permission[<?= $client_id_select ?>]" value="" <?php if (!$client_is_allow && !$client_is_deny) { echo "checked"; } ?>>
</td>
<td class="text-center align-middle">
<input type="radio" class="perm-allow" name="client_permission[<?= $client_id_select ?>]" value="allow" <?php if ($client_is_allow) { echo "checked"; } ?>>
<input type="radio" class="form-check-input perm-allow" name="client_permission[<?= $client_id_select ?>]" value="allow" <?php if ($client_is_allow) { echo "checked"; } ?>>
</td>
<td class="text-center align-middle">
<input type="radio" class="perm-deny" name="client_permission[<?= $client_id_select ?>]" value="deny" <?php if ($client_is_deny) { echo "checked"; } ?>>
<input type="radio" class="form-check-input perm-deny" name="client_permission[<?= $client_id_select ?>]" value="deny" <?php if ($client_is_deny) { echo "checked"; } ?>>
</td>
</tr>
@@ -222,8 +206,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="edit_user" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="edit_user" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Save</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>
@@ -231,7 +215,7 @@ ob_start();
function generatePassword() {
// Send a GET request to ajax.php as ajax.php?get_readable_pass=true
jQuery.get(
itflowGet(
"/agent/ajax.php", {
get_readable_pass: 'true'
},

View File

@@ -22,10 +22,8 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-download mr-2"></i>Export Users</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-download me-2"></i>Export Users</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<?php exportTabsNav(); ?>
@@ -37,23 +35,19 @@ ob_start();
<?php exportTabsFiltersOpen(); ?>
<div class="form-group">
<div class="mb-3">
<label>Search</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-search"></i></span>
</div>
<input type="text" class="form-control" name="q" value="<?= stripslashes(escapeHtml($q_filter)) ?>" placeholder="Name or email">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Archived</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-archive"></i></span>
</div>
<select class="form-control select2" name="archived">
<select class="form-select select2" name="archived">
<option <?php if (!$archived_filter) { echo "selected"; } ?> value="0">Active only</option>
<option <?php if ($archived_filter) { echo "selected"; } ?> value="1">Archived only</option>
</select>

View File

@@ -7,31 +7,25 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-user-plus"></i>Invite User</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" enctype="multipart/form-data" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">
<div class="mb-3">
<label>Email <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-envelope"></i></span>
</div>
<input type="email" class="form-control" name="email" placeholder="Email Address" maxlength="200" required>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Role <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user-shield"></i></span>
</div>
<select class="form-control select2" name="role" required>
<select class="form-select select2" name="role" required>
<option value="">- Role -</option>
<!-- //TODO: Pull from roles -->
</select>
@@ -40,8 +34,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="invite_user" class="btn btn-primary text-bold"><i class="fas fa-paper-plane mr-2"></i>Send Invite</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="invite_user" class="btn btn-primary text-bold"><i class="fas fa-paper-plane me-2"></i>Send Invite</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -18,11 +18,9 @@ $user_role_id = intval($row['user_role_id']);
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-redo-alt mr-2"></i>Restoring user:
<h5 class="modal-title"><i class="fas fa-fw fa-redo-alt me-2"></i>Restoring user:
<strong><?= $user_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -41,27 +39,21 @@ ob_start();
<?php } ?>
</center>
<div class="form-group">
<div class="mb-3">
<label>Set a New Password</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-lock"></i></span>
</div>
<input type="password" class="form-control" data-toggle="password" name="new_password"
placeholder="Enter a new password" autocomplete="new-password" required>
<div class="input-group-append">
<span class="input-group-text"><i class="fa fa-fw fa-eye"></i></span>
</div>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Role <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user-shield"></i></span>
</div>
<select class="form-control select2" name="role" required>
<select class="form-select select2" name="role" required>
<?php
$sql_user_roles = mysqli_query($mysqli, "SELECT role_id, role_name FROM user_roles WHERE role_archived_at IS NULL");
while ($row = mysqli_fetch_assoc($sql_user_roles)) {
@@ -78,8 +70,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="restore_user" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Restore</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="restore_user" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Restore</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -7,10 +7,8 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-building mr-2"></i>New Vendor Template</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-building me-2"></i>New Vendor Template</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -19,13 +17,13 @@ ob_start();
<ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#pills-details">Details</a>
<a class="nav-link active" data-bs-toggle="pill" href="#pills-details">Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-support">Support</a>
<a class="nav-link" data-bs-toggle="pill" href="#pills-support">Support</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-notes">Notes</a>
<a class="nav-link" data-bs-toggle="pill" href="#pills-notes">Notes</a>
</li>
</ul>
@@ -35,42 +33,34 @@ ob_start();
<div class="tab-pane fade show active" id="pills-details">
<div class="form-group">
<div class="mb-3">
<label>Vendor Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Vendor Name" maxlength="200" required autofocus>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
<input type="text" class="form-control" name="description" placeholder="Description" maxlength="200">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Account Number</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-fingerprint"></i></span>
</div>
<input type="text" class="form-control" name="account_number" placeholder="Account number" maxlength="200">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Account Manager</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user"></i></span>
</div>
<input type="text" class="form-control" name="contact_name" placeholder="Account manager's name" maxlength="200">
</div>
</div>
@@ -80,71 +70,59 @@ ob_start();
<div class="tab-pane fade" id="pills-support">
<label>Support Phone / <span class="text-secondary">Extension</span></label>
<div class="form-row">
<div class="row g-2">
<div class="col-9">
<div class="form-group">
<div class="mb-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-phone"></i></span>
</div>
<input type="tel" class="form-control col-2" name="phone_country_code" placeholder="+" maxlength="4">
<input type="tel" class="form-control" name="phone" placeholder="Phone Number" maxlength="200">
<input type="hidden" name="phone_country_code">
<input type="tel" class="form-control" name="phone" placeholder="Phone Number" maxlength="200" data-itflow-phone="phone_country_code">
</div>
</div>
</div>
<div class="col-3">
<div class="form-group">
<div class="mb-3">
<input type="text" class="form-control" name="extension" placeholder="ext." maxlength="200">
</div>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Support Hours</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
</div>
<input type="text" class="form-control" name="hours" placeholder="Support Hours" maxlength="200">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Support Email</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-envelope"></i></span>
</div>
<input type="email" class="form-control" name="email" placeholder="Support Email" maxlength="200">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Support Website URL</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-globe"></i></span>
</div>
<input type="text" class="form-control" name="website" placeholder="Do not include http(s)://" maxlength="200">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Pin/Code</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-key"></i></span>
</div>
<input type="text" class="form-control" name="code" placeholder="Access Code or Pin" maxlength="200">
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>SLA</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-handshake"></i></span>
</div>
<input type="text" class="form-control" name="sla" placeholder="SLA Response Time" maxlength="200">
</div>
</div>
@@ -153,7 +131,7 @@ ob_start();
<div class="tab-pane fade" id="pills-notes">
<div class="form-group">
<div class="mb-3">
<textarea class="form-control" rows="8" placeholder="Enter some notes" name="notes"></textarea>
</div>
@@ -163,8 +141,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="add_vendor_template" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create Template</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_vendor_template" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Create Template</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -14,7 +14,7 @@ $vendor_description = escapeHtml($row['vendor_template_description']);
$vendor_account_number = escapeHtml($row['vendor_template_account_number']);
$vendor_contact_name = escapeHtml($row['vendor_template_contact_name']);
$vendor_phone_country_code = intval($row['vendor_template_phone_country_code']);
$vendor_phone = formatPhoneNumber($row['vendor_template_phone'], $vendor_phone_country_code);
$vendor_phone = formatPhoneNumber($row['vendor_template_phone'], $vendor_phone_country_code, false);
$vendor_extension = escapeHtml($row['vendor_template_extension']);
$vendor_email = escapeHtml($row['vendor_template_email']);
$vendor_website = escapeHtml($row['vendor_template_website']);
@@ -28,10 +28,8 @@ ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-building mr-2"></i>Editing vendor template: <strong><?= $vendor_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title"><i class="fas fa-fw fa-building me-2"></i>Editing vendor template: <strong><?= $vendor_name ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
@@ -41,13 +39,13 @@ ob_start();
<ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#pills-details<?= $vendor_template_id ?>">Details</a>
<a class="nav-link active" data-bs-toggle="pill" href="#pills-details<?= $vendor_template_id ?>">Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-support<?= $vendor_template_id ?>">Support</a>
<a class="nav-link" data-bs-toggle="pill" href="#pills-support<?= $vendor_template_id ?>">Support</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-notes<?= $vendor_template_id ?>">Notes</a>
<a class="nav-link" data-bs-toggle="pill" href="#pills-notes<?= $vendor_template_id ?>">Notes</a>
</li>
</ul>
@@ -60,70 +58,54 @@ ob_start();
<div class="tab-pane fade show active" id="pills-details<?= $vendor_template_id ?>">
<div class="form-group">
<div class="mb-3">
<label>Vendor Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Vendor Name" maxlength="200" value="<?= "$vendor_name" ?>" required>
<div class="input-group-append">
<div class="input-group-text">
<input type="checkbox" name="global_update_vendor_name" value="1">
<input class="form-check-input" type="checkbox" name="global_update_vendor_name" value="1">
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
<input type="text" class="form-control" name="description" placeholder="Description" maxlength="200" value="<?= $vendor_description ?>">
<div class="input-group-append">
<div class="input-group-text">
<input type="checkbox" name="global_update_vendor_description" value="1">
<input class="form-check-input" type="checkbox" name="global_update_vendor_description" value="1">
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Account Number</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-fingerprint"></i></span>
</div>
<input type="text" class="form-control" name="account_number" placeholder="Account number" maxlength="200" value="<?= $vendor_account_number ?>">
<div class="input-group-append">
<div class="input-group-text">
<input type="checkbox" name="global_update_vendor_account_number" value="1">
<input class="form-check-input" type="checkbox" name="global_update_vendor_account_number" value="1">
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Account Manager</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user"></i></span>
</div>
<input type="text" class="form-control" name="contact_name" maxlength="200" value="<?= $vendor_contact_name ?>" placeholder="Vendor contact name">
<div class="input-group-append">
<div class="input-group-text">
<input type="checkbox" name="global_update_vendor_contact_name" value="1">
<input class="form-check-input" type="checkbox" name="global_update_vendor_contact_name" value="1">
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="updateVendorsCheckbox<?= $vendor_template_id ?>" name="update_base_vendors" value="1" >
<label class="custom-control-label" for="updateVendorsCheckbox<?= $vendor_template_id ?>">Update All Base Vendors</label>
<div class="mb-3">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="updateVendorsCheckbox<?= $vendor_template_id ?>" name="update_base_vendors" value="1" >
<label class="form-check-label" for="updateVendorsCheckbox<?= $vendor_template_id ?>">Update All Base Vendors</label>
</div>
</div>
@@ -132,102 +114,78 @@ ob_start();
<div class="tab-pane fade" id="pills-support<?= $vendor_template_id ?>">
<label>Support Phone</label>
<div class="form-row">
<div class="row g-2">
<div class="col-8">
<div class="form-group">
<div class="mb-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-phone"></i></span>
</div>
<input type="tel" class="form-control col-2" name="phone_country_code" placeholder="+" maxlength="4" value="<?= $vendor_phone_country_code ?>">
<input type="tel" class="form-control" name="phone" maxlength="200" value="<?= $vendor_phone ?>">
<input type="hidden" name="phone_country_code" value="<?= $vendor_phone_country_code ?>">
<input type="tel" class="form-control" name="phone" maxlength="200" value="<?= $vendor_phone ?>" data-itflow-phone="phone_country_code">
</div>
</div>
</div>
<div class="col-4">
<div class="input-group">
<input type="text" class="form-control" name="extension" placeholder="Prompts" maxlength="200" value="<?= $vendor_extension ?>">
<div class="input-group-append">
<div class="input-group-text">
<input type="checkbox" name="global_update_vendor_phone" value="1">
<input class="form-check-input" type="checkbox" name="global_update_vendor_phone" value="1">
</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Support Hours</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
</div>
<input type="text" class="form-control" name="hours" placeholder="Support Hours" maxlength="200" value="<?= $vendor_hours ?>">
<div class="input-group-append">
<div class="input-group-text">
<input type="checkbox" name="global_update_vendor_hours" value="1">
<input class="form-check-input" type="checkbox" name="global_update_vendor_hours" value="1">
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Support Email</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-envelope"></i></span>
</div>
<input type="email" class="form-control" name="email" placeholder="Support Email" maxlength="200" value="<?= $vendor_email ?>">
<div class="input-group-append">
<div class="input-group-text">
<input type="checkbox" name="global_update_vendor_email" value="1">
<input class="form-check-input" type="checkbox" name="global_update_vendor_email" value="1">
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Support Website URL</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-globe"></i></span>
</div>
<input type="text" class="form-control" name="website" placeholder="Do not include http(s)://" maxlength="200" value="<?= $vendor_website ?>">
<div class="input-group-append">
<div class="input-group-text">
<input type="checkbox" name="global_update_vendor_website" value="1">
<input class="form-check-input" type="checkbox" name="global_update_vendor_website" value="1">
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>SLA</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-handshake"></i></span>
</div>
<input type="text" class="form-control" name="sla" placeholder="SLA Response Time" maxlength="200" value="<?= $vendor_sla ?>">
<div class="input-group-append">
<div class="input-group-text">
<input type="checkbox" name="global_update_vendor_sla" value="1">
<input class="form-check-input" type="checkbox" name="global_update_vendor_sla" value="1">
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="mb-3">
<label>Pin/Code</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-key"></i></span>
</div>
<input type="text" class="form-control" name="code" placeholder="Access Code or Pin" maxlength="200" value="<?= $vendor_code ?>">
<div class="input-group-append">
<div class="input-group-text">
<input type="checkbox" name="global_update_vendor_code" value="1">
<input class="form-check-input" type="checkbox" name="global_update_vendor_code" value="1">
</div>
</div>
</div>
</div>
@@ -235,13 +193,13 @@ ob_start();
<div class="tab-pane fade" id="pills-notes<?= $vendor_template_id ?>">
<div class="form-group">
<div class="mb-3">
<textarea class="form-control" rows="8" placeholder="Enter some notes" name="notes"><?= $vendor_notes ?></textarea>
</div>
<div class="form-group">
<div class="mb-3">
<label>Update Notes Globally?</label>
<input type="checkbox" name="global_update_vendor_notes" value="1">
<input class="form-check-input" type="checkbox" name="global_update_vendor_notes" value="1">
</div>
</div>
@@ -250,8 +208,8 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary text-bold" name="edit_vendor_template"><i class="fa fa-check mr-2"></i>Update Template</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" class="btn btn-primary text-bold" name="edit_vendor_template"><i class="fa fa-check me-2"></i>Update Template</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -17,95 +17,92 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="card card-dark">
<div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-puzzle-piece mr-2"></i>Access Modules</h3>
<div class="card">
<div class="card-header bg-dark py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-puzzle-piece me-2"></i>Access Modules</h3>
<div class="card-tools">
<div class="btn-group">
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/module/module_add.php">
<i class="fas fa-fw fa-plus mr-2"></i>New Module
<i class="fas fa-fw fa-plus me-2"></i>New Module
</button>
</div>
</div>
</div>
<div class="card-body">
<form class="mb-4" autocomplete="off">
<div class="row">
<div class="card-header py-3">
<form autocomplete="off">
<div class="row g-2 align-items-center">
<div class="col-md-4">
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) {echo stripslashes(escapeHtml($q));} ?>" placeholder="Search Modules">
<div class="input-group-append">
<button class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
</div>
</form>
<hr>
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?> text-nowrap">
<tr>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=module_name&order=<?= $disp ?>">
Module <?php if ($sort == 'module_name') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
</div>
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover mb-0">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?> text-nowrap">
<tr>
<th class="ps-3">
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=module_name&order=<?= $disp ?>">
Module <?php if ($sort == 'module_name') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_assoc($sql)) {
$module_id = intval($row['module_id']);
$module_name = escapeHtml($row['module_name']);
$module_description = escapeHtml($row['module_description']);
?>
<tr>
<td>
<a href="#" <?php if ($module_id > 6) { ?> class="ajax-modal" data-modal-url="modals/modules/module_edit.php?id=<?= $module_id ?>" <?php } ?>>
<strong class="text-dark"><?= $module_name ?></strong>
</a>
<div class="text-secondary"><?= $module_description ?></div>
</td>
<td>
<?php if ($module_id > 6) { ?>
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/module/module_edit.php?id=<?= $module_id ?>">
<i class="fas fa-fw fa-user-edit mr-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_module=<?= $module_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-archive mr-2"></i>Delete
</a>
</div>
</div>
<?php } else { echo "<p class='text-center'>N/A Predefined</p>"; } ?>
</td>
</tr>
<?php
}
while ($row = mysqli_fetch_assoc($sql)) {
$module_id = intval($row['module_id']);
$module_name = escapeHtml($row['module_name']);
$module_description = escapeHtml($row['module_description']);
?>
<tr>
<td class="ps-3">
<a href="#" <?php if ($module_id > 6) { ?> class="ajax-modal" data-modal-url="modals/modules/module_edit.php?id=<?= $module_id ?>" <?php } ?>>
<strong class="text-dark"><?= $module_name ?></strong>
</a>
<div class="text-secondary"><?= $module_description ?></div>
</td>
<td>
<?php if ($module_id > 6) { ?>
<div class="dropdown dropstart text-center">
<button class="btn btn-secondary btn-sm" type="button" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php";
?>
<a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/module/module_edit.php?id=<?= $module_id ?>">
<i class="fas fa-fw fa-user-edit me-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_module=<?= $module_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-archive me-2"></i>Delete
</a>
</div>
</div>
<?php } else { echo "<p class='text-center'>N/A Predefined</p>"; } ?>
</td>
</tr>
<?php
}
?>
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php";
?>
</div>
<?php

View File

@@ -6,96 +6,105 @@ $order = "ASC";
require_once "includes/inc_all_admin.php";
$sql = mysqli_query($mysqli, "SELECT payment_method_created_at, payment_method_description, payment_method_id,
payment_method_name FROM payment_methods ORDER BY $sort $order");
$sql = mysqli_query($mysqli, "SELECT SQL_CALC_FOUND_ROWS payment_method_created_at, payment_method_description, payment_method_id,
payment_method_name FROM payment_methods
WHERE payment_method_name LIKE '%$q%' OR payment_method_description LIKE '%$q%'
ORDER BY $sort $order LIMIT $record_from, $record_to");
$num_rows = mysqli_num_rows($sql);
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="card card-dark">
<div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-credit-card mr-2"></i>Payment Methods</h3>
<div class="card">
<div class="card-header bg-dark py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-credit-card me-2"></i>Payment Methods</h3>
<div class="card-tools">
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/payment_method/payment_method_add.php"><i class="fas fa-plus mr-2"></i>Add Payment Method</button>
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/payment_method/payment_method_add.php"><i class="fas fa-plus me-2"></i>Add Payment Method</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover">
<thead class="text-dark <?php if ($num_rows == 0) { echo "d-none"; } ?>">
<tr>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=payment_method_name&order=<?= $disp ?>">
Method <?php if ($sort == 'payment_method_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=payment_method_description&order=<?= $disp ?>">
Description <?php if ($sort == 'payment_method_description') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=payment_method_created_at&order=<?= $disp ?>">
Created at <?php if ($sort == 'payment_method_created_at') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
<div class="card-header py-3">
<form autocomplete="off">
<div class="row g-2 align-items-center">
<div class="col-md-4">
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(escapeHtml($q)); } ?>" placeholder="Search Payment Methods">
<button class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
</form>
</div>
while ($row = mysqli_fetch_assoc($sql)) {
$payment_method_id = intval($row['payment_method_id']);
$payment_method_name = escapeHtml($row['payment_method_name']);
$payment_method_description = escapeHtml($row['payment_method_description']);
$payment_method_created_at = escapeHtml($row['payment_method_created_at']);
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover mb-0">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th class="ps-3">
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=payment_method_name&order=<?= $disp ?>">
Method <?php if ($sort == 'payment_method_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=payment_method_description&order=<?= $disp ?>">
Description <?php if ($sort == 'payment_method_description') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=payment_method_created_at&order=<?= $disp ?>">
Created at <?php if ($sort == 'payment_method_created_at') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
?>
<tr>
<td>
<a class="text-dark text-bold ajax-modal" href="#"
data-modal-url="modals/payment_method/payment_method_edit.php?id=<?= $payment_method_id ?>">
<?= $payment_method_name ?>
</a>
</td>
<td><?= $payment_method_description ?></td>
<td><?= $payment_method_created_at ?></td>
<td>
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/payment_method/payment_method_edit.php?id=<?= $payment_method_id ?>">
<i class="fas fa-fw fa-edit mr-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_payment_method=<?= $payment_method_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
</div>
</div>
</td>
</tr>
<?php
}
if ($num_rows == 0) {
echo "<h3 class='text-secondary mt-3' style='text-align: center'>No Records Here</h3>";
}
while ($row = mysqli_fetch_assoc($sql)) {
$payment_method_id = intval($row['payment_method_id']);
$payment_method_name = escapeHtml($row['payment_method_name']);
$payment_method_description = escapeHtml($row['payment_method_description']);
$payment_method_created_at = escapeHtml($row['payment_method_created_at']);
?>
<tr>
<td class="ps-3">
<a class="text-dark text-bold ajax-modal" href="#"
data-modal-url="modals/payment_method/payment_method_edit.php?id=<?= $payment_method_id ?>">
<?= $payment_method_name ?>
</a>
</td>
<td><?= $payment_method_description ?></td>
<td><?= $payment_method_created_at ?></td>
<td>
<div class="dropdown dropstart text-center">
<button class="btn btn-secondary btn-sm" type="button" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/payment_method/payment_method_edit.php?id=<?= $payment_method_id ?>">
<i class="fas fa-fw fa-edit me-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_payment_method=<?= $payment_method_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash me-2"></i>Delete
</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<?php
</div>
}
?>
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php"; ?>
</div>
<?php

View File

@@ -6,136 +6,144 @@ $order = "ASC";
require_once "includes/inc_all_admin.php";
$sql = mysqli_query($mysqli, "SELECT account_name, category_name, payment_provider_description, payment_provider_id,
$sql = mysqli_query($mysqli, "SELECT SQL_CALC_FOUND_ROWS account_name, category_name, payment_provider_description, payment_provider_id,
payment_provider_name, payment_provider_threshold, vendor_name FROM payment_providers
LEFT JOIN accounts ON payment_provider_account = account_id
LEFT JOIN vendors ON payment_provider_expense_vendor = vendor_id
LEFT JOIN categories ON payment_provider_expense_category = category_id
ORDER BY $sort $order"
WHERE payment_provider_name LIKE '%$q%' OR payment_provider_description LIKE '%$q%'
ORDER BY $sort $order LIMIT $record_from, $record_to"
);
$num_rows = mysqli_num_rows($sql);
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="card card-dark">
<div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-credit-card mr-2"></i>Payment Providers</h3>
<div class="card">
<div class="card-header bg-dark py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-credit-card me-2"></i>Payment Providers</h3>
<div class="card-tools">
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/payment_provider/payment_provider_add.php"><i class="fas fa-plus mr-2"></i>Add Provider</button>
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/payment_provider/payment_provider_add.php"><i class="fas fa-plus me-2"></i>Add Provider</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover">
<thead class="text-dark <?php if ($num_rows == 0) { echo "d-none"; } ?>">
<tr>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=payment_provider_name&order=<?= $disp ?>">
Provider <?php if ($sort == 'payment_provider_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=account_name&order=<?= $disp ?>">
Expense / Income Account <?php if ($sort == 'account_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=payment_provider_threshold&order=<?= $disp ?>">
Threshold <?php if ($sort == 'payment_provider_threshold') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=vendor_name&order=<?= $disp ?>">
Expense Vendor <?php if ($sort == 'vendor_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=category_name&order=<?= $disp ?>">
Expense Category <?php if ($sort == 'category_name') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">
<a class="text-dark">Saved Payment Methods</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
<div class="card-header py-3">
<form autocomplete="off">
<div class="row g-2 align-items-center">
<div class="col-md-4">
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(escapeHtml($q)); } ?>" placeholder="Search Payment Providers">
<button class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
</form>
</div>
while ($row = mysqli_fetch_assoc($sql)) {
$provider_id = intval($row['payment_provider_id']);
$provider_name = escapeHtml($row['payment_provider_name']);
$provider_description = escapeHtml($row['payment_provider_description']);
$account_name = escapeHtml($row['account_name']);
$threshold = floatval($row['payment_provider_threshold']);
if (!$threshold) {
$threshold = "Not Enforced";
} else {
$threshold = numfmt_format_currency($currency_format, $threshold, $session_company_currency);
}
$vendor_name = escapeHtml($row['vendor_name'] ?? "Expense Disabled");
$category = escapeHtml($row['category_name']);
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('saved_payment_id') AS saved_payment_count FROM client_saved_payment_methods WHERE saved_payment_provider_id = $provider_id"));
$saved_payment_count = intval($row['saved_payment_count']);
?>
<tr>
<td>
<a class="text-dark text-bold ajax-modal" href="#"
data-modal-url="modals/payment_provider/payment_provider_edit.php?id=<?= $provider_id ?>">
<?= $provider_name ?>
</a>
<span class="text-secondary"><?= $provider_description ?></span>
</td>
<td><?= $account_name ?></td>
<td><?= $threshold ?></td>
<td><?= $vendor_name ?></td>
<td><?= $category ?></td>
<td class="text-center">
<a class="badge badge-dark badge-pill p-2" href="saved_payment_methods.php"><?= $saved_payment_count ?></a>
</td>
<td>
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/payment_provider/payment_provider_edit.php?id=<?= $provider_id ?>">
<i class="fas fa-fw fa-edit mr-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_payment_provider=<?= $provider_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i><strong>Delete Provider and</strong>
<ul class="text-xs">
<li>Related Recurring Payments</li>
<li>Related Saved cards</li>
<li>Client Provider Relations</li>
</ul>
</a>
</div>
</div>
</td>
</tr>
<?php
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover mb-0">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th class="ps-3">
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=payment_provider_name&order=<?= $disp ?>">
Provider <?php if ($sort == 'payment_provider_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=account_name&order=<?= $disp ?>">
Expense / Income Account <?php if ($sort == 'account_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=payment_provider_threshold&order=<?= $disp ?>">
Threshold <?php if ($sort == 'payment_provider_threshold') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=vendor_name&order=<?= $disp ?>">
Expense Vendor <?php if ($sort == 'vendor_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=category_name&order=<?= $disp ?>">
Expense Category <?php if ($sort == 'category_name') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">
<a class="text-dark">Saved Payment Methods</a>
</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_assoc($sql)) {
$provider_id = intval($row['payment_provider_id']);
$provider_name = escapeHtml($row['payment_provider_name']);
$provider_description = escapeHtml($row['payment_provider_description']);
$account_name = escapeHtml($row['account_name']);
$threshold = floatval($row['payment_provider_threshold']);
if (!$threshold) {
$threshold = "Not Enforced";
} else {
$threshold = numfmt_format_currency($currency_format, $threshold, $session_company_currency);
}
$vendor_name = escapeHtml($row['vendor_name'] ?? "Expense Disabled");
$category = escapeHtml($row['category_name']);
if ($num_rows == 0) {
echo "<h3 class='text-secondary mt-3' style='text-align: center'>No Records Here</h3>";
}
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('saved_payment_id') AS saved_payment_count FROM client_saved_payment_methods WHERE saved_payment_provider_id = $provider_id"));
$saved_payment_count = intval($row['saved_payment_count']);
?>
<tr>
<td class="ps-3">
<a class="text-dark text-bold ajax-modal" href="#"
data-modal-url="modals/payment_provider/payment_provider_edit.php?id=<?= $provider_id ?>">
<?= $provider_name ?>
</a>
<span class="text-secondary"><?= $provider_description ?></span>
</td>
<td><?= $account_name ?></td>
<td><?= $threshold ?></td>
<td><?= $vendor_name ?></td>
<td><?= $category ?></td>
<td class="text-center">
<a class="badge bg-dark rounded-pill p-2" href="saved_payment_methods.php"><?= $saved_payment_count ?></a>
</td>
<td>
<div class="dropdown dropstart text-center">
<button class="btn btn-secondary btn-sm" type="button" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/payment_provider/payment_provider_edit.php?id=<?= $provider_id ?>">
<i class="fas fa-fw fa-edit me-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_payment_provider=<?= $provider_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash me-2"></i><strong>Delete Provider and</strong>
<ul class="text-xs">
<li>Related Recurring Payments</li>
<li>Related Saved cards</li>
<li>Client Provider Relations</li>
</ul>
</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<?php
</div>
}
?>
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php"; ?>
</div>
<?php

View File

@@ -0,0 +1,67 @@
<?php
// Canned Responses
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_POST['add_canned_response'])) {
validateCSRFToken();
$name = escapeSql($_POST['name']);
// The body is TinyMCE HTML, so it goes in the way ticket replies and ticket template
// details do - escaped for the query only, and purified where it is rendered
$body = mysqli_real_escape_string($mysqli, $_POST['body']);
// 0 is the general case: offered on every ticket whatever its category
$category_id = intval($_POST['category']);
mysqli_query($mysqli, "INSERT INTO canned_responses SET canned_response_name = '$name', canned_response_body = '$body', canned_response_category_id = $category_id");
$canned_response_id = mysqli_insert_id($mysqli);
logAudit("Canned Response", "Create", "$session_name created canned response $name", 0, $canned_response_id);
flashAlert("Canned Response <strong>$name</strong> created");
redirect();
}
if (isset($_POST['edit_canned_response'])) {
validateCSRFToken();
$canned_response_id = intval($_POST['canned_response_id']);
$name = escapeSql($_POST['name']);
$body = mysqli_real_escape_string($mysqli, $_POST['body']);
$category_id = intval($_POST['category']);
mysqli_query($mysqli, "UPDATE canned_responses SET canned_response_name = '$name', canned_response_body = '$body', canned_response_category_id = $category_id WHERE canned_response_id = $canned_response_id");
logAudit("Canned Response", "Edit", "$session_name edited canned response $name", 0, $canned_response_id);
flashAlert("Canned Response <strong>$name</strong> edited");
redirect();
}
if (isset($_GET['delete_canned_response'])) {
validateCSRFToken();
$canned_response_id = intval($_GET['delete_canned_response']);
$name = escapeSql(getFieldById('canned_responses', $canned_response_id, 'canned_response_name'));
mysqli_query($mysqli, "DELETE FROM canned_responses WHERE canned_response_id = $canned_response_id");
logAudit("Canned Response", "Delete", "$session_name deleted canned response $name", 0, $canned_response_id);
flashAlert("Canned Response <strong>$name</strong> deleted", 'error');
redirect();
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
<?php
/*
* ITFlow - GET/POST request handler for AI Providers ('ai_providers')
* ITFlow - GET/POST request handler for payment Providers ('payment_providers')
*/
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
@@ -11,12 +11,7 @@ if (isset($_POST['add_payment_provider'])) {
validateCSRFToken();
$provider = escapeSql($_POST['provider']);
$public_key = escapeSql($_POST['public_key']);
$private_key = escapeSql($_POST['private_key']);
$threshold = floatval($_POST['threshold']);
$account = intval($_POST['account']);
$expense_vendor = intval($_POST['expense_vendor']) ?? 0;
$expense_category = intval($_POST['expense_category']) ?? 0;
require_once 'payment_provider_model.php';
// Check to ensure provider isn't added twice
$sql = mysqli_query($mysqli, "SELECT 1 FROM payment_providers WHERE payment_provider_name = '$provider' LIMIT 1");
@@ -29,7 +24,19 @@ if (isset($_POST['add_payment_provider'])) {
$provider_id = mysqli_insert_id($mysqli);
logAudit("Payment Provider", "Create", "$session_name created AI Provider $provider");
logAudit("Payment Provider", "Create", "$session_name created Payment Provider $provider");
// Check Stripe sk
if ($provider === 'Stripe') {
try {
require_once '../includes/stripe_init.php';
$stripe = new \Stripe\StripeClient($private_key);
$account = $stripe->accounts->retrieve();
} catch (Exception $e) {
flashAlert("Error checking Stripe key: " . $e->getMessage(), 'error');
redirect();
}
}
flashAlert("Payment provider <strong>$provider</strong> created");
@@ -41,19 +48,26 @@ if (isset($_POST['edit_payment_provider'])) {
validateCSRFToken();
require_once 'payment_provider_model.php';
$provider_id = intval($_POST['provider_id']);
$description = escapeSql($_POST['description']);
$public_key = escapeSql($_POST['public_key']);
$private_key = escapeSql($_POST['private_key']);
$threshold = floatval($_POST['threshold']);
$account = intval($_POST['account']);
$expense_vendor = intval($_POST['expense_vendor']) ?? 0;
$expense_category = intval($_POST['expense_category']) ?? 0;
mysqli_query($mysqli,"UPDATE payment_providers SET payment_provider_public_key = '$public_key', payment_provider_private_key = '$private_key', payment_provider_threshold = $threshold, payment_provider_account = $account, payment_provider_expense_vendor = $expense_vendor, payment_provider_expense_category = $expense_category WHERE payment_provider_id = $provider_id");
logAudit("Payment Provider", "Edit", "$session_name edited Payment Provider $provider");
// Check Stripe sk
if ($_POST['provider'] === 'Stripe') {
try {
require_once '../includes/stripe_init.php';
$stripe = new \Stripe\StripeClient($private_key);
$account = $stripe->accounts->retrieve();
} catch (Exception $e) {
flashAlert("Error checking Stripe key: " . $e->getMessage(), 'error');
redirect();
}
}
flashAlert("Payment Provider <strong>$provider</strong> edited");
redirect();

View File

@@ -0,0 +1,9 @@
<?php
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
$public_key = escapeSql($_POST['public_key']);
$private_key = escapeSql($_POST['private_key']);
$threshold = floatval($_POST['threshold']);
$account = intval($_POST['account']);
$expense_vendor = intval($_POST['expense_vendor']) ?? 0;
$expense_category = intval($_POST['expense_category']) ?? 0;

View File

@@ -61,7 +61,8 @@ if (isset($_GET['delete_saved_payment'])) {
// SQL Cascade delete will Remove All Associated Auto Payment Methods on recurring invoices in the recurring payments table.
logAudit("Payment Provider", "Update", "$session_name deleted saved payment method $saved_payment_description (PM: $payment_method)", $client_id);
$payment_method_esc = escapeSql($payment_method);
logAudit("Payment Provider", "Update", "$session_name deleted saved payment method $saved_payment_description (PM: $payment_method_esc)", $client_id);
flashAlert("Payment method <strong>$saved_payment_description</strong> removed", 'error');

View File

@@ -42,8 +42,18 @@ if (isset($_POST['edit_company'])) {
}
}
// The client record standing in for this company. Verified against the clients table
// rather than trusted from the POST, so a stale or hand-made value cannot leave the
// sidebar pointing at a client that no longer exists. 0 clears the designation.
$internal_client_id = intval($_POST['internal_client_id'] ?? 0);
if ($internal_client_id !== 0 && !mysqli_num_rows(mysqli_query($mysqli, "SELECT client_id FROM clients WHERE client_id = $internal_client_id LIMIT 1"))) {
$internal_client_id = 0;
}
mysqli_query($mysqli,"UPDATE companies SET company_name = '$name', company_address = '$address', company_city = '$city', company_state = '$state', company_zip = '$zip', company_country = '$country', company_phone_country_code = '$phone_country_code', company_phone = '$phone', company_email = '$email', company_website = '$website', company_tax_id = '$tax_id' WHERE company_id = 1");
mysqli_query($mysqli,"UPDATE settings SET config_internal_client_id = $internal_client_id WHERE company_id = 1");
logAudit("Settings", "Edit", "$session_name edited company details");
flashAlert("Company <strong>$name</strong> edited");

View File

@@ -3,7 +3,7 @@
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
// Saves and tests return to the tab they were submitted from (see settings_mail.php)
$mail_tabs = ['smtp', 'imap', 'oauth', 'from', 'tests'];
$mail_tabs = ['sending', 'receiving', 'oauth', 'from', 'tests'];
$mail_tab_redirect = 'settings_mail.php';
if (isset($_POST['tab']) && in_array($_POST['tab'], $mail_tabs, true)) {
$mail_tab_redirect .= '?tab=' . $_POST['tab'];
@@ -249,7 +249,7 @@ if (isset($_POST['test_email_smtp'])) {
$mail = addToMailQueue($data);
if ($mail === true) {
flashAlert("Test email queued! <a class='text-bold text-light' href='mail_queue.php'>Check Admin > Mail queue</a>");
flashAlert("Test email queued! Check <strong>Admin &gt; Mail queue</strong>");
} else {
flashAlert("Failed to add test mail to queue", 'error');
}

View File

@@ -113,12 +113,7 @@ if (isset($_POST['edit_sla_settings'])) {
getSlaSettings(true);
// Business hours feed the due date math - re-stamp open SLA tickets
$restamped = 0;
$sql_tickets = mysqli_query($mysqli, "SELECT ticket_id, ticket_sla_id FROM tickets WHERE ticket_sla_id > 0 AND ticket_closed_at IS NULL AND ticket_archived_at IS NULL");
while ($ticket_row = mysqli_fetch_assoc($sql_tickets)) {
applyTicketSla($ticket_row['ticket_id'], $ticket_row['ticket_sla_id']);
$restamped++;
}
$restamped = restampOpenSlaTickets();
logAudit("Settings", "Edit", "$session_name edited SLA / business hours settings");
@@ -128,6 +123,105 @@ if (isset($_POST['edit_sla_settings'])) {
}
if (isset($_POST['add_holiday'])) {
validateCSRFToken();
// Deliberately NOT validateDate() - that falls back to today's date on bad
// input, which would silently close the office today. Reject instead. The
// round-trip comparison also catches impossible dates like 2026-02-30,
// which createFromFormat would otherwise roll forward into March.
$holiday_date_input = $_POST['holiday_date'] ?? '';
$parsed_date = DateTime::createFromFormat('Y-m-d', $holiday_date_input);
if (!$parsed_date || $parsed_date->format('Y-m-d') !== $holiday_date_input) {
flashAlert("Enter a valid date for the closure day.", 'error');
redirect();
}
$holiday_name_input = trim($_POST['holiday_name'] ?? '');
if ($holiday_name_input === '') {
flashAlert("Enter a name for the closure day.", 'error');
redirect();
}
$holiday_date = escapeSql($holiday_date_input);
$holiday_name = escapeSql($holiday_name_input);
// INSERT IGNORE rather than an error: the date is UNIQUE, and re-adding a day
// that is already listed is a no-op the operator does not need telling about
mysqli_query($mysqli, "INSERT IGNORE INTO business_holidays SET holiday_date = '$holiday_date', holiday_name = '$holiday_name'");
getBusinessHolidays(true);
$restamped = restampOpenSlaTickets();
logAudit("Settings", "Create", "$session_name added SLA closure day $holiday_date - $holiday_name");
flashAlert("Closure day added - targets recalculated on $restamped open ticket(s)");
redirect();
}
if (isset($_POST['delete_holiday'])) {
validateCSRFToken();
$holiday_id = intval($_POST['holiday_id']);
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT holiday_date, holiday_name FROM business_holidays WHERE holiday_id = $holiday_id LIMIT 1"));
if (!$row) {
flashAlert("Closure day not found.", 'error');
redirect();
}
$holiday_date = escapeSql($row['holiday_date']);
$holiday_name = escapeSql($row['holiday_name']);
mysqli_query($mysqli, "DELETE FROM business_holidays WHERE holiday_id = $holiday_id");
getBusinessHolidays(true);
$restamped = restampOpenSlaTickets();
logAudit("Settings", "Delete", "$session_name removed SLA closure day $holiday_date - $holiday_name");
flashAlert("Closure day removed - targets recalculated on $restamped open ticket(s)");
redirect();
}
if (isset($_POST['generate_holidays'])) {
validateCSRFToken();
$holiday_year = intval($_POST['holiday_year']);
if ($holiday_year < 2000 || $holiday_year > 2100) {
flashAlert("Enter a year between 2000 and 2100.", 'error');
redirect();
}
// Existing rows win - INSERT IGNORE leaves a hand-entered name on a date the
// generator also produces, so running this over a partly-filled year is safe
$added = 0;
foreach (usFederalHolidays($holiday_year) as $holiday) {
$holiday_date = escapeSql($holiday['date']);
$holiday_name = escapeSql($holiday['name']);
mysqli_query($mysqli, "INSERT IGNORE INTO business_holidays SET holiday_date = '$holiday_date', holiday_name = '$holiday_name'");
$added += mysqli_affected_rows($mysqli) > 0 ? 1 : 0;
}
getBusinessHolidays(true);
$restamped = restampOpenSlaTickets();
logAudit("Settings", "Create", "$session_name generated $added US federal holiday closure day(s) for $holiday_year");
flashAlert("Added $added US federal holiday(s) for $holiday_year - targets recalculated on $restamped open ticket(s)");
redirect();
}
if (isset($_POST['save_sla_assignments'])) {
validateCSRFToken();

View File

@@ -45,3 +45,50 @@ if (isset($_POST['load_starter_content'])) {
redirect();
}
if (isset($_POST['load_demo_data'])) {
validateCSRFToken();
require_once 'demo_data_model.php';
$counts = demoDataLoad($mysqli);
if (!$counts['clients']) {
flashAlert("Nothing to add - every demo client is already here", 'info');
redirect();
}
$summary = "{$counts['clients']} clients, {$counts['contacts']} contacts, {$counts['assets']} assets, {$counts['documentation']} documentation records, {$counts['tickets']} tickets, {$counts['projects']} projects, {$counts['billing']} billing records, {$counts['company']} company records";
logAudit("Demo Data", "Create", "$session_name loaded demo data - $summary");
if ($counts['skipped_credentials']) {
flashAlert("Added demo data: $summary. Credentials were skipped - the credential vault did not open for this session", 'warning');
} else {
flashAlert("Added demo data: $summary");
}
redirect();
}
if (isset($_POST['remove_demo_data'])) {
validateCSRFToken();
require_once 'demo_data_model.php';
$clients_removed = demoDataRemove($mysqli);
if ($clients_removed) {
logAudit("Demo Data", "Deleted", "$session_name removed demo data - $clients_removed demo clients and all associated records");
flashAlert("Removed <strong>$clients_removed</strong> demo clients and everything associated with them", 'error');
} else {
flashAlert("No demo clients found - nothing to remove", 'info');
}
redirect();
}

View File

@@ -591,6 +591,8 @@ function starterContentTags() {
[1, 'Past Due', '#dc3545', 'file-invoice-dollar'],
[1, 'Multi Site', '#001f3f', 'map-marked-alt'],
[1, 'Non Profit', '#3d9970', 'hand-holding-heart'],
[1, 'Hosting', '#20c997', 'server'],
[1, 'Seasonal', '#fd7e14', 'calendar-alt'],
[1, 'After Hours Support', '#343a40', 'moon'],
[1, 'Compliance', '#795548', 'balance-scale'],
[1, 'Cyber Insurance', '#6610f2', 'shield-alt'],

View File

@@ -33,7 +33,13 @@ if (isset($_POST['edit_ticket_status'])) {
$order = intval($_POST['order']);
$status = intval($_POST['status']);
$pauses_sla = intval($_POST['pauses_sla'] ?? 0);
// Built-in statuses have fixed SLA behaviour. The modal renders it read-only, and a
// read-only field submits nothing - so the value is decided here rather than taken
// from the POST, which also means a hand-made request cannot change it.
$pauses_sla = getTicketStatusSlaLock($ticket_status_id);
if (is_null($pauses_sla)) {
$pauses_sla = intval($_POST['pauses_sla'] ?? 0);
}
mysqli_query($mysqli, "UPDATE ticket_statuses SET ticket_status_name = '$name', ticket_status_color = '$color', ticket_status_order = $order, ticket_status_active = $status, ticket_status_pauses_sla = $pauses_sla WHERE ticket_status_id = $ticket_status_id");

View File

@@ -2,313 +2,64 @@
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_GET['update'])) {
/*
* Queueing is the ONLY update path from the web - files and database both. There used to be
* an ?update handler here that ran git pull inside this request, and an ?update_db one that
* ran the migrations; both are gone deliberately. scripts/update_cli.php replaces the files
* this request is executing from and then applies the migrations that came with them, which
* is not something to do half way through a page load, and a host where PHP cannot run
* external commands could never do the first half at all.
*
* The row is written as well as the settings column: config_update_queued_at is what
* cron/app_update.php acts on, and cron_job_run_now is what gets the dispatcher to look
* before the job's own schedule comes round.
*/
/*
* Check Now. The check runs git fetch, so it belongs on the command line for the same reason
* the update does; this only asks the dispatcher to bring the job forward. run_now works on a
* disabled job too, so turning the daily check off does not take the button with it.
*/
if (isset($_GET['check_update'])) {
validateCSRFToken();
enforceAdminPermission();
// git fetch downloads the latest from the remote without merging or rebasing anything.
// The hard reset then throws away every local change and makes the working tree match
// the tracked branch exactly.
//
// That reset used to name origin/master outright, so a force update on an install
// tracking any other branch silently moved it onto master and discarded the code it was
// actually running.
if (isset($_GET['force_update']) == 1) {
$remote_ref = escapeshellarg("origin/" . getRepoBranch());
exec("git fetch --all");
exec("git reset --hard $remote_ref");
} else {
exec("git pull");
}
//header("Location: post.php?update_db");
// Send Telemetry if enabled during update
if ($config_telemetry > 0 OR $config_telemetry = 2) {
$sql = mysqli_query($mysqli,"SELECT * FROM companies WHERE company_id = 1");
$row = mysqli_fetch_assoc($sql);
$company_name = escapeSql($row['company_name']);
$website = escapeSql($row['company_website']);
$city = escapeSql($row['company_city']);
$state = escapeSql($row['company_state']);
$country = escapeSql($row['company_country']);
$currency = escapeSql($row['company_currency']);
$current_version = exec("git rev-parse HEAD");
// Client Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('client_id') AS num FROM clients"));
$client_count = $row['num'];
// Ticket Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('recurring_id') AS num FROM tickets"));
$ticket_count = $row['num'];
// Recurring Ticket Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('recurring_ticket_id') AS num FROM recurring_tickets"));
$recurring_ticket_count = $row['num'];
// Calendar Event Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('event_id') AS num FROM calendar_events"));
$calendar_event_count = $row['num'];
// Quote Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('quote_id') AS num FROM quotes"));
$quote_count = $row['num'];
// Invoice Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('invoice_id') AS num FROM invoices"));
$invoice_count = $row['num'];
// Revenue Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('revenue_id') AS num FROM revenues"));
$revenue_count = $row['num'];
// Recurring Invoice Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('recurring_invoice_id') AS num FROM recurring_invoices"));
$recurring_invoice_count = $row['num'];
// Account Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('account_id') AS num FROM accounts"));
$account_count = $row['num'];
// Tax Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('tax_id') AS num FROM taxes"));
$tax_count = $row['num'];
// Product Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('product_id') AS num FROM products"));
$product_count = $row['num'];
// Payment Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('payment_id') AS num FROM payments WHERE payment_invoice_id > 0"));
$payment_count = $row['num'];
// Company Vendor Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('vendor_id') AS num FROM vendors WHERE vendor_client_id = 0"));
$company_vendor_count = $row['num'];
// Expense Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('expense_id') AS num FROM expenses WHERE expense_vendor_id > 0"));
$expense_count = $row['num'];
// Trip Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('trip_id') AS num FROM trips"));
$trip_count = $row['num'];
// Transfer Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('transfer_id') AS num FROM transfers"));
$transfer_count = $row['num'];
// Contact Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('contact_id') AS num FROM contacts"));
$contact_count = $row['num'];
// Location Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('location_id') AS num FROM locations"));
$location_count = $row['num'];
// Asset Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('asset_id') AS num FROM assets"));
$asset_count = $row['num'];
// Software Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('software_id') AS num FROM software"));
$software_count = $row['num'];
// Software Template Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('software_template_id') AS num FROM software_templates"));
$software_template_count = $row['num'];
// Password Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('credential_id') AS num FROM credentials"));
$credential_count = $row['num'];
// Network Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('network_id') AS num FROM networks"));
$network_count = $row['num'];
// Certificate Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('certificate_id') AS num FROM certificates"));
$certificate_count = $row['num'];
// Domain Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('domain_id') AS num FROM domains"));
$domain_count = $row['num'];
// Service Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('service_id') AS num FROM services"));
$service_count = $row['num'];
// Client Vendor Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('vendor_id') AS num FROM vendors WHERE vendor_client_id > 0"));
$client_vendor_count = $row['num'];
// Vendor Template Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('vendor_template_id') AS num FROM vendor_templates"));
$vendor_template_count = $row['num'];
// File Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('file_id') AS num FROM files"));
$file_count = $row['num'];
// Document Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('document_id') AS num FROM documents"));
$document_count = $row['num'];
// Document Template Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('document_template_id') AS num FROM document_templates"));
$document_template_count = $row['num'];
// Shared Item Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('item_id') AS num FROM shared_items"));
$shared_item_count = $row['num'];
// Company Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('company_id') AS num FROM companies"));
$company_count = $row['num'];
// User Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('user_id') AS num FROM users"));
$user_count = $row['num'];
// Category Expense Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('category_id') AS num FROM categories WHERE category_type = 'Expense'"));
$category_expense_count = $row['num'];
// Category Income Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('category_id') AS num FROM categories WHERE category_type = 'Income'"));
$category_income_count = $row['num'];
// Category Referral Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('category_id') AS num FROM categories WHERE category_type = 'Referral'"));
$category_referral_count = $row['num'];
// Category Payment Method Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('category_id') AS num FROM categories WHERE category_type = 'Payment Method'"));
$category_payment_method_count = $row['num'];
// Tag Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('tag_id') AS num FROM tags"));
$tag_count = $row['num'];
// API Key Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('api_key_id') AS num FROM api_keys"));
$api_key_count = $row['num'];
// Log Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('log_id') AS num FROM logs"));
$log_count = $row['num'];
$postdata = http_build_query(
array(
'installation_id' => "$installation_id",
'version' => "$current_version",
'company_name' => "$company_name",
'website' => "$website",
'city' => "$city",
'state' => "$state",
'country' => "$country",
'currency' => "$currency",
'comments' => "$comments",
'client_count' => $client_count,
'ticket_count' => $ticket_count,
'recurring_ticket_count' => $recurring_ticket_count,
'calendar_event_count' => $calendar_event_count,
'quote_count' => $quote_count,
'invoice_count' => $invoice_count,
'revenue_count' => $revenue_count,
'recurring_invoice_count' => $recurring_invoice_count,
'account_count' => $account_count,
'tax_count' => $tax_count,
'product_count' => $product_count,
'payment_count' => $payment_count,
'company_vendor_count' => $company_vendor_count,
'expense_count' => $expense_count,
'trip_count' => $trip_count,
'transfer_count' => $transfer_count,
'contact_count' => $contact_count,
'location_count' => $location_count,
'asset_count' => $asset_count,
'software_count' => $software_count,
'software_template_count' => $software_template_count,
'credential_count' => $credential_count,
'network_count' => $network_count,
'certificate_count' => $certificate_count,
'domain_count' => $domain_count,
'service_count' => $service_count,
'client_vendor_count' => $client_vendor_count,
'vendor_template_count' => $vendor_template_count,
'file_count' => $file_count,
'document_count' => $document_count,
'document_template_count' => $document_template_count,
'shared_item_count' => $shared_item_count,
'company_count' => $company_count,
'user_count' => $user_count,
'category_expense_count' => $category_expense_count,
'category_income_count' => $category_income_count,
'category_referral_count' => $category_referral_count,
'category_payment_method_count' => $category_payment_method_count,
'tag_count' => $tag_count,
'api_key_count' => $api_key_count,
'log_count' => $log_count,
'config_theme' => "$config_theme",
'config_enable_cron' => $config_enable_cron,
'config_ticket_email_parse' => $config_ticket_email_parse,
'config_module_enable_itdoc' => $config_module_enable_itdoc,
'config_module_enable_ticketing' => $config_module_enable_ticketing,
'config_module_enable_accounting' => $config_module_enable_accounting,
'config_telemetry' => $config_telemetry,
'collection_method' => 4
)
);
$opts = array('http' =>
array(
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $postdata
)
);
$context = stream_context_create($opts);
$result = file_get_contents('https://telemetry.itflow.org', false, $context);
if (!settingsColumnExists($mysqli, 'config_update_latest_commit')) {
flashAlert("Checking needs a schema change this install has not caught up with yet - run php scripts/update_cli.php from a shell once, and it will work from then on.", 'error');
redirect();
}
logAudit("App", "Update", "$session_name ran updates");
mysqli_query($mysqli, "UPDATE cron_jobs SET cron_job_run_now = 1 WHERE cron_job_name = 'update_check'");
flashAlert("Update successful");
logAudit("App", "Update", "$session_name asked cron to check for updates");
sleep(1);
flashAlert("Checking for updates - cron will run the check within a minute.");
redirect();
}
if (isset($_GET['update_db'])) {
if (isset($_GET['queue_update'])) {
validateCSRFToken();
// Get the current version
require_once ('../includes/database_version.php');
enforceAdminPermission();
// Perform upgrades, if required - populates $database_updates_applied and $database_updates_error
require_once ('database_updates.php');
if ($database_updates_error) {
logAudit("Database", "Update", "$session_name ran a database update that failed at $database_updates_error");
flashAlert("Database update failed at $database_updates_error - the version was not advanced past the last successful update, so it is safe to retry", "error");
} else {
logAudit("Database", "Update", "$session_name updated the database structure");
flashAlert("Database structure update successful");
// The files can be newer than the schema - that is the window this whole page exists to
// close - and the column the queue is written to arrives with a migration. There is no
// longer a button that applies migrations on their own, so the way out is the shell.
if (!settingsColumnExists($mysqli, 'config_update_queued_at')) {
flashAlert("Queueing needs a schema change this install has not caught up with yet - run php scripts/update_cli.php from a shell once, and it will work from then on.", 'error');
redirect();
}
sleep(1);
mysqli_query($mysqli, "UPDATE settings SET config_update_queued_at = '" . date('Y-m-d H:i:s') . "' WHERE company_id = 1");
mysqli_query($mysqli, "UPDATE cron_jobs SET cron_job_run_now = 1 WHERE cron_job_name = 'app_update'");
logAudit("App", "Update", "$session_name queued an update to be applied by cron");
flashAlert("Update queued - cron will start it within a minute.");
redirect();

Some files were not shown because too many files have changed in this diff Show More