mirror of
https://github.com/fleetbase/fleetbase.git
synced 2026-02-05 07:33:53 +00:00
Compare commits
246 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43f9914afa | ||
|
|
cbcf7a239b | ||
|
|
58686b9d77 | ||
|
|
360f01ebf7 | ||
|
|
e02c8e831a | ||
|
|
2b8af0a47d | ||
|
|
7603c00ab1 | ||
|
|
6cd226b4f9 | ||
|
|
019ca9bc2d | ||
|
|
86a3b339d4 | ||
|
|
f1edcf607b | ||
|
|
44eac77cde | ||
|
|
529f1cfa90 | ||
|
|
517bc7b3bc | ||
|
|
ccf3fc7b31 | ||
|
|
a0febb1cbc | ||
|
|
768d2e3c85 | ||
|
|
6b5bbd1b7c | ||
|
|
e4a8f9519b | ||
|
|
3de9b6fccb | ||
|
|
979b451130 | ||
|
|
9d4b1167ab | ||
|
|
98cde6795a | ||
|
|
3a70c42054 | ||
|
|
becf73a83e | ||
|
|
f5c16ffe60 | ||
|
|
4b3d23c0b9 | ||
|
|
7ba7b62096 | ||
|
|
70c45a34d4 | ||
|
|
233154ecb2 | ||
|
|
04dd58397e | ||
|
|
6e537daf24 | ||
|
|
662c5ab716 | ||
|
|
502363efc4 | ||
|
|
b7d8a58861 | ||
|
|
8d8c1d2253 | ||
|
|
11f6d173b7 | ||
|
|
7d232597ff | ||
|
|
0e4baa34e0 | ||
|
|
d6d078df62 | ||
|
|
7252a95805 | ||
|
|
b25da51496 | ||
|
|
7d776f2bd5 | ||
|
|
ecfcec72e4 | ||
|
|
ca1741a4b2 | ||
|
|
7ba7821fed | ||
|
|
947565bcf0 | ||
|
|
2d4cc5cf66 | ||
|
|
af3c60fc7c | ||
|
|
53a87d6f38 | ||
|
|
a372b515da | ||
|
|
d7f8f87315 | ||
|
|
f8b9c0415b | ||
|
|
36673ef564 | ||
|
|
19341c81e7 | ||
|
|
b4ecf5bda9 | ||
|
|
1b714a7ef8 | ||
|
|
e41cd62ea5 | ||
|
|
1ca1342052 | ||
|
|
a5a5ddb0d5 | ||
|
|
c51f3ca6c8 | ||
|
|
a9b172081a | ||
|
|
a29ca0ecb9 | ||
|
|
6442644438 | ||
|
|
b525ad4b1a | ||
|
|
b161b415d0 | ||
|
|
e444d7994c | ||
|
|
0e44375d32 | ||
|
|
a9bb2f0166 | ||
|
|
ac02a6b6e0 | ||
|
|
4a640561d6 | ||
|
|
5957bc6339 | ||
|
|
9b15a6856f | ||
|
|
0a4e5b31cc | ||
|
|
0d47ffc785 | ||
|
|
3f2c739810 | ||
|
|
6b2ab28ec9 | ||
|
|
42df48c9b0 | ||
|
|
268749fcd9 | ||
|
|
50ae560409 | ||
|
|
2e48024949 | ||
|
|
908f60aaac | ||
|
|
a1f2992f18 | ||
|
|
0252b387e2 | ||
|
|
12e1ec2cac | ||
|
|
5f2003eec5 | ||
|
|
e05d12dd87 | ||
|
|
86378d3ede | ||
|
|
1d36bf144b | ||
|
|
293f67d6d1 | ||
|
|
01e8298968 | ||
|
|
094d1d375e | ||
|
|
8a1dee0cbd | ||
|
|
df2da8cea1 | ||
|
|
ced5e6b6fd | ||
|
|
de2cbd2ded | ||
|
|
894f4348dd | ||
|
|
2030a72a71 | ||
|
|
41d20c41c5 | ||
|
|
9976075843 | ||
|
|
b26f735fee | ||
|
|
791cc7283d | ||
|
|
21601737a1 | ||
|
|
0cd9e076b8 | ||
|
|
5fe799b708 | ||
|
|
878a70d328 | ||
|
|
4c5487b6bb | ||
|
|
599fd0e8f7 | ||
|
|
64561c85a8 | ||
|
|
c8d8be11d2 | ||
|
|
12847437b0 | ||
|
|
53eadee8a6 | ||
|
|
657de6c99d | ||
|
|
db601606c5 | ||
|
|
6ec176011a | ||
|
|
d416baf3cc | ||
|
|
d80b968eaa | ||
|
|
eef6847919 | ||
|
|
2ad569eaf6 | ||
|
|
dc18b48f6a | ||
|
|
3e93e161dc | ||
|
|
fac29308fd | ||
|
|
7b62e992eb | ||
|
|
18af3b4515 | ||
|
|
264c564ad6 | ||
|
|
dbf9a37ae9 | ||
|
|
46c4ac9dda | ||
|
|
e692bc3365 | ||
|
|
0b807011a3 | ||
|
|
80d84e1c3b | ||
|
|
08c722359a | ||
|
|
ebd9b0d8cb | ||
|
|
ec5ace214c | ||
|
|
396e0cbf36 | ||
|
|
3faac3481f | ||
|
|
36f837a4af | ||
|
|
094bd6c8ce | ||
|
|
aea35f6f5f | ||
|
|
96fa0294bc | ||
|
|
72ed388cd6 | ||
|
|
5a79a06642 | ||
|
|
010a6b0d05 | ||
|
|
6d138761ee | ||
|
|
1f59d8831b | ||
|
|
746b2142f0 | ||
|
|
343e07e24d | ||
|
|
d6094b8e29 | ||
|
|
59d7fa3de9 | ||
|
|
14c05a6c6a | ||
|
|
5c12254a58 | ||
|
|
65416833ca | ||
|
|
40e0de6e6b | ||
|
|
5986ced2c0 | ||
|
|
ca260bb94c | ||
|
|
95ef8ef3c9 | ||
|
|
df8513cf7b | ||
|
|
e2c544b0c9 | ||
|
|
77b39fccf9 | ||
|
|
eaa448f762 | ||
|
|
627522d61c | ||
|
|
201ac84dc9 | ||
|
|
78b1f31053 | ||
|
|
63cf8128c0 | ||
|
|
5d66c7a35f | ||
|
|
0603888393 | ||
|
|
cd50d349f8 | ||
|
|
e478767a60 | ||
|
|
17cb0cd274 | ||
|
|
a3289ddd41 | ||
|
|
ff8986541c | ||
|
|
04d0c126de | ||
|
|
ad43f74d5c | ||
|
|
3c013a3817 | ||
|
|
4392c7e3ff | ||
|
|
97dfbedd1a | ||
|
|
6d13f22a98 | ||
|
|
2c09c87bb6 | ||
|
|
f7f6991ef3 | ||
|
|
73acd4833c | ||
|
|
1c3c4c16a5 | ||
|
|
5d01438dc9 | ||
|
|
9d004410ee | ||
|
|
3a396f3b54 | ||
|
|
e86b970fc8 | ||
|
|
122a0d186a | ||
|
|
7e258f698f | ||
|
|
65ef642315 | ||
|
|
d79f034dbd | ||
|
|
05b7d5e112 | ||
|
|
61779ab102 | ||
|
|
a287c05380 | ||
|
|
ecc41e587e | ||
|
|
166529f9b4 | ||
|
|
189547f9de | ||
|
|
90a42e8a93 | ||
|
|
d28b1d41fb | ||
|
|
b056ef62b0 | ||
|
|
e70cbacbc2 | ||
|
|
fbd4a7490a | ||
|
|
b9b0eb308b | ||
|
|
48e1b89ec8 | ||
|
|
5e02c95b66 | ||
|
|
db8b5c4d6a | ||
|
|
aa214ccad7 | ||
|
|
cdd5524cf3 | ||
|
|
d99cefd2a4 | ||
|
|
1c58fd43c3 | ||
|
|
9226394159 | ||
|
|
9b23b39f32 | ||
|
|
5310fc3ff3 | ||
|
|
9569053f50 | ||
|
|
b08c054b99 | ||
|
|
2e517f2f95 | ||
|
|
7038d375b0 | ||
|
|
d5ea7f7790 | ||
|
|
4eb4b04121 | ||
|
|
24c5b93005 | ||
|
|
80610b9a48 | ||
|
|
9e5551972e | ||
|
|
b784f890f3 | ||
|
|
797a3d61fe | ||
|
|
d2f0bfe83e | ||
|
|
f28ad85c1a | ||
|
|
d7b0826f3f | ||
|
|
dd6008a8aa | ||
|
|
6671fefaaa | ||
|
|
7136f6195c | ||
|
|
2384887620 | ||
|
|
a2778f1552 | ||
|
|
acfda5ed1a | ||
|
|
e08255007a | ||
|
|
41761ea50e | ||
|
|
5c623819ed | ||
|
|
2bd885b1a2 | ||
|
|
88c3842441 | ||
|
|
2c2a4121a8 | ||
|
|
aadd03f14b | ||
|
|
706e94270d | ||
|
|
1dabc375f9 | ||
|
|
0efec46155 | ||
|
|
8bb2c6b65d | ||
|
|
1c0af1a119 | ||
|
|
dd65ee619b | ||
|
|
e790a0e123 | ||
|
|
723e3ca3d1 | ||
|
|
4eb706d33e |
44
.github/workflows/cd.yml
vendored
44
.github/workflows/cd.yml
vendored
@@ -13,9 +13,24 @@ env:
|
||||
GITHUB_AUTH_KEY: ${{ secrets._GITHUB_AUTH_TOKEN }}
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
name: Setup Environment
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
environment: ${{ steps.extract-env.outputs.environment }}
|
||||
steps:
|
||||
- name: Extract environment from branch name
|
||||
id: extract-env
|
||||
run: |
|
||||
ENVIRONMENT=$(echo "${{ github.ref_name }}" | sed 's|deploy/||')
|
||||
echo "environment=${ENVIRONMENT}" >> $GITHUB_OUTPUT
|
||||
echo "Deploying to environment: ${ENVIRONMENT}"
|
||||
|
||||
build_service:
|
||||
name: Build and Deploy the Service
|
||||
needs: [setup]
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ needs.setup.outputs.environment }}
|
||||
permissions:
|
||||
id-token: write # This is required for requesting the JWT
|
||||
contents: read # This is required for actions/checkout
|
||||
@@ -120,8 +135,9 @@ jobs:
|
||||
|
||||
build_frontend:
|
||||
name: Build and Deploy the Console
|
||||
needs: [build_service]
|
||||
needs: [setup, build_service]
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ needs.setup.outputs.environment }}
|
||||
permissions:
|
||||
id-token: write # This is required for requesting the JWT
|
||||
contents: read # This is required for actions/checkout
|
||||
@@ -192,20 +208,20 @@ jobs:
|
||||
fi
|
||||
working-directory: ./console
|
||||
|
||||
- name: Set Env Variables for QA
|
||||
if: startsWith(github.ref, 'refs/heads/deploy/qa')
|
||||
- name: Set Env Variables
|
||||
run: |
|
||||
echo "STRIPE_KEY=${{ secrets.STRIPE_TEST_KEY }}" >> ./environments/.env.production
|
||||
# ensure file ends with a newline
|
||||
printf '\n' >> ./environments/.env.production
|
||||
|
||||
echo "EXTENSIONS=${{ secrets.EXTENSIONS }}" >> ./environments/.env.production
|
||||
echo "LOGROCKET_APP_ID=${{ secrets.LOGROCKET_APP_ID }}" >> ./environments/.env.production
|
||||
echo "EXTENSIONS=@fleetbase/billing-engine,@fleetbase/internals-engine" >> ./environments/.env.production
|
||||
working-directory: ./console
|
||||
|
||||
- name: Set Env Variables for Production
|
||||
if: startsWith(github.ref, 'refs/heads/deploy/production')
|
||||
run: |
|
||||
echo "STRIPE_KEY=${{ secrets.STRIPE_KEY }}" >> ./environments/.env.production
|
||||
echo "LOGROCKET_APP_ID=${{ secrets.LOGROCKET_APP_ID }}" >> ./environments/.env.production
|
||||
echo "EXTENSIONS=@fleetbase/billing-engine,@fleetbase/internals-engine" >> ./environments/.env.production
|
||||
echo "STRIPE_PRICE_LICENSE_ANNUAL_ID=${{ secrets.STRIPE_PRICE_LICENSE_ANNUAL_ID }}" >> ./environments/.env.production
|
||||
echo "STRIPE_PRICE_LICENSE_MONTHLY_ID=${{ secrets.STRIPE_PRICE_LICENSE_MONTHLY_ID }}" >> ./environments/.env.production
|
||||
echo "STRIPE_PRICE_LICENSE_MAJOR_ID=${{ secrets.STRIPE_PRICE_LICENSE_MAJOR_ID }}" >> ./environments/.env.production
|
||||
echo "STRIPE_PRICE_LICENSE_MINOR_ID=${{ secrets.STRIPE_PRICE_LICENSE_MINOR_ID }}" >> ./environments/.env.production
|
||||
echo "STRIPE_PRICE_INSTALLATION_FULL_ID=${{ secrets.STRIPE_PRICE_INSTALLATION_FULL_ID }}" >> ./environments/.env.production
|
||||
echo "STRIPE_PRICE_INSTALLATION_ASSISTED_ID=${{ secrets.STRIPE_PRICE_INSTALLATION_ASSISTED_ID }}" >> ./environments/.env.production
|
||||
working-directory: ./console
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -224,10 +240,6 @@ jobs:
|
||||
set -u
|
||||
|
||||
DEPLOY_BUCKET=${STATIC_DEPLOY_BUCKET:-${{ env.PROJECT }}-${{ env.STACK }}}
|
||||
NEW_BUCKET="${PROJECT}-${STACK}-console"
|
||||
if aws s3api head-bucket --bucket "$NEW_BUCKET" 2>/dev/null; then
|
||||
DEPLOY_BUCKET="$NEW_BUCKET"
|
||||
fi
|
||||
|
||||
# this value will come from the dotenv above
|
||||
echo "Deploying to $DEPLOY_BUCKET"
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@
|
||||
docker-compose.override.yml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
console/public/extensions.json
|
||||
api/public/hot
|
||||
api/public/storage
|
||||
api/storage/*.key
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
http://:8000 {
|
||||
root * /fleetbase/api/public
|
||||
encode zstd br gzip
|
||||
|
||||
php_server {
|
||||
resolve_root_symlink
|
||||
}
|
||||
|
||||
582
README.md
582
README.md
@@ -1,257 +1,325 @@
|
||||
<div id="hero">
|
||||
<p align="center" dir="auto">
|
||||
<a href="https://fleetbase.io" rel="nofollow">
|
||||
<img src="https://user-images.githubusercontent.com/58805033/191936702-fed04b0f-7966-4041-96d0-95e27bf98248.png" alt="Fleetbase logo" width="500" height="120" style="max-width: 100%;">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center" dir="auto">
|
||||
Modular logistics and supply chain operating system
|
||||
<br>
|
||||
<a href="https://docs.fleetbase.io/" rel="nofollow" target="_fleetbase_docs">Documentation</a>
|
||||
·
|
||||
<a href="https://console.fleetbase.io" rel="nofollow" target="_fleetbase_console">Cloud Version</a>
|
||||
·
|
||||
<a href="https://console.fleetbase.io/aws-marketplace" rel="nofollow" target="_aws_marketplace">Deploy on AWS</a>
|
||||
·
|
||||
<a href="https://tally.so/r/3NBpAW" rel="nofollow">Book a Demo</a>
|
||||
·
|
||||
<a href="https://discord.gg/V7RVWRQ2Wm" target="discord" rel="nofollow">Discord</a>
|
||||
</p>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
## What is Fleetbase?
|
||||
|
||||
Fleetbase is a modular logistics and supply chain operating system designed to streamline management, planning, optimization, and operational control across various sectors of the supply chain industry.
|
||||
|
||||
<p align="center" dir="auto">
|
||||
<img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/fleetbase_overview.png" alt="Fleetbase Console" width="1200" style="max-width: 100%;" />
|
||||
</p>
|
||||
|
||||
## Visual Feature Showcase
|
||||
|
||||
<p align="center" dir="auto">
|
||||
<img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/order-board-kanban.png" alt="Fleetbase Order Board" width="1200" style="max-width: 100%;" />
|
||||
<em>Visualize and manage your orders with a dynamic Kanban board.</em>
|
||||
</p>
|
||||
|
||||
<p align="center" dir="auto">
|
||||
<img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/order-workflow-config.png" alt="Fleetbase Order Workflow Configuration" width="1200" style="max-width: 100%;" />
|
||||
<em>Create custom order flows and automation with the intuitive workflow builder.</em>
|
||||
</p>
|
||||
|
||||
<p align="center" dir="auto">
|
||||
<img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/order-map-view.png" alt="Fleetbase Order Map View" width="1200" style="max-width: 100%;" />
|
||||
<em>Track individual orders in real-time on an interactive map.</em>
|
||||
</p>
|
||||
|
||||
<p align="center" dir="auto">
|
||||
<img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/live-map-tracking.png" alt="Fleetbase Live Map Tracking" width="1200" style="max-width: 100%;" />
|
||||
<em>Get a complete overview of your fleet and active orders on a live map.</em>
|
||||
</p>
|
||||
|
||||
<p align="center" dir="auto">
|
||||
<img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/fleet-map-zones.png" alt="Fleetbase Fleet Map with Zones" width="1200" style="max-width: 100%;" />
|
||||
<em>Define and manage service areas and zones for your fleet.</em>
|
||||
</p>
|
||||
|
||||
**Quickstart**
|
||||
|
||||
```bash
|
||||
git clone git@github.com:fleetbase/fleetbase.git
|
||||
cd fleetbase && ./scripts/docker-install.sh
|
||||
```
|
||||
|
||||
## 📖 Table of contents
|
||||
|
||||
- [Features](#-features)
|
||||
- [Install](#-install)
|
||||
- [Deploy on AWS](#-deploy-on-aws-in-one-click)
|
||||
- [Extensions](#-extensions)
|
||||
- [Apps](#-apps)
|
||||
- [Roadmap](#-roadmap)
|
||||
- [Bugs and Feature Requests](#-bugs-and--feature-requests)
|
||||
- [Documentation](#-documentation)
|
||||
- [Contributing](#-contributing)
|
||||
- [Community](#-community)
|
||||
- [Creators](#-creators)
|
||||
- [License & Copyright](#-license-and-copyright)
|
||||
|
||||
## 📦 Features
|
||||
- **Extensible:** Build installable extensions and additional functionality directly into the OS via modular architecture.
|
||||
- **Developer Friendly:** RESTful API, socket, and webhooks to seamlessly integrate with external systems or develop custom applications.
|
||||
- **Native Apps:** Collection of open-source and native apps designed for operations and customer facing.
|
||||
- **Collaboration:** Dedicated chat and comments system for collaboration across your organization.
|
||||
- **Security:** Secure data encryption, adherence to industry-standard security practices, and a comprehensive dynamic Identity and Access Management (IAM) system.
|
||||
- **Telematics:** Integrate and connect to hardware devices and sensors to provide more feedback and visibility into operations.
|
||||
- **Internationalized:** Translate into multiple languages to accommodate diverse user bases and global operations.
|
||||
- **Framework:** PHP core built around logistics and supply chain abstractions to streamline extension development.
|
||||
- **Dynamic:** Configurable rules, flows and logic to enable automation and customization.
|
||||
- **UI/UX:** Clean, responsive user-friendly interface for efficient management and operations from desktop or mobile.
|
||||
- **Dashboards:** Create custom dashboards and widgets to get full visibility into operations.
|
||||
- **Scalability:** Uninterrupted growth with scalable infrastructure and design, capable of handling increasing data volume and user demand as your business expands.
|
||||
- **Continuous Improvements:** Commitment to continuous improvement, providing regular updates that seamlessly introduce optimizations, new features, and overall enhancements to the OS.
|
||||
- **Open Source:** Deploy it either on-premise or in the cloud according to your organization's needs and preferences.
|
||||
|
||||
## 💾 Install
|
||||
Getting up and running with Fleetbase via Docker is the quickest and most straightforward way. If you’d like to use Fleetbase without docker read the [full install guide in the Fleetbase documentation](https://docs.fleetbase.io/getting-started/install).
|
||||
|
||||
Make sure you have both the latest versions of docker and docker-compose installed on your system.
|
||||
|
||||
```bash
|
||||
git clone git@github.com:fleetbase/fleetbase.git
|
||||
cd fleetbase && ./scripts/docker-install.sh
|
||||
```
|
||||
|
||||
### Accessing Fleetbase
|
||||
Once successfully installed and running you can then access the Fleetbase console on port 4200 and the API will be accessible from port 8000.
|
||||
|
||||
Fleetbase Console: http://localhost:4200
|
||||
Fleetbase API: http://localhost:8000
|
||||
|
||||
### Additional Configurations
|
||||
|
||||
**CORS:** If you’re installing directly on a server you will need to configure the environment variables to the application container:
|
||||
```
|
||||
CONSOLE_HOST=http://{yourhost}:4200
|
||||
```
|
||||
If you have additional applications or frontends you can use the environment variable `FRONTEND_HOSTS` to add a comma delimited list of additioal frontend hosts.
|
||||
|
||||
**Application Key** If you get an issue about a missing application key just run:
|
||||
```bash
|
||||
docker compose exec application bash -c "php artisan key:generate --show"
|
||||
```
|
||||
Next copy this value to the `APP_KEY` environment variable in the application container and restart.
|
||||
|
||||
**Routing:** Fleetbase ships with a default OSRM server hosted by `[router.project-osrm.org](https://router.project-osrm.org)` but you’re able to use your own or any other OSRM compatible server. You can modify this in the `console/environments` directory by modifying the .env file of the environment you’re deploying and setting the `OSRM_HOST` to the OSRM server for Fleetbase to use.
|
||||
|
||||
**Services:** There are a few environment variables which need to be set for Fleetbase to function with full features. If you're deploying with docker then it's easiest to just create a `docker-compose.override.yml` and supply the environment variables in this file.
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
services:
|
||||
application:
|
||||
environment:
|
||||
CONSOLE_HOST: http://localhost:4200
|
||||
MAIL_MAILER: (ses, smtp, mailgun, postmark, sendgrid)
|
||||
OSRM_HOST: https://router.project-osrm.org
|
||||
IPINFO_API_KEY:
|
||||
GOOGLE_MAPS_API_KEY:
|
||||
GOOGLE_MAPS_LOCALE: us
|
||||
TWILIO_SID:
|
||||
TWILIO_TOKEN:
|
||||
TWILIO_FROM:
|
||||
|
||||
socket:
|
||||
environment:
|
||||
# IMPORTANT: Configure WebSocket origins for security
|
||||
# Development (localhost only - include WebSocket protocols):
|
||||
SOCKETCLUSTER_OPTIONS: '{"origins":"http://localhost:*,https://localhost:*,ws://localhost:*,wss://localhost:*"}'
|
||||
# Production (replace with your actual domain):
|
||||
# SOCKETCLUSTER_OPTIONS: '{"origins":"https://yourdomain.com:*,wss://yourdomain.com:*"}'
|
||||
```
|
||||
|
||||
**WebSocket Security:** The `SOCKETCLUSTER_OPTIONS` environment variable controls which domains can connect to the WebSocket server. Always restrict origins to your specific domains in production to prevent security vulnerabilities.
|
||||
|
||||
You can learn more about full installation, and configuration in the [official documentation](https://docs.fleetbase.io/getting-started/install).
|
||||
|
||||
## 🚀 Deploy on AWS in One Click
|
||||
|
||||
Deploy your complete Fleetbase logistics platform on AWS with enterprise-grade security, scalability, and reliability. No DevOps expertise required!
|
||||
|
||||
[](https://console.fleetbase.io/aws-marketplace)
|
||||
|
||||
### ✨ What You Get
|
||||
|
||||
- **Complete AWS Infrastructure**: ECS Fargate, RDS MySQL, ElastiCache Redis, S3, CloudFront, and more
|
||||
- **25-Minute Setup**: From zero to production-ready logistics platform
|
||||
- **Enterprise Security**: VPC isolation, encrypted storage, secrets management
|
||||
- **Auto-Scaling**: Handle traffic spikes with ECS Fargate auto-scaling
|
||||
- **High Availability**: Multi-AZ deployment with 99.9% uptime SLA
|
||||
- **Cost Optimized**: Pay-as-you-use with optimized resource allocation
|
||||
|
||||
### 🏗️ Infrastructure Included
|
||||
|
||||
Your AWS deployment includes a complete, production-ready infrastructure stack:
|
||||
|
||||
- **Compute**: ECS Fargate cluster with auto-scaling services
|
||||
- **Database**: RDS MySQL 8.0 with automated backups and Multi-AZ support
|
||||
- **Cache**: ElastiCache Redis for high-performance caching
|
||||
- **Storage**: S3 object storage with CloudFront CDN for global distribution
|
||||
- **Networking**: VPC with private subnets, NAT gateways, and security groups
|
||||
- **Load Balancing**: Application Load Balancer with SSL certificates
|
||||
- **Monitoring**: CloudWatch logs, container insights, and health monitoring
|
||||
- **Messaging**: SQS message queues for background job processing
|
||||
|
||||
[**🚀 Deploy Now**](https://console.fleetbase.io/aws-marketplace) | [**📖 Learn More**](https://docs.fleetbase.io/category/deploying/aws)
|
||||
|
||||
# 🧩 Extensions
|
||||
|
||||
Extensions are modular components that enhance the functionality of your Fleetbase instance. They allow you to add new features, customize existing behavior, or integrate with external systems.
|
||||
|
||||
You can find extensions available from the official [Fleetbase Console](https://console.fleetbase.io), here you will also be able get your registry token to install extensions to a self-hosted Fleetbase instance.
|
||||
|
||||
Additionally you're able to develop and publish your own extensions as well which you can read more about developing extensions via the [extension building guide](https://docs.fleetbase.io/developers/building-an-extension).
|
||||
|
||||
## ⌨️ Fleetbase CLI
|
||||
|
||||
The Fleetbase CLI is a powerful tool designed to simplify the management of extensions for your Fleetbase instance. With the CLI, you can effortlessly handle authentication, install and uninstall extensions, and scaffold new extensions if you are developing your own.
|
||||
|
||||
Get started with the CLI with npm:
|
||||
|
||||
```bash
|
||||
npm i -g @fleetbase/cli
|
||||
```
|
||||
|
||||
Once installed, you can access a variety of commands to manage your Fleetbase extensions.
|
||||
|
||||
# 📱 Apps
|
||||
|
||||
Fleetbase offers a few open sourced apps which are built on Fleetbase which can be cloned and customized. Every app is built so that the Fleetbase instance can be switched out whether on-premise install or cloud hosted.
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/fleetbase/storefront-app">Storefront App</a>: Fleetbase based ecommerce/on-demand app for launching your very own shop or marketplace to Apple or Android playstore.</li>
|
||||
<li><a href="https://github.com/fleetbase/navigator-app">Navigator App</a>: Fleetbase based driver app which can be used for drivers to manage and update order, additionally provides real time driver location which can be viewed in the Fleetbase Console.</li>
|
||||
</ul>
|
||||
|
||||
## 🛣️ Roadmap
|
||||
1. **Inventory and Warehouse Management** ~ Pallet will be Fleetbase’s first official extension for WMS & Inventory.
|
||||
2. **Accounting and Invoicing** ~ Ledger will be Fleetbase’s first official extension accounting and invoicing.
|
||||
3. **AI** ~ AI Agent intrgation for system and workflows.
|
||||
4. **Dynamic Rules System** ~ Trigger events, tasks jobs from a rule builder on resources.
|
||||
|
||||
## 🪲 Bugs and 💡 Feature Requests
|
||||
|
||||
Have a bug or a feature request? Please check the <a href="https://github.com/fleetbase/fleetbase/issues">issue tracker</a> and search for existing and closed issues. If your problem or idea is not addressed yet, please <a href="https://github.com/fleetbase/fleetbase/issues/new">open a new issue</a>.
|
||||
|
||||
## 👨💻 Contributing
|
||||
|
||||
Please read through our <a href="https://github.com/fleetbase/fleetbase/blob/main/CONTRIBUTING.md">contributing guidelines</a>. Included are directions for opening issues, coding standards, and notes on development.
|
||||
|
||||
## 👥 Community
|
||||
|
||||
Get updates on Fleetbase's development and chat with the project maintainers and community members by joining our <a href="https://discord.gg/V7RVWRQ2Wm">Discord</a>.
|
||||
|
||||
<ul>
|
||||
<li>Follow <a href="https://x.com/fleetbase_io">@fleetbase_io on X</a>.</li>
|
||||
<li>Read and subscribe to <a href="https://www.fleetbase.io/blog-2">The Official Fleetbase Blog</a>.</li>
|
||||
<li>Ask and explore <a href="https://github.com/orgs/fleetbase/discussions">our GitHub Discussions</a>.</li>
|
||||
</ul>
|
||||
<p dir="auto">See the <a href="https://github.com/fleetbase/fleetbase/releases">Releases</a> section of our GitHub project for changelogs for each release version of Fleetbase.</p>
|
||||
<p>Release announcement posts on <a href="https://www.fleetbase.io/blog-2" rel="nofollow">the official Fleetbase blog</a> contain summaries of the most noteworthy changes made in each release.</p>
|
||||
|
||||
## Creators
|
||||
|
||||
<p dir="auto"><strong>Ronald A. Richardson</strong>- Co-founder & CTO</p>
|
||||
<img src="https://user-images.githubusercontent.com/58805033/230263021-212f2553-1269-473d-be94-313cb3eecfa5.png" alt="Ron Image" width="75" height="75" style="max-width: 100%;">
|
||||
<p><a href="https://github.com/orgs/fleetbase/people/roncodes">Github</a> | <a href="https://www.linkedin.com/in/ronald-a-richardson/">LinkedIn</a></p>
|
||||
|
||||
<p dir="auto"><strong>Shiv Thakker</strong> - Co-founder & CEO</p>
|
||||
<img src="https://user-images.githubusercontent.com/58805033/230262598-1ce6d0cc-fb65-41f9-8384-5cf5cbf369c7.png" alt="Shiv Image" width="75" height="75" style="max-width: 100%;">
|
||||
<p><a href="https://github.com/orgs/fleetbase/people/shivthakker">Github</a> | <a href="https://www.linkedin.com/in/shivthakker/">LinkedIn</a></p>
|
||||
|
||||
|
||||
# License & Copyright
|
||||
|
||||
Fleetbase is made available under the terms of the <a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank">GNU Affero General Public License 3.0 (AGPL 3.0)</a>. For other licenses <a href="mailto:hello@fleetbase.io" target="_blank">contact us</a>.
|
||||
|
||||
<div id="hero">
|
||||
<p align="center" dir="auto">
|
||||
<a href="https://fleetbase.io" rel="nofollow">
|
||||
<img src="https://user-images.githubusercontent.com/58805033/191936702-fed04b0f-7966-4041-96d0-95e27bf98248.png" alt="Fleetbase logo" width="500" height="120" style="max-width: 100%;">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center" dir="auto">
|
||||
<a href="https://github.com/fleetbase/fleetbase/blob/main/LICENSE"><img src="https://img.shields.io/github/license/fleetbase/fleetbase" alt="License"></a>
|
||||
<a href="https://github.com/fleetbase/fleetbase/releases"><img src="https://img.shields.io/github/v/release/fleetbase/fleetbase" alt="Latest Release"></a>
|
||||
<a href="https://github.com/fleetbase/fleetbase/stargazers"><img src="https://img.shields.io/github/stars/fleetbase/fleetbase?style=social" alt="GitHub Stars"></a>
|
||||
<a href="https://discord.gg/V7RVWRQ2Wm"><img src="https://img.shields.io/discord/699834923032248430?logo=discord&label=Discord" alt="Discord"></a>
|
||||
<a href="https://github.com/fleetbase/fleetbase/issues"><img src="https://img.shields.io/github/issues/fleetbase/fleetbase" alt="GitHub Issues"></a>
|
||||
</p>
|
||||
<p align="center" dir="auto">
|
||||
Modular logistics and supply chain operating system
|
||||
<br>
|
||||
<a href="https://docs.fleetbase.io/" rel="nofollow" target="_fleetbase_docs">Documentation</a>
|
||||
·
|
||||
<a href="https://console.fleetbase.io" rel="nofollow" target="_fleetbase_console">Cloud Version</a>
|
||||
·
|
||||
<a href="https://tally.so/r/3NBpAW" rel="nofollow">Book a Demo</a>
|
||||
·
|
||||
<a href="https://discord.gg/V7RVWRQ2Wm" target="discord" rel="nofollow">Discord</a>
|
||||
</p>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
## What is Fleetbase?
|
||||
|
||||
Fleetbase is a modular logistics and supply chain operating system designed to streamline management, planning, optimization, and operational control across various sectors of the supply chain industry.
|
||||
|
||||
<p align="center" dir="auto">
|
||||
<img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/fleetbase_overview.png" alt="Fleetbase Console" width="1200" style="max-width: 100%;" />
|
||||
</p>
|
||||
|
||||
## 🎯 Who Is Fleetbase For?
|
||||
|
||||
Fleetbase is designed for organizations that need powerful logistics and supply chain management:
|
||||
|
||||
- **E-commerce & Retail** - Manage deliveries, track orders, and optimize last-mile logistics
|
||||
- **Food & Beverage** - Coordinate restaurant deliveries, manage drivers, and track real-time orders
|
||||
- **Courier Services** - Dispatch drivers, optimize routes, and provide customer tracking
|
||||
- **Field Services** - Schedule technicians, manage service areas, and track job completion
|
||||
- **Enterprise Logistics** - Build custom supply chain solutions with full API access
|
||||
- **Developers** - Extend and customize with a modular architecture and comprehensive API
|
||||
|
||||
## Visual Feature Showcase
|
||||
|
||||
| Feature | Screenshot | Description |
|
||||
|---------|------------|-------------|
|
||||
| **Order Board** | <img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/order-board-kanban.png" alt="Fleetbase Order Board" width="600" /> | Visualize and manage your orders with a dynamic Kanban board. |
|
||||
| **Workflow Builder** | <img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/order-workflow-config.png" alt="Fleetbase Order Workflow Configuration" width="600" /> | Create custom order flows and automation with the intuitive workflow builder. |
|
||||
| **Order Tracking** | <img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/order-map-view.png" alt="Fleetbase Order Map View" width="600" /> | Track individual orders in real-time on an interactive map. |
|
||||
| **Live Fleet Map** | <img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/live-map-tracking.png" alt="Fleetbase Live Map Tracking" width="600" /> | Get a complete overview of your fleet and active orders on a live map. |
|
||||
| **Service Zones** | <img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/fleet-map-zones.png" alt="Fleetbase Fleet Map with Zones" width="600" /> | Define and manage service areas and zones for your fleet. |
|
||||
|
||||
**Quickstart**
|
||||
|
||||
```bash
|
||||
git clone git@github.com:fleetbase/fleetbase.git
|
||||
cd fleetbase && ./scripts/docker-install.sh
|
||||
```
|
||||
|
||||
## 📖 Table of contents
|
||||
|
||||
- [Features](#-features)
|
||||
- [Install](#-install)
|
||||
- [Extensions](#-extensions)
|
||||
- [Apps](#-apps)
|
||||
- [Roadmap](#-roadmap)
|
||||
- [Deployment Options](#-deployment-options)
|
||||
- [Bugs and Feature Requests](#-bugs-and--feature-requests)
|
||||
- [Documentation](#-documentation)
|
||||
- [Contributing](#-contributing)
|
||||
- [Community](#-community)
|
||||
- [Creators](#creators)
|
||||
- [License & Copyright](#license--copyright)
|
||||
|
||||
## 📦 Features
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| 🔌 **Extensible** | Build installable extensions and additional functionality directly into the OS via modular architecture. |
|
||||
| 👨💻 **Developer Friendly** | RESTful API, socket, and webhooks to seamlessly integrate with external systems or develop custom applications. |
|
||||
| 📱 **Native Apps** | Collection of open-source and native apps designed for operations and customer facing. |
|
||||
| 🤝 **Collaboration** | Dedicated chat and comments system for collaboration across your organization. |
|
||||
| 🔒 **Security** | Secure data encryption, adherence to industry-standard security practices, and a comprehensive dynamic Identity and Access Management (IAM) system. |
|
||||
| 📡 **Telematics** | Integrate and connect to hardware devices and sensors to provide more feedback and visibility into operations. |
|
||||
| 🌐 **Internationalized** | Translate into multiple languages to accommodate diverse user bases and global operations. |
|
||||
| ⚙️ **Framework** | PHP core built around logistics and supply chain abstractions to streamline extension development. |
|
||||
| 🔄 **Dynamic** | Configurable rules, flows and logic to enable automation and customization. |
|
||||
| 🎨 **UI/UX** | Clean, responsive user-friendly interface for efficient management and operations from desktop or mobile. |
|
||||
| 📊 **Dashboards** | Create custom dashboards and widgets to get full visibility into operations. |
|
||||
| 📈 **Scalability** | Uninterrupted growth with scalable infrastructure and design, capable of handling increasing data volume and user demand as your business expands. |
|
||||
| 🔄 **Continuous Improvements** | Commitment to continuous improvement, providing regular updates that seamlessly introduce optimizations, new features, and overall enhancements to the OS. |
|
||||
| 🌍 **Open Source** | Deploy it either on-premise or in the cloud according to your organization's needs and preferences. |
|
||||
|
||||
## 💾 Install
|
||||
Getting up and running with Fleetbase via Docker is the quickest and most straightforward way. If you'd like to use Fleetbase without docker read the [full install guide in the Fleetbase documentation](https://docs.fleetbase.io/getting-started/install).
|
||||
|
||||
Make sure you have both the latest versions of docker and docker-compose installed on your system.
|
||||
|
||||
```bash
|
||||
git clone git@github.com:fleetbase/fleetbase.git
|
||||
cd fleetbase && ./scripts/docker-install.sh
|
||||
```
|
||||
|
||||
### Accessing Fleetbase
|
||||
Once successfully installed and running you can then access the Fleetbase console on port 4200 and the API will be accessible from port 8000.
|
||||
|
||||
Fleetbase Console: http://localhost:4200
|
||||
Fleetbase API: http://localhost:8000
|
||||
|
||||
### Additional Configurations
|
||||
|
||||
**CORS:** If you're installing directly on a server you will need to configure the environment variables to the application container:
|
||||
```
|
||||
CONSOLE_HOST=http://{yourhost}:4200
|
||||
```
|
||||
If you have additional applications or frontends you can use the environment variable `FRONTEND_HOSTS` to add a comma delimited list of additional frontend hosts.
|
||||
|
||||
**Application Key** If you get an issue about a missing application key just run:
|
||||
```bash
|
||||
docker compose exec application bash -c "php artisan key:generate --show"
|
||||
```
|
||||
Next copy this value to the `APP_KEY` environment variable in the application container and restart.
|
||||
|
||||
**Routing:** Fleetbase ships with a default OSRM server hosted by [router.project-osrm.org](https://router.project-osrm.org) but you're able to use your own or any other OSRM compatible server. You can modify this in the `console/environments` directory by modifying the .env file of the environment you're deploying and setting the `OSRM_HOST` to the OSRM server for Fleetbase to use.
|
||||
|
||||
**Services:** There are a few environment variables which need to be set for Fleetbase to function with full features. If you're deploying with docker then it's easiest to just create a `docker-compose.override.yml` and supply the environment variables in this file.
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
services:
|
||||
application:
|
||||
environment:
|
||||
CONSOLE_HOST: http://localhost:4200
|
||||
MAIL_MAILER: (ses, smtp, mailgun, postmark, sendgrid)
|
||||
OSRM_HOST: https://router.project-osrm.org
|
||||
IPINFO_API_KEY:
|
||||
GOOGLE_MAPS_API_KEY:
|
||||
GOOGLE_MAPS_LOCALE: us
|
||||
TWILIO_SID:
|
||||
TWILIO_TOKEN:
|
||||
TWILIO_FROM:
|
||||
|
||||
socket:
|
||||
environment:
|
||||
# IMPORTANT: Configure WebSocket origins for security
|
||||
# Development (localhost only - include WebSocket protocols):
|
||||
SOCKETCLUSTER_OPTIONS: '{"origins":"http://localhost:*,https://localhost:*,ws://localhost:*,wss://localhost:*"}'
|
||||
# Production (replace with your actual domain):
|
||||
# SOCKETCLUSTER_OPTIONS: '{"origins":"https://yourdomain.com:*,wss://yourdomain.com:*"}'
|
||||
```
|
||||
|
||||
**WebSocket Security:** The `SOCKETCLUSTER_OPTIONS` environment variable controls which domains can connect to the WebSocket server. Always restrict origins to your specific domains in production to prevent security vulnerabilities.
|
||||
|
||||
You can learn more about full installation, and configuration in the [official documentation](https://docs.fleetbase.io/getting-started/install).
|
||||
|
||||
# 🧩 Extensions
|
||||
|
||||
Extensions are modular components that enhance the functionality of your Fleetbase instance. They allow you to add new features, customize existing behavior, or integrate with external systems.
|
||||
|
||||
You can find extensions available from the official [Fleetbase Console](https://console.fleetbase.io), here you will also be able get your registry token to install extensions to a self-hosted Fleetbase instance.
|
||||
|
||||
Additionally you're able to develop and publish your own extensions as well which you can read more about developing extensions via the [extension building guide](https://docs.fleetbase.io/developers/building-an-extension).
|
||||
|
||||
## ⌨️ Fleetbase CLI
|
||||
|
||||
The Fleetbase CLI is a powerful tool designed to simplify the management of extensions for your Fleetbase instance. With the CLI, you can effortlessly handle authentication, install and uninstall extensions, and scaffold new extensions if you are developing your own.
|
||||
|
||||
Get started with the CLI with npm:
|
||||
|
||||
```bash
|
||||
npm i -g @fleetbase/cli
|
||||
```
|
||||
|
||||
Once installed, you can access a variety of commands to manage your Fleetbase extensions.
|
||||
|
||||
# 📱 Apps
|
||||
|
||||
Fleetbase offers open-source mobile apps that can be customized and deployed:
|
||||
|
||||
| App | Description | Platform | Repository |
|
||||
|-----|-------------|----------|------------|
|
||||
| **Storefront App** | E-commerce/on-demand app for launching your own shop or marketplace | iOS & Android | [GitHub](https://github.com/fleetbase/storefront-app) |
|
||||
| **Navigator App** | Driver app for managing orders with real-time location tracking | iOS & Android | [GitHub](https://github.com/fleetbase/navigator-app) |
|
||||
|
||||
## 🛣️ Roadmap
|
||||
|
||||
| Feature | Status | Expected Release | Description |
|
||||
|---------|--------|------------------|-------------|
|
||||
| **Pallet (WMS)** | 🚧 In Development | Late Q1 / Early Q2 2026 | Inventory and Warehouse Management extension |
|
||||
| **Ledger** | 🚧 In Development | Late Q1 / Early Q2 2026 | Accounting and Invoicing extension |
|
||||
| **AI Agent** | 🔬 Research | Q4 2026 | AI integration for system and workflow automation |
|
||||
| **Dynamic Rules** | 📋 Planned | 2027 | Rule builder to trigger events, tasks, and jobs |
|
||||
|
||||
Want to influence our roadmap? [Join the discussion](https://github.com/orgs/fleetbase/discussions)
|
||||
|
||||
## 🚀 Deployment Options
|
||||
|
||||
| Option | Best For | Setup Time | Maintenance |
|
||||
|--------|----------|------------|-------------|
|
||||
| **Docker (Local)** | Development & Testing | 5 minutes | Self-managed |
|
||||
| **On-Premise** | Production on your own infrastructure | 30-60 minutes | Self-managed |
|
||||
| **Cloud Self-Hosted** | Production (AWS, GCP, Azure) | 30-60 minutes | Self-managed |
|
||||
| **Fleetbase Cloud** | Quick start, no DevOps | Instant | Fully managed |
|
||||
|
||||
[View detailed deployment guides →](https://docs.fleetbase.io/category/deploying)
|
||||
|
||||
## 🐛 Bugs and 💡 Feature Requests
|
||||
|
||||
Have a bug or a feature request? Please check the <a href="https://github.com/fleetbase/fleetbase/issues">issue tracker</a> and search for existing and closed issues. If your problem or idea is not addressed yet, please <a href="https://github.com/fleetbase/fleetbase/issues/new">open a new issue</a>.
|
||||
|
||||
## 📄 Documentation
|
||||
|
||||
Fleetbase has comprehensive documentation to help you get started and make the most of the platform:
|
||||
|
||||
- **Getting Started**: [Installation Guide](https://docs.fleetbase.io/getting-started/install)
|
||||
- **API Reference**: [API Documentation](https://docs.fleetbase.io/api-reference)
|
||||
- **Extension Development**: [Building Extensions](https://docs.fleetbase.io/developers/building-an-extension)
|
||||
- **Deployment**: [Deployment Guides](https://docs.fleetbase.io/deployment)
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We welcome contributions from the community! Here's how you can help:
|
||||
|
||||
- **Report Bugs**: [Open an issue](https://github.com/fleetbase/fleetbase/issues/new)
|
||||
- **Suggest Features**: [Start a discussion](https://github.com/orgs/fleetbase/discussions)
|
||||
- **Submit PRs**: Read our [Contributing Guide](https://github.com/fleetbase/fleetbase/blob/main/CONTRIBUTING.md)
|
||||
- **Write Documentation**: Help improve our [docs](https://docs.fleetbase.io)
|
||||
- **Build Extensions**: Create and share [extensions](https://docs.fleetbase.io/developers/building-an-extension)
|
||||
|
||||
**Development Setup**: See our [Development Installation Guide](https://docs.fleetbase.io/getting-started/install/for-development) for detailed instructions on setting up your local development environment.
|
||||
|
||||
## 👥 Community
|
||||
|
||||
Get updates on Fleetbase's development and chat with the project maintainers and community members by joining our <a href="https://discord.gg/V7RVWRQ2Wm">Discord</a>.
|
||||
|
||||
<ul>
|
||||
<li>Follow <a href="https://x.com/fleetbase_io">@fleetbase_io on X</a>.</li>
|
||||
<li>Read and subscribe to <a href="https://www.fleetbase.io/blog-2">The Official Fleetbase Blog</a>.</li>
|
||||
<li>Ask and explore <a href="https://github.com/orgs/fleetbase/discussions">our GitHub Discussions</a>.</li>
|
||||
</ul>
|
||||
<p dir="auto">See the <a href="https://github.com/fleetbase/fleetbase/releases">Releases</a> section of our GitHub project for changelogs for each release version of Fleetbase.</p>
|
||||
<p>Release announcement posts on <a href="https://www.fleetbase.io/blog-2" rel="nofollow">the official Fleetbase blog</a> contain summaries of the most noteworthy changes made in each release.</p>
|
||||
|
||||
## Creators
|
||||
|
||||
<table style="border: none;">
|
||||
<tr>
|
||||
<td align="center" style="border: none;">
|
||||
<img src="https://user-images.githubusercontent.com/58805033/230263021-212f2553-1269-473d-be94-313cb3eecfa5.png" alt="Ronald A. Richardson" width="120" height="120" style="border-radius: 50%;">
|
||||
<br>
|
||||
<strong>Ronald A. Richardson</strong>
|
||||
<br>
|
||||
Co-founder & CTO
|
||||
<br>
|
||||
<a href="https://github.com/orgs/fleetbase/people/roncodes">GitHub</a> | <a href="https://www.linkedin.com/in/ronald-a-richardson/">LinkedIn</a>
|
||||
</td>
|
||||
<td align="center" style="border: none;">
|
||||
<img src="https://user-images.githubusercontent.com/58805033/230262598-1ce6d0cc-fb65-41f9-8384-5cf5cbf369c7.png" alt="Shiv Thakker" width="120" height="120" style="border-radius: 50%;">
|
||||
<br>
|
||||
<strong>Shiv Thakker</strong>
|
||||
<br>
|
||||
Co-founder & CEO
|
||||
<br>
|
||||
<a href="https://github.com/orgs/fleetbase/people/shivthakker">GitHub</a> | <a href="https://www.linkedin.com/in/shivthakker/">LinkedIn</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
# License & Copyright
|
||||
|
||||
Fleetbase is available under a **dual-licensing model** to accommodate both open-source community users and commercial enterprises:
|
||||
|
||||
## Open Source License (AGPL-3.0)
|
||||
|
||||
By default, Fleetbase is licensed under the [GNU Affero General Public License v3.0 (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.html). This license allows you to use, modify, and distribute Fleetbase freely, provided that:
|
||||
|
||||
- Any modifications or derivative works are also made available under AGPL-3.0
|
||||
- If you run a modified version as a network service, you must make the source code available to users
|
||||
|
||||
The AGPL-3.0 is ideal for open-source projects, academic research, and organizations committed to sharing their improvements with the community.
|
||||
|
||||
## Commercial License (FCL)
|
||||
|
||||
For organizations that require more flexibility, Fleetbase offers a **Fleetbase Commercial License (FCL)** that provides:
|
||||
|
||||
- **Freedom from AGPL obligations** – Deploy and modify Fleetbase without source code disclosure requirements
|
||||
- **Proprietary integrations** – Build closed-source extensions and integrations
|
||||
- **Commercial protections** – Warranties, indemnities, and legal assurances not provided under AGPL
|
||||
- **Derivative work ownership** – Retain full ownership of your modifications and customizations
|
||||
- **Flexible licensing options** – Choose from annual, monthly, or perpetual license models
|
||||
|
||||
### Commercial License Options
|
||||
|
||||
| License Type | Price | Support & Updates | Best For |
|
||||
|--------------|-------|-------------------|----------|
|
||||
| **Annual License** | $25,000/year | ✅ All upgrades & Business Support included | Organizations requiring continuous updates and support |
|
||||
| **Monthly License** | $2,500/month | ✅ All upgrades & Business Support included | Pilot projects and short-term deployments |
|
||||
| **Major Version License** | $25,000 (one-time) | ❌ No ongoing support | Stable deployments on a single major version |
|
||||
| **Minor Version License** | $15,000 (one-time) | ❌ No ongoing support | Locked version deployments |
|
||||
|
||||
### When You Need a Commercial License
|
||||
|
||||
You should consider a commercial license if you:
|
||||
|
||||
- Want to build proprietary extensions or integrations without open-sourcing them
|
||||
- Need to embed Fleetbase in a commercial product without AGPL obligations
|
||||
- Require enterprise-grade support, SLAs, and legal protections
|
||||
- Plan to modify Fleetbase without sharing your changes publicly
|
||||
|
||||
### Get a Commercial License
|
||||
|
||||
For more information about commercial licensing options, please contact us:
|
||||
|
||||
- **Email:** [hello@fleetbase.io](mailto:hello@fleetbase.io)
|
||||
- **Website:** [fleetbase.io](https://fleetbase.io)
|
||||
|
||||
---
|
||||
|
||||
**Copyright © 2026 Fleetbase Pte. Ltd.** All rights reserved.
|
||||
|
||||
|
||||
17
RELEASE.md
17
RELEASE.md
@@ -1,14 +1,19 @@
|
||||
# 🚀 Fleetbase v0.7.22 — 2025-12-07
|
||||
# 🚀 Fleetbase v0.7.27 — 2026-02-05
|
||||
|
||||
> "Organizations can now set their own alpha-numeric sender ID for SMS"
|
||||
> "Improvements and patches"
|
||||
|
||||
---
|
||||
|
||||
## ✨ Highlights
|
||||
- **Custom Alphanumeric Sender ID for SMS:**
|
||||
Organizations can now configure their own **Alphanumeric Sender ID** used when sending verification codes and other SMS notifications.
|
||||
This feature improves brand recognition, enhances trust, and aligns outbound communication with each organization’s identity.
|
||||
Supported in regions/carriers where alphanumeric senders are allowed (e.g., Mongolia and others).
|
||||
- Core now supports disabling cache in runtime for `HasApiModelCache` x `HasApiModelBehavior`
|
||||
- Added new `FileResolverService` to support file resolution from file resources, URL's, base64, and file uploads [190c03d](https://github.com/fleetbase/core-api/pull/187/commits/190c03d484648319f3d890439f74e45820f352fc)
|
||||
- `VerificationCode` model in core always throws SMS exceptions
|
||||
- FleetOps: Patched proof of delivery component in order details
|
||||
- FleetOps: Improved and patched service rate `getServicableForPlaces` which improved service quote performance
|
||||
- FleetOps: Fix location GeoJSON Point casting for `location` properties - using the new `Utils::castPoint` utility [208151f](https://github.com/fleetbase/fleetops/pull/202/commits/208151f37ece54bb23cfeeebdbb6fde1142908f7)
|
||||
- Storefront: Critical patch for QPay checkout workflow [storefront#66](https://github.com/fleetbase/storefront/pull/66)
|
||||
- Storefront: Added new phone number verification endpoints for customers (`request-phone-verification` and `verify-phone-number`)
|
||||
- Storefront: Fixed cart based service quotes
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ class Kernel extends HttpKernel
|
||||
],
|
||||
|
||||
'api' => [
|
||||
'throttle:api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -18,12 +18,21 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.0 <=8.2.28",
|
||||
"php": ">=8.0 <=8.2.30",
|
||||
"appstract/laravel-opcache": "^4.0",
|
||||
"fleetbase/core-api": "^1.6.28",
|
||||
"fleetbase/fleetops-api": "^0.6.29",
|
||||
"fleetbase/registry-bridge": "^0.1.2",
|
||||
"fleetbase/storefront-api": "^0.4.9",
|
||||
"fleetbase/core-api": "^1.6.35",
|
||||
"fleetbase/fleetops-api": "^0.6.34",
|
||||
"fleetbase/registry-bridge": "^0.1.5",
|
||||
"fleetbase/storefront-api": "^0.4.13",
|
||||
"fleetbase/aws-marketplace": "^0.0.8",
|
||||
"fleetbase/billing-api": "^0.1.20",
|
||||
"fleetbase/customer-portal-api": "^0.0.10",
|
||||
"fleetbase/flespi-integration": "^0.1.16",
|
||||
"fleetbase/internals-api": "^0.0.27",
|
||||
"fleetbase/samsara-api": "^0.0.3",
|
||||
"fleetbase/solid-api": "^0.0.7",
|
||||
"fleetbase/valhalla-api": "^0.0.3",
|
||||
"fleetbase/vroom-api": "^0.0.3",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"laravel/framework": "^10.0",
|
||||
"laravel/octane": "^2.3",
|
||||
@@ -36,7 +45,7 @@
|
||||
"psr/http-factory-implementation": "*",
|
||||
"resend/resend-php": "^0.14.0",
|
||||
"s-ichikawa/laravel-sendgrid-driver": "^4.0",
|
||||
"stripe/stripe-php": "13.13.0",
|
||||
"stripe/stripe-php": "^17.0",
|
||||
"symfony/mailgun-mailer": "^7.1",
|
||||
"symfony/postmark-mailer": "^7.1"
|
||||
},
|
||||
@@ -52,6 +61,14 @@
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://registry.fleetbase.io"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/fleetbase/aws-marketplace"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/fleetbase/internals"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
|
||||
2568
api/composer.lock
generated
2568
api/composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@ return [
|
||||
|
||||
'allowed_headers' => ['*'],
|
||||
|
||||
'exposed_headers' => ['x-compressed-json', 'access-console-sandbox', 'access-console-sandbox-key'],
|
||||
'exposed_headers' => ['x-compressed-json', 'access-console-sandbox', 'access-console-sandbox-key', 'content-disposition'],
|
||||
|
||||
'max_age' => 0,
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ return [
|
||||
'channels' => [
|
||||
'stack' => [
|
||||
'driver' => 'stack',
|
||||
'channels' => ['single'],
|
||||
'channels' => ['single', 'stdout'],
|
||||
'ignore_exceptions' => false,
|
||||
],
|
||||
|
||||
|
||||
@@ -105,8 +105,8 @@ return [
|
||||
OperationTerminated::class => [
|
||||
FlushOnce::class,
|
||||
FlushTemporaryContainerInstances::class,
|
||||
// DisconnectFromDatabases::class,
|
||||
// CollectGarbage::class,
|
||||
DisconnectFromDatabases::class,
|
||||
CollectGarbage::class,
|
||||
],
|
||||
|
||||
WorkerErrorOccurred::class => [
|
||||
|
||||
@@ -43,4 +43,10 @@ return [
|
||||
'secret' => env('STRIPE_SECRET', env('STRIPE_API_SECRET')),
|
||||
'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
|
||||
],
|
||||
|
||||
'callpromn' => [
|
||||
'api_key' => env('CALLPROMN_API_KEY', ''),
|
||||
'from' => env('CALLPROMN_FROM', ''),
|
||||
'base_url' => env('CALLPROMN_BASE_URL', 'https://api.messagepro.mn' ),
|
||||
],
|
||||
];
|
||||
|
||||
20
api/resources/lang/uz/auth.php
Normal file
20
api/resources/lang/uz/auth.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used during authentication for various
|
||||
| messages that we need to display to the user. You are free to modify
|
||||
| these language lines according to your application's requirements.
|
||||
|
|
||||
*/
|
||||
|
||||
'failed' => 'Ushbu hisobga olish maʼlumotlari bizning yozuvlarimizga mos kelmadi.',
|
||||
'password' => 'Taqdim etilgan parol noto‘g‘ri.',
|
||||
'throttle' => 'Kirishga urinishlar juda koʻp. Iltimos, :soniyadan keyin qayta urinib koʻring.',
|
||||
|
||||
];
|
||||
19
api/resources/lang/uz/pagination.php
Normal file
19
api/resources/lang/uz/pagination.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Pagination Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used by the paginator library to build
|
||||
| the simple pagination links. You are free to change them to anything
|
||||
| you want to customize your views to better match your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'previous' => '« Oldingi',
|
||||
'next' => 'Keyingi »',
|
||||
|
||||
];
|
||||
22
api/resources/lang/uz/passwords.php
Normal file
22
api/resources/lang/uz/passwords.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Reset Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are the default lines which match reasons
|
||||
| that are given by the password broker for a password update attempt
|
||||
| has failed, such as for an invalid token or invalid new password.
|
||||
|
|
||||
*/
|
||||
|
||||
'reset' => 'Sizning parolingiz tiklandi!',
|
||||
'sent' => 'Parolni tiklash havolasini elektron pochta orqali yubordik!',
|
||||
'throttled' => 'Qayta urinishdan oldin kuting.',
|
||||
'token' => 'Ushbu parolni tiklash belgisi yaroqsiz.',
|
||||
'user' => "Biz bu elektron pochta manziliga ega foydalanuvchini topa olmadik.",
|
||||
|
||||
];
|
||||
163
api/resources/lang/uz/validation.php
Normal file
163
api/resources/lang/uz/validation.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines contain the default error messages used by
|
||||
| the validator class. Some of these rules have multiple versions such
|
||||
| as the size rules. Feel free to tweak each of these messages here.
|
||||
|
|
||||
*/
|
||||
|
||||
'accepted' => ':attribute qabul qilinishi kerak.',
|
||||
'accepted_if' => ':other :value bo‘lsa, :attribute qabul qilinishi kerak.',
|
||||
'active_url' => ':attribute yaroqli URL emas.',
|
||||
'after' => ':attribute :date dan keyingi sana boʻlishi kerak.',
|
||||
'after_or_equal' => ':attribute :date ga teng yoki undan keyingi sana boʻlishi kerak.',
|
||||
'alpha' => ':attribute faqat harflardan iborat boʻlishi kerak.',
|
||||
'alpha_dash' => ':attribute faqat harflar, raqamlar, chiziqlar va pastki chiziqlardan iborat boʻlishi kerak.',
|
||||
'alpha_num' => ':attribute faqat harflar va raqamlardan iborat boʻlishi kerak.',
|
||||
'array' => ':attribute massiv boʻlishi kerak.',
|
||||
'before' => ':attribute :date gacha boʻlgan sana boʻlishi kerak.',
|
||||
'before_or_equal' => ':attribute :date ga teng yoki undan oldingi sana boʻlishi kerak.',
|
||||
'between' => [
|
||||
'numeric' => ':attribute :min va :max orasida boʻlishi kerak.',
|
||||
'file' => ':attribute :min va :max kilobayt orasida boʻlishi kerak.',
|
||||
'string' => ':attribute :min va :max belgilar orasida boʻlishi kerak.',
|
||||
'array' => ':attribute :min va :max ta element orasida boʻlishi kerak.',
|
||||
],
|
||||
'boolean' => ':attribute maydoni rost yoki yolgʻon boʻlishi kerak.',
|
||||
'confirmed' => ':attribute tasdiqlanishi mos kelmadi.',
|
||||
'current_password' => 'Parol noto‘g‘ri.',
|
||||
'date' => ':attribute yaroqli sana emas.',
|
||||
'date_equals' => ':attribute :date ga teng sana boʻlishi kerak.',
|
||||
'date_format' => ':attribute :format formatiga mos kelmadi.',
|
||||
'declined' => ':attribute rad etilishi kerak.',
|
||||
'declined_if' => ':other :value bo‘lsa, :attribute rad etilishi kerak.',
|
||||
'different' => ':attribute va :other har xil boʻlishi kerak.',
|
||||
'digits' => ':attribute :digits raqamdan iborat boʻlishi kerak.',
|
||||
'digits_between' => ':attribute :min va :max raqamlari orasida boʻlishi kerak.',
|
||||
'dimensions' => ':attribute rasm oʻlchamlari yaroqsiz.',
|
||||
'distinct' => ':attribute maydonida takroriy qiymat mavjud.',
|
||||
'email' => ':attribute yaroqli elektron pochta manzili boʻlishi kerak.',
|
||||
'ends_with' => ':attribute quyidagilardan biri bilan tugashi kerak: :values.',
|
||||
'enum' => 'Tanlangan :attribute yaroqsiz.',
|
||||
'exists' => 'Tanlangan :attribute yaroqsiz.',
|
||||
'file' => ':attribute fayl boʻlishi kerak.',
|
||||
'filled' => ':attribute maydonida qiymat boʻlishi kerak.',
|
||||
'gt' => [
|
||||
'numeric' => ':attribute :value dan katta boʻlishi kerak.',
|
||||
'file' => ':attribute :value kilobaytdan katta boʻlishi kerak.',
|
||||
'string' => ':attribute :value belgidan katta boʻlishi kerak.',
|
||||
'array' => ':attribute :value dan koʻp elementga ega boʻlishi kerak.',
|
||||
],
|
||||
'gte' => [
|
||||
'numeric' => ':attribute :value ga teng yoki undan katta boʻlishi kerak.',
|
||||
'file' => ':attribute :value kilobaytga teng yoki undan katta boʻlishi kerak.',
|
||||
'string' => ':attribute :value belgiga teng yoki undan katta boʻlishi kerak.',
|
||||
'array' => ':attribute :value yoki undan koʻp elementga ega boʻlishi kerak.',
|
||||
],
|
||||
'image' => ':attribute rasm boʻlishi kerak.',
|
||||
'in' => 'Tanlangan :attribute yaroqsiz.',
|
||||
'in_array' => ':attribute maydoni :other da mavjud emas.',
|
||||
'integer' => ':attribute butun son boʻlishi kerak.',
|
||||
'ip' => ':attribute yaroqli IP manzil boʻlishi kerak.',
|
||||
'ipv4' => ':attribute yaroqli IPv4 manzil boʻlishi kerak.',
|
||||
'ipv6' => ':attribute yaroqli IPv6 manzil boʻlishi kerak.',
|
||||
'json' => ':attribute yaroqli JSON qatori boʻlishi kerak.',
|
||||
'lt' => [
|
||||
'numeric' => ':attribute :value dan kichik boʻlishi kerak.',
|
||||
'file' => ':attribute :value kilobaytdan kichik boʻlishi kerak.',
|
||||
'string' => ':attribute :value belgidan kichik boʻlishi kerak.',
|
||||
'array' => ':attribute :value dan kam elementga ega boʻlishi kerak.',
|
||||
],
|
||||
'lte' => [
|
||||
'numeric' => ':attribute :value ga teng yoki undan kichik boʻlishi kerak.',
|
||||
'file' => ':attribute :value kilobaytga teng yoki undan kichik boʻlishi kerak.',
|
||||
'string' => ':attribute :value belgiga teng yoki undan kichik boʻlishi kerak.',
|
||||
'array' => ':attribute :value dan koʻp boʻlmagan elementga ega boʻlishi kerak.',
|
||||
],
|
||||
'mac_address' => ':attribute yaroqli MAC manzil boʻlishi kerak.',
|
||||
'max' => [
|
||||
'numeric' => ':attribute :max dan katta boʻlmasligi kerak.',
|
||||
'file' => ':attribute :max kilobaytdan katta boʻlmasligi kerak.',
|
||||
'string' => ':attribute :max belgidan katta boʻlmasligi kerak.',
|
||||
'array' => ':attribute :max dan koʻp boʻlmagan elementga ega boʻlishi kerak.',
|
||||
],
|
||||
'mimes' => ':attribute :values turidagi fayl boʻlishi kerak.',
|
||||
'mimetypes' => ':attribute :values turidagi fayl boʻlishi kerak.',
|
||||
'min' => [
|
||||
'numeric' => ':attribute kamida :min boʻlishi kerak.',
|
||||
'file' => ':attribute kamida :min kilobayt boʻlishi kerak.',
|
||||
'string' => ':attribute kamida :min belgidan iborat boʻlishi kerak.',
|
||||
'array' => ':attribute kamida :min ta elementga ega boʻlishi kerak.',
|
||||
],
|
||||
'multiple_of' => ':attribute :value ning karrali boʻlishi kerak.',
|
||||
'not_in' => 'Tanlangan :attribute yaroqsiz.',
|
||||
'not_regex' => ':attribute formati yaroqsiz.',
|
||||
'numeric' => ':attribute raqam boʻlishi kerak.',
|
||||
'password' => 'Parol noto‘g‘ri.',
|
||||
'present' => ':attribute maydoni mavjud boʻlishi kerak.',
|
||||
'prohibited' => ':attribute maydoni taqiqlangan.',
|
||||
'prohibited_if' => ':other :value bo‘lsa, :attribute maydoni taqiqlangan.',
|
||||
'prohibited_unless' => ':other :values da boʻlmasa, :attribute maydoni taqiqlangan.',
|
||||
'prohibits' => ':attribute maydoni :other ning mavjud boʻlishini taqiqlaydi.',
|
||||
'regex' => ':attribute formati yaroqsiz.',
|
||||
'required' => ':attribute maydoni toʻldirilishi shart.',
|
||||
'required_array_keys' => ':attribute maydonida :values uchun yozuvlar boʻlishi kerak.',
|
||||
'required_if' => ':other :value bo‘lsa, :attribute maydoni toʻldirilishi shart.',
|
||||
'required_unless' => ':other :values da boʻlmasa, :attribute maydoni toʻldirilishi shart.',
|
||||
'required_with' => ':values mavjud boʻlganda :attribute maydoni toʻldirilishi shart.',
|
||||
'required_with_all' => ':values mavjud boʻlganda :attribute maydoni toʻldirilishi shart.',
|
||||
'required_without' => ':values mavjud boʻlmaganda :attribute maydoni toʻldirilishi shart.',
|
||||
'required_without_all' => ':values ning hech biri mavjud boʻlmaganda :attribute maydoni toʻldirilishi shart.',
|
||||
'same' => ':attribute va :other mos kelishi kerak.',
|
||||
'size' => [
|
||||
'numeric' => ':attribute :size boʻlishi kerak.',
|
||||
'file' => ':attribute :size kilobayt boʻlishi kerak.',
|
||||
'string' => ':attribute :size belgidan iborat boʻlishi kerak.',
|
||||
'array' => ':attribute :size ta elementdan iborat boʻlishi kerak.',
|
||||
],
|
||||
'starts_with' => ':attribute quyidagilardan biri bilan boshlanishi kerak: :values.',
|
||||
'string' => ':attribute qator boʻlishi kerak.',
|
||||
'timezone' => ':attribute yaroqli vaqt mintaqasi boʻlishi kerak.',
|
||||
'unique' => ':attribute allaqachon olingan.',
|
||||
'uploaded' => ':attribute yuklab olinmadi.',
|
||||
'url' => ':attribute yaroqli URL boʻlishi kerak.',
|
||||
'uuid' => ':attribute yaroqli UUID boʻlishi kerak.',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify custom validation messages for attributes using the
|
||||
| convention "attribute.rule" to name the lines. This makes it quick to
|
||||
| specify a specific custom language line for a given attribute rule.
|
||||
|
|
||||
*/
|
||||
|
||||
'custom' => [
|
||||
'attribute-name' => [
|
||||
'rule-name' => 'custom-message',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Attributes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used to swap our attribute placeholder
|
||||
| with something more reader friendly such as "E-Mail Address" instead
|
||||
| of "email". This simply helps us make our message more expressive.
|
||||
|
|
||||
*/
|
||||
|
||||
'attributes' => [],
|
||||
|
||||
];
|
||||
@@ -2,6 +2,7 @@ import Application from '@ember/application';
|
||||
import Resolver from 'ember-resolver';
|
||||
import loadInitializers from 'ember-load-initializers';
|
||||
import config from '@fleetbase/console/config/environment';
|
||||
import './deprecation-workflow';
|
||||
|
||||
export default class App extends Application {
|
||||
modulePrefix = config.modulePrefix;
|
||||
|
||||
@@ -1,42 +1,61 @@
|
||||
<div class="bg-white dark:bg-gray-800 py-5 px-4 shadow rounded-lg w-full">
|
||||
<div class="mb-4">
|
||||
<Image src={{@brand.logo_url}} @fallbackSrc="/images/fleetbase-logo-svg.svg" alt={{t "app.name"}} height="56" class="h-10 object-contain mx-auto" />
|
||||
<div class="mt-2">
|
||||
<h2 class="text-center text-lg font-extrabold text-gray-900 dark:text-white truncate">
|
||||
{{t "onboard.index.title"}}
|
||||
</h2>
|
||||
<div class="flex items-center justify-center h-screen min-h-screen px-4 py-12 bg-gray-50 dark:bg-gray-900 sm:px-6 lg:px-8 overflow-y-scroll">
|
||||
<div class="w-full max-w-md h-screen flex items-center justify-center py-4">
|
||||
<div class="bg-white dark:bg-gray-800 py-5 px-4 shadow rounded-lg w-full">
|
||||
<div class="mb-4">
|
||||
<Image src={{@brand.logo_url}} @fallbackSrc="/images/fleetbase-logo-svg.svg" alt={{t "app.name"}} height="56" class="h-10 object-contain mx-auto" />
|
||||
<div class="mt-2">
|
||||
<h2 class="text-center text-lg font-extrabold text-gray-900 dark:text-white truncate">
|
||||
{{t "onboard.index.title"}}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex px-3 py-2 mb-4 rounded-md shadow-sm bg-blue-200">
|
||||
<div>
|
||||
<FaIcon @icon="hand-spock" @size="lg" class="text-blue-900 mr-4" />
|
||||
</div>
|
||||
<p class="flex-1 text-sm text-blue-900 dark:text-blue-900">
|
||||
{{t "onboard.index.welcome-title" htmlSafe=true companyName=(t "app.name")}}
|
||||
{{t "onboard.index.welcome-text"}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form {{on "submit" (perform this.onboard)}}>
|
||||
{{#if this.error}}
|
||||
<InfoBlock @icon="exclamation-triangle" @text={{this.error}} class="mb-6 px-3 py-2 bg-red-300 text-red-900" @textClass="text-red-900" />
|
||||
{{/if}}
|
||||
<InputGroup @name={{t "onboard.index.full-name"}} @value={{this.name}} @helpText={{t "onboard.index.full-name-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.your-email"}} @type="email" @value={{this.email}} @helpText={{t "onboard.index.your-email-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.phone"}} @helpText={{t "onboard.index.phone-help-text"}}>
|
||||
<PhoneInput @onInput={{fn (mut this.phone)}} class="form-input input-lg w-full" />
|
||||
</InputGroup>
|
||||
<InputGroup @name={{t "onboard.index.organization-name"}} @value={{this.organization_name}} @helpText={{t "onboard.index.organization-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.password"}} @value={{this.password}} @type="password" @helpText={{t "onboard.index.password-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup
|
||||
@name={{t "onboard.index.confirm-password"}}
|
||||
@value={{this.password_confirmation}}
|
||||
@type="password"
|
||||
@helpText={{t "onboard.index.confirm-password-help-text"}}
|
||||
@inputClass="input-lg"
|
||||
/>
|
||||
|
||||
<div class="flex items-center justify-end mt-5">
|
||||
<Button
|
||||
@buttonType="submit"
|
||||
@icon="check"
|
||||
@iconPrefix="fas"
|
||||
@type="primary"
|
||||
@size="lg"
|
||||
@text={{t "onboard.index.continue-button-text"}}
|
||||
@isLoading={{this.onboard.isRunning}}
|
||||
@disabled={{not this.filled}}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<RegistryYield @registry="onboard" as |YieldedComponent ctx|>
|
||||
<YieldedComponent @context={{ctx}} />
|
||||
</RegistryYield>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex px-3 py-2 mb-4 rounded-md shadow-sm bg-blue-200">
|
||||
<div>
|
||||
<FaIcon @icon="hand-spock" @size="lg" class="text-blue-900 mr-4" />
|
||||
</div>
|
||||
<p class="flex-1 text-sm text-blue-900 dark:text-blue-900">
|
||||
{{t "onboard.index.welcome-title" htmlSafe=true companyName=(t "app.name")}}
|
||||
{{t "onboard.index.welcome-text"}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form {{on "submit" (perform this.onboard)}}>
|
||||
{{#if this.error}}
|
||||
<InfoBlock @icon="exclamation-triangle" @text={{this.error}} class="mb-6 px-3 py-2 bg-red-300 text-red-900" @textClass="text-red-900" />
|
||||
{{/if}}
|
||||
<InputGroup @name={{t "onboard.index.full-name"}} @value={{this.name}} @helpText={{t "onboard.index.full-name-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.your-email"}} @type="email" @value={{this.email}} @helpText={{t "onboard.index.your-email-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.phone"}} @helpText={{t "onboard.index.phone-help-text"}}>
|
||||
<PhoneInput @onInput={{fn (mut this.phone)}} class="form-input input-lg w-full" />
|
||||
</InputGroup>
|
||||
<InputGroup @name={{t "onboard.index.organization-name"}} @value={{this.organization_name}} @helpText={{t "onboard.index.organization-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.password"}} @value={{this.password}} @type="password" @helpText={{t "onboard.index.password-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.confirm-password"}} @value={{this.password_confirmation}} @type="password" @helpText={{t "onboard.index.confirm-password-help-text"}} @inputClass="input-lg" />
|
||||
|
||||
<div class="flex items-center justify-end mt-5">
|
||||
<Button @buttonType="submit" @icon="check" @iconPrefix="fas" @type="primary" @size="lg" @text={{t "onboard.index.continue-button-text"}} @isLoading={{this.onboard.isRunning}} @disabled={{not this.filled}} />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<RegistryYield @registry="onboard" as |YieldedComponent ctx|>
|
||||
<YieldedComponent @context={{ctx}} />
|
||||
</RegistryYield>
|
||||
</div>
|
||||
@@ -1,78 +1,82 @@
|
||||
{{page-title (t "onboard.verify-email.header-title")}}
|
||||
|
||||
{{#if this.initialized}}
|
||||
<div class="bg-white dark:bg-gray-800 py-8 px-4 shadow rounded-lg w-full">
|
||||
<div class="mb-6">
|
||||
<LinkTo @route="console" class="flex items-center justify-center">
|
||||
<LogoIcon @size="12" class="rounded-md" />
|
||||
</LinkTo>
|
||||
<h2 class="mt-6 text-center text-lg font-extrabold text-gray-900 dark:text-white truncate">
|
||||
{{t "onboard.verify-email.title"}}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="flex items-center justify-center h-screen min-h-screen px-4 py-12 bg-gray-50 dark:bg-gray-900 sm:px-6 lg:px-8 overflow-y-scroll">
|
||||
<div class="w-full max-w-md h-screen flex items-center justify-center py-4">
|
||||
{{#if this.initialized}}
|
||||
<div class="bg-white dark:bg-gray-800 py-8 px-4 shadow rounded-lg w-full">
|
||||
<div class="mb-6">
|
||||
<LinkTo @route="console" class="flex items-center justify-center">
|
||||
<LogoIcon @size="12" class="rounded-md" />
|
||||
</LinkTo>
|
||||
<h2 class="mt-6 text-center text-lg font-extrabold text-gray-900 dark:text-white truncate">
|
||||
{{t "onboard.verify-email.title"}}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<InfoBlock @type="info" @icon="shield-halved" @iconSize="lg">
|
||||
{{t "onboard.verify-email.message-text" htmlSafe=true}}
|
||||
</InfoBlock>
|
||||
<InfoBlock @type="info" @icon="shield-halved" @iconSize="lg">
|
||||
{{t "onboard.verify-email.message-text" htmlSafe=true}}
|
||||
</InfoBlock>
|
||||
|
||||
<form class="mt-8 space-y-6" {{on "submit" (perform this.verify)}}>
|
||||
<InputGroup
|
||||
@type="tel"
|
||||
@name={{t "onboard.verify-email.verification-input-label"}}
|
||||
@value={{this.code}}
|
||||
@helpText={{t "onboard.verify-email.verification-code-text"}}
|
||||
@inputClass="input-lg"
|
||||
{{on "input" this.verification.validateInput}}
|
||||
{{did-insert this.verification.validateInput}}
|
||||
/>
|
||||
<form class="mt-8 space-y-6" {{on "submit" (perform this.verify)}}>
|
||||
<InputGroup
|
||||
@type="tel"
|
||||
@name={{t "onboard.verify-email.verification-input-label"}}
|
||||
@value={{this.code}}
|
||||
@helpText={{t "onboard.verify-email.verification-code-text"}}
|
||||
@inputClass="input-lg"
|
||||
{{on "input" this.verification.validateInput}}
|
||||
{{did-insert this.verification.validateInput}}
|
||||
/>
|
||||
|
||||
<div class="flex flex-row items-center space-x-4">
|
||||
<Button
|
||||
@icon="check"
|
||||
@iconPrefix="fas"
|
||||
@buttonType="submit"
|
||||
@type="primary"
|
||||
@size="lg"
|
||||
@text="Verify & Continue"
|
||||
@isLoading={{this.verify.isRunning}}
|
||||
@disabled={{not this.verification.ready}}
|
||||
/>
|
||||
<a href="#" {{on "click" this.verification.didntReceiveCode}} class="text-sm text-blue-400 hover:text-blue-300">{{t "onboard.verify-email.didnt-receive-a-code"}}</a>
|
||||
</div>
|
||||
<div class="flex flex-row items-center space-x-4">
|
||||
<Button
|
||||
@icon="check"
|
||||
@iconPrefix="fas"
|
||||
@buttonType="submit"
|
||||
@type="primary"
|
||||
@size="lg"
|
||||
@text="Verify & Continue"
|
||||
@isLoading={{this.verify.isRunning}}
|
||||
@disabled={{not this.verification.ready}}
|
||||
/>
|
||||
<a href="#" {{on "click" this.verification.didntReceiveCode}} class="text-sm text-blue-400 hover:text-blue-300">{{t "onboard.verify-email.didnt-receive-a-code"}}</a>
|
||||
</div>
|
||||
|
||||
{{#if this.verification.waiting}}
|
||||
<div class="flex flex-col flex-grow-0 flex-shrink-0 text-sm bg-yellow-800 border border-yellow-600 px-2 py-2 rounded-md text-yellow-100 my-4 transition-all">
|
||||
<div class="flex flex-row items-start mb-2">
|
||||
<div class="w-8 flex-grow-0 flex-shrink-0">
|
||||
<FaIcon @icon="triangle-exclamation" @size="xl" class="pt-1" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex-1 text-sm text-yellow-100">
|
||||
<div>{{t "auth.verification.didnt-receive-a-code" htmlSafe=true}}</div>
|
||||
<div>{{t "auth.verification.not-sent.alternative-choice" htmlSafe=true}}</div>
|
||||
{{#if this.verification.waiting}}
|
||||
<div class="flex flex-col flex-grow-0 flex-shrink-0 text-sm bg-yellow-800 border border-yellow-600 px-2 py-2 rounded-md text-yellow-100 my-4 transition-all">
|
||||
<div class="flex flex-row items-start mb-2">
|
||||
<div class="w-8 flex-grow-0 flex-shrink-0">
|
||||
<FaIcon @icon="triangle-exclamation" @size="xl" class="pt-1" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex-1 text-sm text-yellow-100">
|
||||
<div>{{t "auth.verification.didnt-receive-a-code" htmlSafe=true}}</div>
|
||||
<div>{{t "auth.verification.not-sent.alternative-choice" htmlSafe=true}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Button
|
||||
@text={{t "auth.verification.not-sent.resend-email"}}
|
||||
@buttonType="button"
|
||||
@type="link"
|
||||
class="text-yellow-100"
|
||||
@wrapperClass="px-4 py-2 bg-gray-900 bg-opacity-25 hover:opacity-50"
|
||||
@onClick={{this.verification.resendEmail}}
|
||||
/>
|
||||
<Button
|
||||
@text={{t "auth.verification.not-sent.send-by-sms"}}
|
||||
@buttonType="button"
|
||||
@type="link"
|
||||
class="text-yellow-100"
|
||||
@wrapperClass="px-4 py-2 bg-gray-900 bg-opacity-25 hover:opacity-50"
|
||||
@onClick={{this.verification.resendBySms}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Button
|
||||
@text={{t "auth.verification.not-sent.resend-email"}}
|
||||
@buttonType="button"
|
||||
@type="link"
|
||||
class="text-yellow-100"
|
||||
@wrapperClass="px-4 py-2 bg-gray-900 bg-opacity-25 hover:opacity-50"
|
||||
@onClick={{this.verification.resendEmail}}
|
||||
/>
|
||||
<Button
|
||||
@text={{t "auth.verification.not-sent.send-by-sms"}}
|
||||
@buttonType="button"
|
||||
@type="link"
|
||||
class="text-yellow-100"
|
||||
@wrapperClass="px-4 py-2 bg-gray-900 bg-opacity-25 hover:opacity-50"
|
||||
@onClick={{this.verification.resendBySms}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</form>
|
||||
{{/if}}
|
||||
</form>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
@@ -1,7 +1,9 @@
|
||||
<section class="onboarding step-host">
|
||||
{{#if this.initialized}}
|
||||
{{#if this.currentComponent}}
|
||||
{{component this.currentComponent context=this.context orchestrator=this.orchestrator brand=@brand}}
|
||||
{{#if this.orchestrator.wrapper}}
|
||||
{{component (lazy-engine-component this.orchestrator.wrapper) currentStepComponent=this.currentComponent context=this.context orchestrator=this.orchestrator brand=@brand}}
|
||||
{{else if this.currentComponent}}
|
||||
{{component (lazy-engine-component this.currentComponent) context=this.context orchestrator=this.orchestrator brand=@brand}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<div class="flex items-center justify-center min-h-24">
|
||||
|
||||
@@ -68,6 +68,7 @@ export default class ConsoleAccountIndexController extends Controller {
|
||||
subject_uuid: this.user.id,
|
||||
subject_type: 'user',
|
||||
type: 'user_avatar',
|
||||
resize: 'md'
|
||||
},
|
||||
(uploadedFile) => {
|
||||
this.user.setProperties({
|
||||
|
||||
@@ -77,7 +77,7 @@ export default class ConsoleAdminOrganizationsIndexUsersController extends Contr
|
||||
valuePath: 'roleName',
|
||||
},
|
||||
{
|
||||
label: this.intl.t('common.phone-number'),
|
||||
label: this.intl.t('common.phone'),
|
||||
valuePath: 'phone',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,6 +2,9 @@ import Controller from '@ember/controller';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default class ConsoleAdminVirtualController extends Controller {
|
||||
@tracked bodyClass = 'overflow-y-scroll h-full';
|
||||
@tracked containerClass = 'container mx-auto h-screen';
|
||||
@tracked wrapperClass = 'max-w-3xl my-10 mx-auto';
|
||||
@tracked view;
|
||||
queryParams = ['view'];
|
||||
}
|
||||
|
||||
9
console/app/deprecation-workflow.js
Normal file
9
console/app/deprecation-workflow.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import setupDeprecationWorkflow from 'ember-cli-deprecation-workflow';
|
||||
|
||||
setupDeprecationWorkflow({
|
||||
workflow: [
|
||||
{ handler: 'silence', matchId: 'ember-concurrency.deprecate-decorator-task' },
|
||||
{ handler: 'silence', matchId: 'new-helper-names' },
|
||||
{ handler: 'silence', matchId: 'ember-data:deprecate-non-strict-relationships' },
|
||||
],
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import translations from 'ember-intl/translations';
|
||||
import { all } from 'rsvp';
|
||||
|
||||
const isBrowser = typeof window !== 'undefined';
|
||||
const isValidLang = (lang) => typeof lang === 'string' && /^[a-z]{2,3}$/i.test(lang);
|
||||
|
||||
function langOf(tag = 'en') {
|
||||
return String(tag).toLowerCase().split('-')[0];
|
||||
@@ -15,6 +16,8 @@ async function loadBasePolyfills() {
|
||||
}
|
||||
|
||||
async function loadLocaleData(lang) {
|
||||
if (!isValidLang(lang)) return;
|
||||
|
||||
return all([
|
||||
import(`@formatjs/intl-numberformat/locale-data/${lang}.js`),
|
||||
import(`@formatjs/intl-pluralrules/locale-data/${lang}.js`),
|
||||
@@ -26,11 +29,11 @@ async function loadLocaleData(lang) {
|
||||
export function initialize(application) {
|
||||
if (!isBrowser) return;
|
||||
|
||||
// Build-time list of locales from the generated module
|
||||
const locales = translations.map(([locale]) => String(locale));
|
||||
const langs = [...new Set(locales.map(langOf))];
|
||||
const langs = [...new Set(locales.map(langOf).filter(isValidLang))];
|
||||
|
||||
application.deferReadiness();
|
||||
|
||||
(async () => {
|
||||
await loadBasePolyfills();
|
||||
await all(langs.map(loadLocaleData));
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
export function initialize(appInstance) {
|
||||
// Set window.Fleetbase to the application instance for global access
|
||||
// This is used by services and engines to access the root application instance
|
||||
if (typeof window !== 'undefined') {
|
||||
window.Fleetbase = appInstance;
|
||||
}
|
||||
|
||||
// Look up UniverseService and set the application instance
|
||||
const universeService = appInstance.lookup('service:universe');
|
||||
if (universeService) {
|
||||
|
||||
@@ -33,6 +33,7 @@ export default class Company extends Model {
|
||||
@attr('string') phone;
|
||||
@attr('string') status;
|
||||
@attr('string') slug;
|
||||
@attr('boolean', { defaultValue: false }) onboarding_completed;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') joined_at;
|
||||
|
||||
@@ -34,6 +34,7 @@ export default class UserModel extends Model {
|
||||
@attr('boolean') is_admin;
|
||||
@attr('boolean') is_subscribed;
|
||||
@attr('boolean') is_trialing;
|
||||
@attr('boolean') company_onboarding_completed;
|
||||
@attr('raw') meta;
|
||||
@attr('raw') subscription;
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ export default class OnboardIndexRoute extends Route {
|
||||
};
|
||||
|
||||
beforeModel() {
|
||||
this.orchestrator.start();
|
||||
// Resume from previous session if data exists in localStorage
|
||||
this.orchestrator.start(null, { resume: true });
|
||||
}
|
||||
|
||||
model() {
|
||||
|
||||
@@ -4,8 +4,6 @@ import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
|
||||
export default class UserSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {
|
||||
/**
|
||||
* Embedded relationship attributes
|
||||
*
|
||||
* @var {Object}
|
||||
*/
|
||||
get attrs() {
|
||||
return {
|
||||
@@ -16,22 +14,45 @@ export default class UserSerializer extends ApplicationSerializer.extend(Embedde
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize serializer so that the password is never sent to the server via Ember Data
|
||||
* Prevent partial payloads from overwriting fully-loaded
|
||||
* user records in the store.
|
||||
*
|
||||
* @param {Snapshot} snapshot
|
||||
* @param {Object} options
|
||||
* @return {Object} json
|
||||
* This runs ONLY on incoming data.
|
||||
*/
|
||||
normalize(modelClass, resourceHash, prop) {
|
||||
let normalized = super.normalize(modelClass, resourceHash, prop);
|
||||
|
||||
// Existing user already loaded in the store?
|
||||
let existing = this.store.peekRecord(normalized.data.type, normalized.data.id);
|
||||
|
||||
if (existing) {
|
||||
let attrs = normalized.data.attributes || {};
|
||||
|
||||
for (let key in attrs) {
|
||||
if (attrs[key] === null || attrs[key] === undefined || key === 'avatar_url') {
|
||||
delete attrs[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize serializer so that sensitive or server-managed
|
||||
* fields are never sent to the backend.
|
||||
*/
|
||||
serialize() {
|
||||
const json = super.serialize(...arguments);
|
||||
|
||||
// delete the password always
|
||||
// Never send password
|
||||
delete json.password;
|
||||
// delete verification attributes
|
||||
|
||||
// Verification flags
|
||||
delete json.email_verified_at;
|
||||
delete json.phone_verified_at;
|
||||
|
||||
// delete server managed dates
|
||||
// Server-managed timestamps
|
||||
delete json.deleted_at;
|
||||
delete json.created_at;
|
||||
delete json.updated_at;
|
||||
|
||||
@@ -1,39 +1,133 @@
|
||||
import Service, { inject as service } from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
const CONTEXT_PREFIX = 'onboarding:context:';
|
||||
const KEYS_INDEX = `${CONTEXT_PREFIX}__keys__`;
|
||||
|
||||
export default class OnboardingContextService extends Service {
|
||||
@service appCache;
|
||||
@tracked data = {};
|
||||
|
||||
/**
|
||||
* Get a value from in-memory state first, then fallback to cache
|
||||
*/
|
||||
get(key) {
|
||||
return this.data[key] ?? this.appCache.get(`onboarding:context:${key}`);
|
||||
return this.data[key] ?? this.appCache.get(`${CONTEXT_PREFIX}${key}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value directly from cache
|
||||
*/
|
||||
getFromCache(key) {
|
||||
return this.appCache.get(`onboarding:context:${key}`);
|
||||
return this.appCache.get(`${CONTEXT_PREFIX}${key}`);
|
||||
}
|
||||
|
||||
set(key, value, options = {}) {
|
||||
this.data = { ...this.data, [key]: value };
|
||||
if (options?.persist === true) {
|
||||
this.appCache.set(`onboarding:context:${key}`, value);
|
||||
/**
|
||||
* Restore all persisted onboarding context values from cache
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
restore() {
|
||||
const keys = this.appCache.get(KEYS_INDEX) ?? [];
|
||||
const persisted = {};
|
||||
|
||||
for (const key of keys) {
|
||||
const value = this.appCache.get(`${CONTEXT_PREFIX}${key}`);
|
||||
if (value !== undefined) {
|
||||
persisted[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return persisted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge data into the context
|
||||
* Optionally persist all merged values
|
||||
*/
|
||||
merge(data = {}, options = {}) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter out sensitive fields
|
||||
const sensitiveFields = ['password', 'password_confirmation'];
|
||||
const filteredData = {};
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
if (!sensitiveFields.includes(key)) {
|
||||
filteredData[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
this.data = { ...this.data, ...filteredData };
|
||||
|
||||
if (options.persist === true) {
|
||||
const keys = new Set(this.appCache.get(KEYS_INDEX) ?? []);
|
||||
|
||||
for (const key of Object.keys(filteredData)) {
|
||||
keys.add(key);
|
||||
this.appCache.set(`${CONTEXT_PREFIX}${key}`, this.data[key]);
|
||||
}
|
||||
|
||||
this.appCache.set(KEYS_INDEX, [...keys]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a single value
|
||||
* Optionally persist it
|
||||
*/
|
||||
set(key, value, options = {}) {
|
||||
// Don't store sensitive fields
|
||||
const sensitiveFields = ['password', 'password_confirmation'];
|
||||
if (sensitiveFields.includes(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.data = { ...this.data, [key]: value };
|
||||
|
||||
if (options.persist === true) {
|
||||
const keys = new Set(this.appCache.get(KEYS_INDEX) ?? []);
|
||||
keys.add(key);
|
||||
|
||||
this.appCache.set(`${CONTEXT_PREFIX}${key}`, value);
|
||||
this.appCache.set(KEYS_INDEX, [...keys]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience alias for persisted set
|
||||
*/
|
||||
persist(key, value) {
|
||||
this.set(key, value, { persist: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a key from memory and cache
|
||||
*/
|
||||
del(key) {
|
||||
const { [key]: _drop, ...rest } = this.data; // eslint-disable-line no-unused-vars
|
||||
const { [key]: _removed, ...rest } = this.data; // eslint-disable-line no-unused-vars
|
||||
this.data = rest;
|
||||
this.appCache.set(`onboarding:context:${key}`, undefined);
|
||||
|
||||
const keys = new Set(this.appCache.get(KEYS_INDEX) ?? []);
|
||||
keys.delete(key);
|
||||
|
||||
this.appCache.set(`${CONTEXT_PREFIX}${key}`, undefined);
|
||||
this.appCache.set(KEYS_INDEX, [...keys]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully reset onboarding context (memory + persistence)
|
||||
*/
|
||||
reset() {
|
||||
for (let key in this.data) {
|
||||
this.appCache.set(`onboarding:context:${key}`, undefined);
|
||||
const keys = this.appCache.get(KEYS_INDEX) ?? [];
|
||||
|
||||
for (const key of keys) {
|
||||
this.appCache.set(`${CONTEXT_PREFIX}${key}`, undefined);
|
||||
}
|
||||
|
||||
this.appCache.set(KEYS_INDEX, []);
|
||||
this.data = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,17 +7,43 @@ export default class OnboardingOrchestratorService extends Service {
|
||||
@service onboardingContext;
|
||||
|
||||
@tracked flow = null;
|
||||
@tracked wrapper = null;
|
||||
@tracked current = null;
|
||||
@tracked history = [];
|
||||
@tracked sessionId = null;
|
||||
|
||||
start(flowId = null, opts = {}) {
|
||||
/**
|
||||
* localStorage key for persisting navigation history
|
||||
*/
|
||||
get historyStorageKey() {
|
||||
return `onboarding:history:${this.flow?.id || 'default'}`;
|
||||
}
|
||||
|
||||
async start(flowId = null, opts = {}) {
|
||||
const flow = this.onboardingRegistry.getFlow(flowId ?? this.onboardingRegistry.defaultFlow);
|
||||
if (!flow) throw new Error(`Onboarding flow '${flowId}' not found`);
|
||||
|
||||
this.flow = flow;
|
||||
this.wrapper = flow.wrapper || null;
|
||||
this.sessionId = opts.sessionId || null;
|
||||
this.history = [];
|
||||
this.goto(flow.entry);
|
||||
|
||||
// Restore history if resuming from a previous session
|
||||
if (opts.resume) {
|
||||
this._restoreHistory();
|
||||
}
|
||||
|
||||
// Execute onFlowWillStart hook if defined
|
||||
if (typeof this.flow.onFlowWillStart === 'function') {
|
||||
await this.flow.onFlowWillStart(this.flow, this);
|
||||
}
|
||||
|
||||
await this.goto(flow.entry);
|
||||
|
||||
// Execute onFlowDidStart hook if defined
|
||||
if (typeof this.flow.onFlowDidStart === 'function') {
|
||||
await this.flow.onFlowDidStart(this.flow, this);
|
||||
}
|
||||
}
|
||||
|
||||
async goto(stepId) {
|
||||
@@ -25,27 +51,46 @@ export default class OnboardingOrchestratorService extends Service {
|
||||
const step = this.flow.steps.find((s) => s.id === stepId);
|
||||
if (!step) throw new Error(`Step '${stepId}' not found`);
|
||||
|
||||
// Execute onStepWillChange hook if defined
|
||||
const previousStep = this.current;
|
||||
if (typeof this.flow.onStepWillChange === 'function') {
|
||||
await this.flow.onStepWillChange(step, previousStep, this);
|
||||
}
|
||||
|
||||
// Guard function - skip step if guard returns false
|
||||
if (typeof step.guard === 'function' && !step.guard(this.onboardingContext)) {
|
||||
return this.next();
|
||||
}
|
||||
|
||||
// beforeEnter lifecycle hook
|
||||
if (typeof step.beforeEnter === 'function') {
|
||||
await step.beforeEnter(this.onboardingContext);
|
||||
}
|
||||
|
||||
this.current = step;
|
||||
|
||||
// Execute onStepDidChange hook if defined
|
||||
if (typeof this.flow.onStepDidChange === 'function') {
|
||||
await this.flow.onStepDidChange(this.current, previousStep, this);
|
||||
}
|
||||
}
|
||||
|
||||
async next() {
|
||||
if (!this.flow || !this.current) return;
|
||||
|
||||
const leaving = this.current;
|
||||
|
||||
// afterLeave lifecycle hook
|
||||
if (typeof leaving.afterLeave === 'function') {
|
||||
await leaving.afterLeave(this.onboardingContext);
|
||||
}
|
||||
|
||||
if (!this.history.includes(leaving)) this.history.push(leaving);
|
||||
if (!this.history.includes(leaving)) {
|
||||
this.history.push(leaving);
|
||||
this._persistHistory();
|
||||
}
|
||||
|
||||
// Support both string and function for next property
|
||||
let nextId;
|
||||
if (typeof leaving.next === 'function') {
|
||||
nextId = leaving.next(this.onboardingContext);
|
||||
@@ -53,8 +98,23 @@ export default class OnboardingOrchestratorService extends Service {
|
||||
nextId = leaving.next;
|
||||
}
|
||||
|
||||
// If no next step, flow is complete
|
||||
if (!nextId) {
|
||||
// Execute onFlowWillEnd hook if defined
|
||||
if (typeof this.flow.onFlowWillEnd === 'function') {
|
||||
await this.flow.onFlowWillEnd(leaving, this);
|
||||
}
|
||||
|
||||
this.current = null; // finished
|
||||
|
||||
// Execute onFlowDidEnd hook if defined
|
||||
if (typeof this.flow.onFlowDidEnd === 'function') {
|
||||
await this.flow.onFlowDidEnd(leaving, this);
|
||||
}
|
||||
|
||||
// Clear history from localStorage when flow completes
|
||||
this._clearHistory();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -66,6 +126,89 @@ export default class OnboardingOrchestratorService extends Service {
|
||||
const prev = this.history[this.history.length - 1];
|
||||
if (prev && prev.allowBack === false) return;
|
||||
this.history = this.history.slice(0, -1);
|
||||
this._persistHistory();
|
||||
await this.goto(prev.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current path (for flows with multiple paths)
|
||||
* This is a helper method that can be used by flows to determine the current path
|
||||
*/
|
||||
getCurrentPath() {
|
||||
if (!this.flow || !this.flow.paths) return null;
|
||||
|
||||
// Determine path based on context or current step
|
||||
for (const [pathId, pathDef] of Object.entries(this.flow.paths)) {
|
||||
if (pathDef.steps && pathDef.steps.some(s => s.id === this.current?.id)) {
|
||||
return pathDef;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a step is in the current path
|
||||
*/
|
||||
isStepInPath(stepId) {
|
||||
const currentPath = this.getCurrentPath();
|
||||
if (!currentPath) return true; // If no paths defined, all steps are valid
|
||||
|
||||
return currentPath.steps?.some(s => s.id === stepId) ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist navigation history to localStorage
|
||||
* Stores only step IDs to keep storage lightweight
|
||||
* @private
|
||||
*/
|
||||
_persistHistory() {
|
||||
if (!this.flow) return;
|
||||
|
||||
try {
|
||||
const historyIds = this.history.map(step => step.id);
|
||||
localStorage.setItem(this.historyStorageKey, JSON.stringify(historyIds));
|
||||
} catch (error) {
|
||||
console.warn('[OnboardingOrchestrator] Failed to persist history:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore navigation history from localStorage
|
||||
* Reconstructs step objects from stored IDs
|
||||
* @private
|
||||
*/
|
||||
_restoreHistory() {
|
||||
if (!this.flow) return;
|
||||
|
||||
try {
|
||||
const stored = localStorage.getItem(this.historyStorageKey);
|
||||
if (!stored) return;
|
||||
|
||||
const historyIds = JSON.parse(stored);
|
||||
this.history = historyIds
|
||||
.map(id => this.flow.steps.find(s => s.id === id))
|
||||
.filter(Boolean); // Remove any invalid steps
|
||||
|
||||
console.log('[OnboardingOrchestrator] Restored history:', this.history.map(s => s.id));
|
||||
} catch (error) {
|
||||
console.warn('[OnboardingOrchestrator] Failed to restore history:', error);
|
||||
this.history = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear navigation history from localStorage
|
||||
* Called when flow completes or is reset
|
||||
* @private
|
||||
*/
|
||||
_clearHistory() {
|
||||
if (!this.flow) return;
|
||||
|
||||
try {
|
||||
localStorage.removeItem(this.historyStorageKey);
|
||||
} catch (error) {
|
||||
console.warn('[OnboardingOrchestrator] Failed to clear history:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export default class OnboardingRegistryService extends Service {
|
||||
this.defaultFlow = flowId;
|
||||
}
|
||||
|
||||
registerFlow(flow) {
|
||||
registerFlow(flow, options = {}) {
|
||||
if (!flow || !flow.id || !flow.entry || !Array.isArray(flow.steps)) {
|
||||
throw new Error('Invalid FlowDef: id, entry, steps are required');
|
||||
}
|
||||
@@ -23,6 +23,11 @@ export default class OnboardingRegistryService extends Service {
|
||||
}
|
||||
}
|
||||
this.flows.set(flow.id, flow);
|
||||
|
||||
// If specified, set as default flow
|
||||
if (options.default) {
|
||||
this.defaultFlow = flow.id;
|
||||
}
|
||||
}
|
||||
|
||||
getFlow(id) {
|
||||
|
||||
@@ -4,21 +4,27 @@
|
||||
<div class="container mx-auto h-screen">
|
||||
<div class="max-w-3xl my-10 mx-auto">
|
||||
<ContentPanel @title={{t "common.your-profile"}} @open={{true}} @wrapperClass="bordered-classic">
|
||||
<form class="flex flex-col md:flex-row" {{on "submit" (perform this.saveProfile)}}>
|
||||
<form class="flex flex-col items-start md:flex-row" {{on "submit" (perform this.saveProfile)}}>
|
||||
<div class="w-32 flex flex-col justify-center mb-6 mr-6">
|
||||
<Image src={{this.user.avatar_url}} @fallbackSrc={{config "defaultValues.userImage"}} alt={{this.user.name}} class="w-32 h-32 rounded-md" />
|
||||
<FileUpload @name={{t "console.account.index.photos"}} @accept="image/*" @onFileAdded={{this.uploadNewPhoto}} @labelClass="flex flex-row items-center justify-center" as |queue|>
|
||||
<Image src={{this.user.avatar_url}} @fallbackSrc={{config "defaultValues.userImage"}} alt={{this.user.name}} class="w-32 h-32 rounded-md mt-1" />
|
||||
<FileUpload
|
||||
@name={{t "console.account.index.photos"}}
|
||||
@accept="image/*"
|
||||
@onFileAdded={{this.uploadNewPhoto}}
|
||||
@labelClass="flex flex-row items-center justify-center"
|
||||
as |queue|
|
||||
>
|
||||
<a tabindex={{0}} class="flex items-center px-0 mt-2 text-xs no-underline truncate btn btn-sm btn-default" disabled={{queue.files.length}}>
|
||||
{{#if queue.files.length}}
|
||||
<div class="mr-1.5">
|
||||
<Spinner />
|
||||
</div>
|
||||
<span>
|
||||
{{t "common.uploading"}}
|
||||
{{t "common.uploading"}}
|
||||
</span>
|
||||
{{else}}
|
||||
<FaIcon @icon="image" class="mr-1.5" />
|
||||
<span>
|
||||
<span>
|
||||
{{t "console.account.index.upload-new"}}
|
||||
</span>
|
||||
{{/if}}
|
||||
@@ -34,11 +40,31 @@
|
||||
</InputGroup>
|
||||
<InputGroup @name={{t "common.date-of-birth"}} @type="date" @value={{this.user.date_of_birth}} />
|
||||
<InputGroup @name={{t "common.timezone"}} @helpText={{t "console.account.index.timezone"}}>
|
||||
<Select @value={{this.user.timezone}} @options={{this.timezones}} @onSelect={{fn (mut this.user.timezone)}} @placeholder={{t "console.account.index.timezone"}} />
|
||||
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
||||
<PowerSelect
|
||||
@options={{this.timezones}}
|
||||
@selected={{this.user.timezone}}
|
||||
@onChange={{fn (mut this.user.timezone)}}
|
||||
@placeholder={{t "console.account.index.timezone"}}
|
||||
@triggerClass="form-select form-input"
|
||||
@searchEnabled={{true}}
|
||||
as |option|
|
||||
>
|
||||
<div>{{option}}</div>
|
||||
</PowerSelect>
|
||||
</div>
|
||||
</InputGroup>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center justify-end">
|
||||
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text={{t "common.save-changes"}} @onClick={{perform this.saveProfile}} @isLoading={{not this.saveProfile.isIdle}} />
|
||||
<Button
|
||||
@buttonType="submit"
|
||||
@type="primary"
|
||||
@size="lg"
|
||||
@icon="save"
|
||||
@text={{t "common.save-changes"}}
|
||||
@onClick={{perform this.saveProfile}}
|
||||
@isLoading={{not this.saveProfile.isIdle}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{{page-title @model.title}}
|
||||
<Layout::Section::Header @title={{@model.title}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen">
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-">
|
||||
<LazyEngineComponent @component={{@model.component}} @params={{@model.componentParams}} />
|
||||
<Layout::Section::Body class={{this.bodyClass}}>
|
||||
<div class={{this.containerClass}}>
|
||||
<div class={{this.wrapperClass}}>
|
||||
{{component (lazy-engine-component @model.component) params=@model.componentParams controller=this}}
|
||||
</div>
|
||||
</div>
|
||||
<Spacer @height="300px" />
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
<div class="flex items-center justify-center h-screen min-h-screen px-4 py-12 bg-gray-50 dark:bg-gray-900 sm:px-6 lg:px-8 overflow-y-scroll">
|
||||
<div class="w-full max-w-md h-screen flex items-center justify-center py-4">
|
||||
{{outlet}}
|
||||
</div>
|
||||
<Spacer @height="300px" />
|
||||
<div class="onboard-route-wrapper">
|
||||
{{outlet}}
|
||||
</div>
|
||||
@@ -23,7 +23,7 @@ module.exports = function (environment) {
|
||||
APP: {
|
||||
autoboot: true,
|
||||
extensions: asArray(getenv('EXTENSIONS')),
|
||||
disableRuntimeConfig: toBoolean(getenv('DISABLE_RUNTIME_CONFIG')),
|
||||
disableRuntimeConfig: toBoolean(getenv('DISABLE_RUNTIME_CONFIG', environment === 'production')),
|
||||
},
|
||||
|
||||
API: {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
API_HOST=
|
||||
API_HOST=https://api.fleetbase.io
|
||||
API_NAMESPACE=int/v1
|
||||
API_SECURE=true
|
||||
SOCKETCLUSTER_PATH=/socketcluster/
|
||||
SOCKETCLUSTER_HOST=
|
||||
SOCKETCLUSTER_HOST=socket.fleetbase.io
|
||||
SOCKETCLUSTER_SECURE=true
|
||||
SOCKETCLUSTER_PORT=38000
|
||||
OSRM_HOST=https://router.project-osrm.org
|
||||
SOCKETCLUSTER_PORT=8000
|
||||
OSRM_HOST=https://router.project-osrm.org
|
||||
DISABLE_RUNTIME_CONFIG=true
|
||||
9
console/environments/.env.qa
Normal file
9
console/environments/.env.qa
Normal file
@@ -0,0 +1,9 @@
|
||||
API_HOST=https://api.qa.fleetbase.io
|
||||
API_NAMESPACE=int/v1
|
||||
API_SECURE=true
|
||||
SOCKETCLUSTER_PATH=/socketcluster/
|
||||
SOCKETCLUSTER_HOST=socket.qa.fleetbase.io
|
||||
SOCKETCLUSTER_SECURE=true
|
||||
SOCKETCLUSTER_PORT=8000
|
||||
OSRM_HOST=https://router.project-osrm.org
|
||||
DISABLE_RUNTIME_CONFIG=true
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@fleetbase/console",
|
||||
"version": "0.7.22",
|
||||
"version": "0.7.27",
|
||||
"private": true,
|
||||
"description": "Modular logistics and supply chain operating system (LSOS)",
|
||||
"repository": "https://github.com/fleetbase/fleetbase",
|
||||
@@ -33,15 +33,24 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ember/legacy-built-in-components": "^0.4.2",
|
||||
"@fleetbase/aws-marketplace": "^0.0.8",
|
||||
"@fleetbase/billing-engine": "^0.1.20",
|
||||
"@fleetbase/customer-portal-engine": "^0.0.10",
|
||||
"@fleetbase/dev-engine": "^0.2.12",
|
||||
"@fleetbase/ember-core": "^0.3.8",
|
||||
"@fleetbase/ember-ui": "^0.3.14",
|
||||
"@fleetbase/fleetops-data": "^0.1.23",
|
||||
"@fleetbase/fleetops-engine": "^0.6.29",
|
||||
"@fleetbase/ember-core": "^0.3.10",
|
||||
"@fleetbase/ember-ui": "^0.3.18",
|
||||
"@fleetbase/fleetops-data": "^0.1.25",
|
||||
"@fleetbase/fleetops-engine": "^0.6.34",
|
||||
"@fleetbase/flespi-engine": "^0.1.16",
|
||||
"@fleetbase/iam-engine": "^0.1.6",
|
||||
"@fleetbase/internals-engine": "^0.0.27",
|
||||
"@fleetbase/leaflet-routing-machine": "^3.2.17",
|
||||
"@fleetbase/registry-bridge-engine": "^0.1.2",
|
||||
"@fleetbase/storefront-engine": "^0.4.9",
|
||||
"@fleetbase/registry-bridge-engine": "^0.1.5",
|
||||
"@fleetbase/samsara-engine": "^0.0.3",
|
||||
"@fleetbase/solid-engine": "^0.0.7",
|
||||
"@fleetbase/storefront-engine": "^0.4.13",
|
||||
"@fleetbase/valhalla-engine": "^0.0.3",
|
||||
"@fleetbase/vroom-engine": "^0.0.3",
|
||||
"@formatjs/intl-datetimeformat": "^6.18.2",
|
||||
"@formatjs/intl-numberformat": "^8.15.6",
|
||||
"@formatjs/intl-pluralrules": "^5.4.6",
|
||||
@@ -93,6 +102,7 @@
|
||||
"ember-cli-babel": "^8.2.0",
|
||||
"ember-cli-clean-css": "^3.0.0",
|
||||
"ember-cli-dependency-checker": "^3.3.2",
|
||||
"ember-cli-deprecation-workflow": "^4.0.0",
|
||||
"ember-cli-dotenv": "^3.1.0",
|
||||
"ember-cli-htmlbars": "^6.3.0",
|
||||
"ember-cli-inject-live-reload": "^2.1.0",
|
||||
@@ -149,9 +159,9 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@fleetbase/ember-core": "latest",
|
||||
"@fleetbase/ember-ui": "latest",
|
||||
"@fleetbase/fleetops-data": "latest"
|
||||
"@fleetbase/ember-core": "^0.3.10",
|
||||
"@fleetbase/ember-ui": "^0.3.18",
|
||||
"@fleetbase/fleetops-data": "^0.1.25"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
|
||||
3771
console/pnpm-lock.yaml
generated
3771
console/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -346,6 +346,7 @@ common:
|
||||
{resource} ({resourceName}) deleted.
|
||||
continue-without-saving: Continue Without Saving?
|
||||
continue-without-saving-prompt: You have unsaved changes to this {resource}. Continuing will discard them. Click Continue to proceed.
|
||||
changelog: Changelog
|
||||
|
||||
resource:
|
||||
alert: Alert
|
||||
|
||||
777
console/translations/uz-uz.yaml
Normal file
777
console/translations/uz-uz.yaml
Normal file
@@ -0,0 +1,777 @@
|
||||
app:
|
||||
name: Fleetbase
|
||||
|
||||
common:
|
||||
new: Yangi
|
||||
create: Yaratish
|
||||
add: Qo'shish
|
||||
edit: Tahrirlash
|
||||
update: Yangilash
|
||||
save: Saqlash
|
||||
save-changes: O'zgarishlarni saqlash
|
||||
delete: O'chirish
|
||||
delete-selected: Tanlanganlarni o'chirish
|
||||
delete-selected-count: "{count} ta tanlanganni o'chirish"
|
||||
your-profile: Sizning profilingiz
|
||||
date-of-birth: Tug'ilgan sana
|
||||
organization: Tashkilot
|
||||
two-factor: Ikki faktorli
|
||||
remove: O'chirish
|
||||
cancel: Bekor qilish
|
||||
confirm: Tasdiqlash
|
||||
close: Yopish
|
||||
open: Ochish
|
||||
view: Ko'rish
|
||||
preview: Ko'rib chiqish
|
||||
upload: Yuklash
|
||||
download: Yuklab olish
|
||||
import: Import qilish
|
||||
export: Eksport qilish
|
||||
print: Chop etish
|
||||
duplicate: Nusxalash
|
||||
copy: Nusxa olish
|
||||
paste: Joylashtirish
|
||||
share: Ulashish
|
||||
refresh: Yangilash
|
||||
reset: Qayta o'rnatish
|
||||
retry: Qayta urinish
|
||||
back: Orqaga
|
||||
next: Keyingisi
|
||||
previous: Oldingisi
|
||||
submit: Yuborish
|
||||
apply: Qo'llash
|
||||
continue: Davom etish
|
||||
proceed: Davom etish
|
||||
select: Tanlash
|
||||
deselect: Tanlovni bekor qilish
|
||||
search: Qidirish
|
||||
filter: Filtr
|
||||
sort: Saralash
|
||||
view-all: Hammasini ko'rish
|
||||
clear: Tozalash
|
||||
done: Bajarildi
|
||||
finish: Tugatish
|
||||
skip: O'tkazib yuborish
|
||||
method: Usul
|
||||
bulk-delete: Ommaviy o'chirish
|
||||
bulk-delete-resource: "{resource} ni ommaviy o'chirish"
|
||||
bulk-cancel: Ommaviy bekor qilish
|
||||
bulk-cancel-resource: "{resource} ni ommaviy bekor qilish"
|
||||
bulk-actions: Ommaviy amallar
|
||||
column: Ustun
|
||||
row: Qator
|
||||
table: Jadval
|
||||
list: Ro'yxat
|
||||
grid: Setka
|
||||
form: Shakl
|
||||
field: Maydon
|
||||
section: Bo'lim
|
||||
panel: Panel
|
||||
card: Karta
|
||||
tab: Varaqa
|
||||
modal: Modal
|
||||
dialog: Dialog
|
||||
menu: Menyu
|
||||
dropdown: Ochiladigan menyu
|
||||
tooltip: Maslahat
|
||||
sidebar: Yon panel
|
||||
toolbar: Asboblar paneli
|
||||
footer: Pastki qism
|
||||
header: Yuqori qism
|
||||
title: Sarlavha
|
||||
subtitle: Kichik sarlavha
|
||||
description: Tavsif
|
||||
placeholder: Joy egallovchi
|
||||
label: Yorliq
|
||||
button: Tugma
|
||||
icon: Belgi
|
||||
avatar: Avatar
|
||||
link: Havola
|
||||
badge: Nishon
|
||||
tag: Teg
|
||||
banner: Banner
|
||||
step: Qadam
|
||||
progress: Jarayon
|
||||
map: Xarita
|
||||
board: Doska
|
||||
loading: Yuklanmoqda
|
||||
loading-resource: "{resource} yuklanmoqda"
|
||||
saving: Saqlanmoqda
|
||||
processing: Ishlanmoqda
|
||||
fetching: Olinmoqda
|
||||
updating: Yangilanmoqda
|
||||
uploading: Yuklanmoqda
|
||||
completed: Bajarildi
|
||||
success: Muvaffaqiyatli
|
||||
failed: Muvaffaqiyatsiz
|
||||
error: Xato
|
||||
warning: Ogohlantirish
|
||||
info: Ma'lumot
|
||||
ready: Tayyor
|
||||
activity: Faoliyat
|
||||
active: Faol
|
||||
inactive: Nofaol
|
||||
enabled: Yoqilgan
|
||||
disabled: O'chirilgan
|
||||
online: Onlayn
|
||||
offline: Oflayn
|
||||
pending: Kutishda
|
||||
archived: Arxivlangan
|
||||
hidden: Yashirin
|
||||
visible: Ko'rinadigan
|
||||
empty: Bo'sh
|
||||
not-found: Topilmadi
|
||||
no-results: Natijalar yo'q
|
||||
try-again: Qayta urinib ko'ring
|
||||
are-you-sure: Ishonchingiz komilmi?
|
||||
changes-saved: O'zgarishlar muvaffaqiyatli saqlandi.
|
||||
saved-successfully: O'zgarishlar muvaffaqiyatli saqlandi.
|
||||
field-saved: >-
|
||||
{field} muvaffaqiyatli saqlandi.
|
||||
changes-discarded: O'zgarishlar bekor qilindi.
|
||||
delete-confirm: Haqiqatan ham bu elementni oʻchirib tashlamoqchimisiz?
|
||||
action-successful: Amal muvaffaqiyatli bajarildi.
|
||||
action-failed: Amal bajarilmadi. Iltimos, qaytadan urining.
|
||||
something-went-wrong: Nimadir noto'g'ri ketdi.
|
||||
please-wait: Iltimos kuting...
|
||||
sign-in: Kirish
|
||||
sign-out: Chiqish
|
||||
sign-up: Ro'yxatdan o'tish
|
||||
log-in: Kirish
|
||||
log-out: Chiqish
|
||||
register: Ro'yxatdan o'tish
|
||||
forgot-password: Parolni unutdingizmi
|
||||
reset-password: Parolni tiklash
|
||||
change-password: Parolni o'zgartirish
|
||||
password: Parol
|
||||
confirm-password: Parolni tasdiqlang
|
||||
email: Elektron pochta
|
||||
username: Foydalanuvchi nomi
|
||||
remember-me: Meni eslab qol
|
||||
welcome: Xush kelibsiz
|
||||
welcome-back: Xush kelibsiz
|
||||
profile: Profil
|
||||
account: Hisob
|
||||
settings: Sozlamalar
|
||||
preferences: Afzalliklar
|
||||
record: Yozuv
|
||||
records: Yozuvlar
|
||||
item: Element
|
||||
items: Elementlar
|
||||
entry: Yozuv
|
||||
entries: Yozuvlar
|
||||
id: ID
|
||||
name: Ism
|
||||
type: Tur
|
||||
category: Kategoriya
|
||||
overview: Umumiy koʻrinish
|
||||
value: Qiymat
|
||||
amount: Miqdor
|
||||
price: Narx
|
||||
quantity: Miqdori
|
||||
status: Holat
|
||||
date: Sana
|
||||
date-created: Yaratilgan sana
|
||||
date-updated: Yangilangan sana
|
||||
time: Vaqt
|
||||
created-at: Yaratilgan vaqti
|
||||
updated-at: Yangilangan vaqti
|
||||
expired-at: Yaroqlilik muddati
|
||||
last-seen-at: Oxirgi marta ko'rilgan
|
||||
last-modified: Oxirgi o'zgartirish
|
||||
last-modified-data: >-
|
||||
Oxirgi o'zgartirish: {date}
|
||||
actions: Amallar
|
||||
details: Tafsilotlar
|
||||
notes: Eslatmalar
|
||||
reference: Malumot
|
||||
filter-by: Filtr bo'yicha
|
||||
filter-by-field: "{field} bo'yicha filtr"
|
||||
sort-by: Saralash
|
||||
ascending: O'sish tartibida
|
||||
descending: Kamayish tartibida
|
||||
all: Hammasi
|
||||
none: Hech qanday
|
||||
select-all: Hammasini tanlash
|
||||
deselect-all: Barchasini bekor qilish
|
||||
show-more: Ko'proq ko'rsatish
|
||||
show-less: Kamroq ko'rsatish
|
||||
page: Sahifa
|
||||
of: ning
|
||||
total: Jami
|
||||
items-per-page: Har bir sahifadagi elementlar
|
||||
showing: Ko'rsatilmoqda
|
||||
to: gacha
|
||||
results: Natijalar
|
||||
load-more: Ko'proq yuklash
|
||||
no-more-results: Boshqa natijalar yo'q
|
||||
today: Bugun
|
||||
yesterday: Kecha
|
||||
tomorrow: Ertaga
|
||||
day: Kun
|
||||
week: Hafta
|
||||
month: Oy
|
||||
year: Yil
|
||||
date-range: Sana oralig'i
|
||||
start-date: Boshlanish sanasi
|
||||
end-date: Tugash sanasi
|
||||
time-zone: Vaqt mintaqasi
|
||||
system: Tizim
|
||||
dashboard: Boshqaruv paneli
|
||||
home: Bosh sahifa
|
||||
analytics: Analitika
|
||||
reports: Hisobotlar
|
||||
logs: Jurnallar
|
||||
help: Yordam
|
||||
support: Qo'llab-quvvatlash
|
||||
contact: Aloqa
|
||||
documentation: Hujjatlar
|
||||
language: Til
|
||||
timezone: Vaqt mintaqasi
|
||||
version: Versiya
|
||||
theme: Mavzu
|
||||
light-mode: Yorug'lik rejimi
|
||||
dark-mode: Qorong'u rejim
|
||||
update-available: Yangilanish mavjud
|
||||
install-update: Yangilanishni o'rnatish
|
||||
maintenance-mode: Texnik xizmat ko'rsatish rejimi
|
||||
notification: Bildirishnoma
|
||||
notifications: Bildirishnomalar
|
||||
mark-as-read: O'qilgan deb belgilash
|
||||
mark-all-as-read: Hammasini o'qilgan deb belgilash
|
||||
clear-notifications: Bildirishnomalarni tozalash
|
||||
company: Kompaniya
|
||||
companies: Kompaniyalar
|
||||
user: Foydalanuvchi
|
||||
users: Foydalanuvchilar
|
||||
role: Rol
|
||||
roles: Rollar
|
||||
permission: Ruxsat
|
||||
permissions: Ruxsatlar
|
||||
group: Guruh
|
||||
groups: Guruhlar
|
||||
unauthorized: Ruxsatsiz
|
||||
forbidden: Taqiqlandi
|
||||
resource-not-found: Resurs topilmadi
|
||||
server-error: Server xatosi
|
||||
validation-error: Tasdiqlash xatosi
|
||||
timeout-error: So'rov vaqti tugadi
|
||||
network-error: Tarmoq xatosi
|
||||
unknown-error: Noma'lum xato
|
||||
file: Fayl
|
||||
files: Fayllar
|
||||
folder: Papka
|
||||
folders: Papkalar
|
||||
upload-file: Fayl yuklash
|
||||
upload-files: Fayllarni yuklash
|
||||
upload-image: Rasm yuklash
|
||||
upload-image-supported: PNG, JPEG va GIF-larni qo'llab-quvvatlaydi
|
||||
choose-file: Faylni tanlang
|
||||
choose-files: Fayllarni tanlang
|
||||
drag-and-drop: Tortib tashlang
|
||||
download-file: Faylni yuklab olish
|
||||
file-size: Fayl hajmi
|
||||
file-type: Fayl turi
|
||||
confirm-delete: O'chirishni tasdiqlang
|
||||
confirm-action: Amalni tasdiqlang
|
||||
confirm-exit: Chiqishni tasdiqlang
|
||||
confirm-and-save-changes: Tasdiqlang va o'zgarishlarni saqlang
|
||||
are-you-sure-exit: Haqiqatan ham chiqmoqchimisiz?
|
||||
unsaved-changes-warning: Sizda saqlanmagan o'zgarishlar mavjud.
|
||||
connected: Ulangan
|
||||
disconnected: Ulanmagan
|
||||
reconnecting: Qayta ulanmoqda
|
||||
connection-lost: Ulanish uzildi
|
||||
connection-restored: Ulanish tiklandi
|
||||
show: Ko'rsatish
|
||||
hide: Yashirish
|
||||
expand: Kengaytirish
|
||||
collapse: Yig'ish
|
||||
enable: Yoqish
|
||||
disable: O'chirish
|
||||
minimize: Kichraytirish
|
||||
maximize: Kattalashtirish
|
||||
restore: Qayta tiklash
|
||||
zoom-in: Kattalashtirish
|
||||
zoom-out: Kichraytirish
|
||||
fullscreen: To'liq ekran
|
||||
exit-fullscreen: To'liq ekrandan chiqish
|
||||
yes: Ha
|
||||
no: Yo'q
|
||||
ok: OK
|
||||
none-available: Mavjud emas
|
||||
default: Standart
|
||||
custom: Maxsus
|
||||
general: Umumiy
|
||||
advanced: Kengaytirilgan
|
||||
placeholder-text: Bu yerga matn kiriting...
|
||||
learn-more: Ko'proq bilish
|
||||
view-resource: "{resource} ni ko'rish"
|
||||
view-resource-details: "{resource} tafsilotlarini ko'rish"
|
||||
create-a-new-resource: "Yangi {resource} yaratish"
|
||||
create-new-resource: "Yangi {resource} yaratish"
|
||||
search-resource: "{resource} ni qidirish"
|
||||
new-resource: "Yangi {resource}"
|
||||
update-resource: "{resource} ni yangilash"
|
||||
save-resource-changes: "{resource} o'zgarishlarini saqlash"
|
||||
creating-resource: "{resource} yaratilmoqda"
|
||||
cancel-resource: "{resource} ni bekor qilish"
|
||||
delete-resource: "{resource} ni o'chirish"
|
||||
delete-resource-name: >-
|
||||
O'chirish: {resourceName}
|
||||
delete-resource-named: "{resource} ({resourceName}) ni o'chirish"
|
||||
delete-resource-prompt: Bu amalni bekor qilib bo'lmaydi. O'chirilgandan so'ng, yozuv butunlay o'chiriladi.
|
||||
delete-cannot-be-undone: Bu amalni bekor qilib bo'lmaydi. O'chirilgandan so'ng, yozuv butunlay o'chiriladi.
|
||||
create-resource: "{resource} yaratish"
|
||||
edit-resource: "{resource} ni tahrirlash"
|
||||
edit-resource-details: "{resource} tafsilotlarini tahrirlash"
|
||||
edit-resource-type-name: >-
|
||||
Tahrirlash {resource}: {resourceName}
|
||||
edit-resource-name: >-
|
||||
Tahrirlash: {resourceName}
|
||||
config: Konfiguratsiya
|
||||
select-field: "{field} ni tanlang"
|
||||
columns: Ustunlar
|
||||
metadata: Metama'lumotlar
|
||||
meta: Meta
|
||||
resource-created-success: "Yangi {resource} muvaffaqiyatli yaratildi."
|
||||
resource-created-success-name: "Yangi {resource} ({resourceName}) muvaffaqiyatli yaratildi."
|
||||
resource-updated-success: >-
|
||||
{resource} ({resourceName}) muvaffaqiyatli yangilandi.
|
||||
resource-action-success: >-
|
||||
{resource} ({resourceName}) {action} muvaffaqiyatli bajarildi.
|
||||
resource-deleted-success: >-
|
||||
{resource} ({resourceName}) muvaffaqiyatli o'chirildi.
|
||||
resource-deleted: >-
|
||||
{resource} ({resourceName}) o'chirildi.
|
||||
continue-without-saving: Saqlamasdan davom etish kerakmi?
|
||||
continue-without-saving-prompt: Sizda bu {resource} ga saqlanmagan o'zgarishlar mavjud. Davom etish ularni bekor qiladi. Davom etish uchun Davom etish tugmasini bosing.
|
||||
changelog: O'zgarishlar jurnali
|
||||
|
||||
resource:
|
||||
alert: Ogohlantirish
|
||||
alerts: Ogohlantirishlar
|
||||
brand: Brend
|
||||
brands: Brendlar
|
||||
category: Kategoriya
|
||||
categories: Kategoriyalar
|
||||
chat-attachment: Chat ilovasi
|
||||
chat-attachments: Chat ilovalari
|
||||
chat-channel: Chat kanali
|
||||
chat-channels: Chat kanallari
|
||||
chat-log: Chat jurnali
|
||||
chat-logs: Chat jurnallari
|
||||
chat-message: Chat xabari
|
||||
chat-messages: Chat xabarlari
|
||||
chat-participant: Chat ishtirokchisi
|
||||
chat-participants: Chat ishtirokchilari
|
||||
chat-receipt: Chat kvitansiyasi
|
||||
chat-receipts: Chat kvitansiyalari
|
||||
comment: Izoh
|
||||
comments: Izohlar
|
||||
company: Kompaniya
|
||||
companies: Kompaniyalar
|
||||
custom-field-value: Maxsus maydon qiymati
|
||||
custom-field-values: Maxsus maydon qiymatlari
|
||||
custom-field: Maxsus maydon
|
||||
custom-fields: Maxsus maydonlar
|
||||
dashboard-widget: Boshqaruv paneli vidjeti
|
||||
dashboard-widgets: Boshqaruv paneli vidjetlari
|
||||
dashboard: Boshqaruv paneli
|
||||
dashboards: Boshqaruv panellari
|
||||
extension: Kengaytma
|
||||
extensions: Kengaytmalar
|
||||
file: Fayl
|
||||
files: Fayllar
|
||||
group: Guruh
|
||||
groups: Guruhlar
|
||||
notification: Bildirishnoma
|
||||
notifications: Bildirishnomalar
|
||||
permission: Ruxsat
|
||||
permissions: Ruxsatlar
|
||||
policy: Siyosat
|
||||
policies: Siyosatlar
|
||||
report: Hisobot
|
||||
reports: Hisobotlar
|
||||
role: Rol
|
||||
roles: Rollar
|
||||
setting: Sozlama
|
||||
settings: Sozlamalar
|
||||
transaction: Tranzaksiya
|
||||
transactions: Tranzaksiyalar
|
||||
user-device: Foydalanuvchi qurilmasi
|
||||
user-devices: Foydalanuvchi qurilmalari
|
||||
user: Foydalanuvchi
|
||||
users: Foydalanuvchilar
|
||||
|
||||
dropzone:
|
||||
file: fayl
|
||||
drop-to-upload: Yuklash uchun tashlang
|
||||
invalid: Noto'g'ri
|
||||
files-ready-for-upload: >-
|
||||
{numOfFiles} ta fayl yuklashga tayyor.
|
||||
upload-images-videos: Rasmlar va videolarni yuklash
|
||||
upload-documents: Hujjatlarni yuklash
|
||||
upload-documents-files: Hujjatlar va fayllarni yuklash
|
||||
upload-avatar-files: Maxsus avatarlarni yuklash
|
||||
dropzone-supported-images-videos: Rasm va video fayllarni bu yerga tortib tashlang
|
||||
dropzone-supported-avatars: SVG yoki PNG fayllarini tortib tashlang
|
||||
dropzone-supported-files: Fayllarni bu yerga tortib tashlang
|
||||
or-select-button-text: yoki yuklash uchun fayllarni tanlang.
|
||||
upload-queue: Yuklash navbati
|
||||
uploading: Yuklanmoqda...
|
||||
|
||||
two-fa-enforcement-alert:
|
||||
message: Hisobingiz xavfsizligini oshirish uchun tashkilotingiz Ikki Faktorli Autentifikatsiyani (2FA) talab qiladi. Qo'shimcha himoya qatlami uchun hisob sozlamalaringizda 2FA-ni yoqing.
|
||||
button-text: 2FA-ni sozlash
|
||||
|
||||
comment-thread:
|
||||
publish-comment-button-text: Izohni chop etish
|
||||
publish-reply-button-text: Javobni chop etish
|
||||
reply-comment-button-text: Javob berish
|
||||
edit-comment-button-text: Tahrirlash
|
||||
delete-comment-button-text: O'chirish
|
||||
comment-published-ago: >-
|
||||
{createdAgo} oldin
|
||||
comment-input-placeholder: Yangi izoh kiriting...
|
||||
comment-reply-placeholder: Javobingizni kiriting...
|
||||
comment-input-empty-notification: Bo'sh izohlarni chop eta olmaysiz...
|
||||
comment-min-length-notification: Izoh kamida 2 ta belgidan iborat bo'lishi kerak
|
||||
|
||||
dashboard:
|
||||
select-dashboard: Boshqaruv panelini tanlang
|
||||
create-new-dashboard: Yangi boshqaruv paneli yaratish
|
||||
create-a-new-dashboard: Yangi boshqaruv paneli yaratish
|
||||
confirm-create-dashboard: Boshqaruv panelini yaratish!
|
||||
edit-layout: Tartibni tahrirlash
|
||||
add-widgets: Vidjetlarni qo'shish
|
||||
delete-dashboard: Boshqaruv panelini o'chirish
|
||||
save-dashboard: Boshqaruv panelini saqlash
|
||||
you-cannot-delete-this-dashboard: Siz bu boshqaruv panelini o'chira olmaysiz.
|
||||
are-you-sure-you-want-delete-dashboard: Haqiqatan ham bu {dashboardName} ni o'chirmoqchimisiz?
|
||||
dashboard-widget-panel:
|
||||
widget-name: >-
|
||||
{widgetName} vidjeti
|
||||
select-widgets: Vidjetlarni tanlang
|
||||
close-and-save: Yopish va saqlash
|
||||
filter-widgets: Vidjetlarni kalit so'z bo'yicha filtrlash
|
||||
|
||||
filters-picker:
|
||||
filters: Filtrlar
|
||||
filter-data: Ma'lumotlarni filtrlash
|
||||
|
||||
visible-column-picker:
|
||||
select-viewable-columns: Ko'rinadigan ustunlarni tanlang
|
||||
customize-columns: Ustunlarni moslashtirish
|
||||
|
||||
component:
|
||||
file:
|
||||
dropdown-label: Fayl amallari
|
||||
import-modal:
|
||||
loading-message: Import qayta ishlanmoqda...
|
||||
drop-upload: Yuklash uchun tashlang
|
||||
invalid: Noto'g'ri
|
||||
ready-upload: yuklashga tayyor.
|
||||
upload-spreadsheets: Jadvallarni yuklash
|
||||
drag-drop: Jadval fayllarini bu yerga tortib tashlang
|
||||
button-text: yoki yuklash uchun jadvallarni tanlang
|
||||
spreadsheets: jadvallar
|
||||
upload-queue: Yuklash navbati
|
||||
dropzone:
|
||||
file: fayl
|
||||
drop-to-upload: Yuklash uchun tashlang
|
||||
invalid: Noto'g'ri
|
||||
files-ready-for-upload: >-
|
||||
{numOfFiles} ta fayl yuklashga tayyor.
|
||||
upload-images-videos: Rasmlar va videolarni yuklash
|
||||
upload-documents: Hujjatlarni yuklash
|
||||
upload-documents-files: Hujjatlar va fayllarni yuklash
|
||||
upload-avatar-files: Maxsus avatarlarni yuklash
|
||||
dropzone-supported-images-videos: Rasm va video fayllarni bu yerga tortib tashlang
|
||||
dropzone-supported-avatars: SVG yoki PNG fayllarini tortib tashlang
|
||||
dropzone-supported-files: Fayllarni bu yerga tortib tashlang
|
||||
or-select-button-text: yoki yuklash uchun fayllarni tanlang.
|
||||
upload-queue: Yuklash navbati
|
||||
uploading: Yuklanmoqda...
|
||||
two-fa-enforcement-alert:
|
||||
message: Hisobingiz xavfsizligini oshirish uchun tashkilotingiz Ikki Faktorli Autentifikatsiyani (2FA) talab qiladi. Qo'shimcha himoya qatlami uchun hisob sozlamalaringizda 2FA-ni yoqing.
|
||||
button-text: 2FA-ni sozlash
|
||||
comment-thread:
|
||||
publish-comment-button-text: Izohni chop etish
|
||||
publish-reply-button-text: Javobni chop etish
|
||||
reply-comment-button-text: Javob berish
|
||||
edit-comment-button-text: Tahrirlash
|
||||
delete-comment-button-text: O'chirish
|
||||
comment-published-ago: >-
|
||||
{createdAgo} oldin
|
||||
comment-input-placeholder: Yangi izoh kiriting...
|
||||
comment-reply-placeholder: Javobingizni kiriting...
|
||||
comment-input-empty-notification: Bo'sh izohlarni chop eta olmaysiz...
|
||||
comment-min-length-notification: Izoh kamida 2 ta belgidan iborat bo'lishi kerak
|
||||
dashboard:
|
||||
select-dashboard: Boshqaruv panelini tanlang
|
||||
create-new-dashboard: Yangi boshqaruv paneli yaratish
|
||||
create-a-new-dashboard: Yangi boshqaruv paneli yaratish
|
||||
confirm-create-dashboard: Boshqaruv panelini yaratish!
|
||||
edit-layout: Tartibni tahrirlash
|
||||
add-widgets: Vidjetlarni qo'shish
|
||||
delete-dashboard: Boshqaruv panelini o'chirish
|
||||
save-dashboard: Boshqaruv panelini saqlash
|
||||
you-cannot-delete-this-dashboard: Siz bu boshqaruv panelini o'chira olmaysiz.
|
||||
are-you-sure-you-want-delete-dashboard: Haqiqatan ham bu {dashboardName} ni o'chirmoqchimisiz?
|
||||
dashboard-widget-panel:
|
||||
widget-name: >-
|
||||
{widgetName} vidjeti
|
||||
select-widgets: Vidjetlarni tanlang
|
||||
close-and-save: Yopish va saqlash
|
||||
|
||||
services:
|
||||
dashboard-service:
|
||||
create-dashboard-success-notification: Yangi `{dashboardName}` boshqaruv paneli muvaffaqiyatli yaratildi.
|
||||
delete-dashboard-success-notification: >-
|
||||
`{dashboardName}` boshqaruv paneli o'chirildi.
|
||||
|
||||
auth:
|
||||
verification:
|
||||
header-title: Hisobni tekshirish
|
||||
title: Elektron pochta manzilingizni tasdiqlang
|
||||
message-text: <strong>Deyarli tayyor!</strong><br> Tasdiqlash kodi uchun elektron pochtangizni tekshiring.
|
||||
verification-code-text: Elektron pochta orqali olgan tasdiqlash kodini kiriting.
|
||||
verification-input-label: Tasdiqlash kodi
|
||||
verify-button-text: Tasdiqlash va davom etish
|
||||
didnt-receive-a-code: Hali kod olmadizmi?
|
||||
not-sent:
|
||||
message: Hali kod olmadizmi?
|
||||
alternative-choice: Hisobingizni tasdiqlash uchun quyidagi muqobil variantlardan foydalaning.
|
||||
resend-email: E-pochtani qayta yuborish
|
||||
send-by-sms: SMS orqali yuborish
|
||||
two-fa:
|
||||
verify-code:
|
||||
verification-code: Tasdiqlash kodi
|
||||
check-title: E-pochtangizni yoki telefoningizni tekshiring
|
||||
check-subtitle: Biz sizga tasdiqlash kodini yubordik. Kirish jarayonini yakunlash uchun quyidagi kodni kiriting.
|
||||
expired-help-text: Sizning 2FA autentifikatsiya kodingiz muddati tugagan. Agar ko'proq vaqt kerak bo'lsa, boshqa kod so'rashingiz mumkin.
|
||||
resend-code: Kodni qayta yuborish
|
||||
verify-code: Kodni tasdiqlash
|
||||
cancel-two-factor: Ikki faktorli autentifikatsiyani bekor qilish
|
||||
invalid-session-error-notification: Noto'g'ri sessiya. Iltimos, qaytadan urining.
|
||||
verification-successful-notification: Tasdiqlash muvaffaqiyatli!
|
||||
verification-code-expired-notification: Tasdiqlash kodi muddati tugagan. Iltimos, yangisini so'rang.
|
||||
verification-code-failed-notification: Tasdiqlash muvaffaqiyatsiz tugadi. Iltimos, qaytadan urining.
|
||||
resend-code:
|
||||
verification-code-resent-notification: Yangi tasdiqlash kodi yuborildi.
|
||||
verification-code-resent-error-notification: Tasdiqlash kodini qayta yuborishda xato. Iltimos, qaytadan urining.
|
||||
forgot-password:
|
||||
success-message: Davom etish uchun elektron pochtangizni tekshiring!
|
||||
is-sent:
|
||||
title: Deyarli tayyor!
|
||||
message: <strong>E-pochtangizni tekshiring!</strong><br> Biz sizga parolingizni tiklash imkonini beruvchi sehrli havolani elektron pochtangizga yubordik. Havola 15 daqiqadan so'ng tugaydi.
|
||||
not-sent:
|
||||
title: Parolingizni unutdingizmi?
|
||||
message: <strong>Xavotir olmang, biz sizga yordam beramiz.</strong><br> {appName} ga kirish uchun foydalanadigan elektron pochta manzilingizni kiriting va biz sizga parolingizni tiklash uchun xavfsiz havola yuboramiz.
|
||||
form:
|
||||
email-label: Sizning elektron pochta manzilingiz
|
||||
submit-button: OK, menga sehrli havola yuboring!
|
||||
nevermind-button: Hechqisi yo'q
|
||||
login:
|
||||
title: Hisobingizga kiring
|
||||
no-identity-notification: E-pochtangizni kiritishni unutdingizmi?
|
||||
no-password-notification: Parolingizni kiritishni unutdingizmi?
|
||||
unverified-notification: Davom etish uchun hisobingiz tasdiqlanishi kerak.
|
||||
password-reset-required: Davom etish uchun parolni tiklash talab qilinadi.
|
||||
failed-attempt:
|
||||
message: <strong>Parolingizni unutdingizmi?</strong><br> Parolingizni tiklash uchun quyidagi tugmani bosing.
|
||||
button-text: Ok, tiklashga yordam bering!
|
||||
form:
|
||||
email-label: Elektron pochta manzili
|
||||
password-label: Parol
|
||||
remember-me-label: Meni eslab qol
|
||||
forgot-password-label: Parolingizni unutdingizmi?
|
||||
sign-in-button: Kirish
|
||||
create-account-button: Yangi hisob yaratish
|
||||
slow-connection-message: Ulanish muammolari yuzaga kelmoqda.
|
||||
reset-password:
|
||||
success-message: Parolingiz tiklandi! Davom etish uchun tizimga kiring.
|
||||
invalid-verification-code: Bu parolni tiklash havolasi yaroqsiz yoki muddati o'tgan.
|
||||
title: Parolingizni tiklang
|
||||
form:
|
||||
code:
|
||||
label: Sizning tiklash kodingiz
|
||||
help-text: E-pochtangizda olgan tasdiqlash kodi.
|
||||
password:
|
||||
label: Yangi parol
|
||||
help-text: Davom etish uchun kamida 6 ta belgidan iborat parol kiriting.
|
||||
confirm-password:
|
||||
label: Yangi parolni tasdiqlang
|
||||
help-text: Davom etish uchun kamida 6 ta belgidan iborat parol kiriting.
|
||||
submit-button: Parolni tiklash
|
||||
back-button: Orqaga
|
||||
|
||||
console:
|
||||
create-or-join-organization:
|
||||
modal-title: Tashkilot yaratish yoki unga qo'shilish
|
||||
join-success-notification: Siz yangi tashkilotga qo'shildingiz!
|
||||
create-success-notification: Siz yangi tashkilot yaratdingiz!
|
||||
switch-organization:
|
||||
modal-title: Haqiqatan ham tashkilotni {organizationName} ga o'zgartirmoqchimisiz?
|
||||
modal-body: Tasdiqlaganingizdan so'ng hisobingiz tizimda qoladi, lekin asosiy tashkilotingiz o'zgaradi.
|
||||
modal-accept-button-text: Ha, men tashkilotni o'zgartirmoqchiman
|
||||
success-notification: Siz tashkilotlarni o'zgartirdingiz
|
||||
account:
|
||||
index:
|
||||
upload-new: Yangisini yuklash
|
||||
phone: Sizning telefon raqamingiz.
|
||||
photos: rasmlar
|
||||
timezone: Vaqt mintaqangizni tanlang.
|
||||
admin:
|
||||
menu:
|
||||
overview: Umumiy ko'rinish
|
||||
organizations: Tashkilotlar
|
||||
branding: Brending
|
||||
"2fa-config": 2FA konfiguratsiyasi
|
||||
schedule-monitor: Jadval monitori
|
||||
services: Xizmatlar
|
||||
mail: Pochta
|
||||
filesystem: Fayl tizimi
|
||||
queue: Navbat
|
||||
socket: Soket
|
||||
push-notifications: Push-bildirishnomalar
|
||||
schedule-monitor:
|
||||
schedule-monitor: Jadval monitori
|
||||
task-logs-for: >-
|
||||
Vazifalar jurnali uchun:
|
||||
showing-last-count: Oxirgi {count} ta jurnal ko'rsatilmoqda
|
||||
name: Nomi
|
||||
type: Turi
|
||||
timezone: Vaqt mintaqasi
|
||||
last-started: Oxirgi boshlangan
|
||||
last-finished: Oxirgi tugallangan
|
||||
last-failure: Oxirgi muvaffaqiyatsizlik
|
||||
date: Sana
|
||||
memory: Xotira
|
||||
runtime: Ish vaqti
|
||||
output: Chiqish
|
||||
no-output: Chiqish yo'q
|
||||
config:
|
||||
database:
|
||||
title: Ma'lumotlar bazasi konfiguratsiyasi
|
||||
filesystem:
|
||||
title: Fayl tizimi konfiguratsiyasi
|
||||
mail:
|
||||
title: Pochta konfiguratsiyasi
|
||||
notification-channels:
|
||||
title: Push-bildirishnomalar konfiguratsiyasi
|
||||
queue:
|
||||
title: Navbat konfiguratsiyasi
|
||||
services:
|
||||
title: Xizmatlar konfiguratsiyasi
|
||||
socket:
|
||||
title: Soket konfiguratsiyasi
|
||||
branding:
|
||||
title: Brending
|
||||
icon-text: Belgi
|
||||
upload-new: Yangisini yuklash
|
||||
reset-default: Standart holatga qaytarish
|
||||
logo-text: Logotip
|
||||
theme: Standart mavzu
|
||||
index:
|
||||
total-users: Jami foydalanuvchilar
|
||||
total-organizations: Jami tashkilotlar
|
||||
total-transactions: Jami tranzaksiyalar
|
||||
notifications:
|
||||
title: Bildirishnomalar
|
||||
notification-settings: Bildirishnomalar sozlamalari
|
||||
organizations:
|
||||
index:
|
||||
title: Tashkilotlar
|
||||
owner-name-column: Egasi
|
||||
owner-phone-column: Egasining telefoni
|
||||
owner-email-column: Egasining telefoni
|
||||
users-count-column: Foydalanuvchilar
|
||||
phone-column: Telefon
|
||||
email-column: Elektron pochta
|
||||
users:
|
||||
title: Foydalanuvchilar
|
||||
settings:
|
||||
index:
|
||||
title: Tashkilot sozlamalari
|
||||
organization-name: Tashkilot nomi
|
||||
organization-description: Tashkilot tavsifi
|
||||
organization-phone: Tashkilot telefon raqami
|
||||
organization-currency: Tashkilot valyutasi
|
||||
organization-id: Tashkilot IDsi
|
||||
organization-branding: Tashkilot brendingi
|
||||
logo: Logotip
|
||||
logo-help-text: Tashkilotingiz uchun logotip.
|
||||
upload-new-logo: Yangi logotip yuklash
|
||||
backdrop: Orqa fon
|
||||
backdrop-help-text: Tashkilotingiz uchun ixtiyoriy banner yoki fon rasmi.
|
||||
upload-new-backdrop: Yangi orqa fon yuklash
|
||||
organization-timezone: Tashkilotingiz uchun standart vaqt mintaqasini tanlang.
|
||||
select-timezone: Vaqt mintaqasini tanlang.
|
||||
extensions:
|
||||
title: Kengaytmalar tez kunda!
|
||||
message: Kengaytmalar ombori va bozorini ishga tushirishga tayyorgarlik ko'rayotganimiz sababli, kelgusi versiyalarda qayta tekshiring.
|
||||
notifications:
|
||||
select-all: Hammasini tanlash
|
||||
mark-as-read: O'qilgan deb belgilash
|
||||
received: >-
|
||||
Qabul qilindi:
|
||||
message: Ko'rsatish uchun bildirishnomalar yo'q.
|
||||
|
||||
invite:
|
||||
for-users:
|
||||
invitation-message: Sizni {companyName} ga qo'shilishga taklif qilishdi
|
||||
invitation-sent-message: Sizni {appName} dagi {companyName} tashkilotiga qo'shilishga taklif qilishdi. Ushbu taklifni qabul qilish uchun elektron pochta orqali olgan taklifnomangiz kodini kiriting va davom etish tugmasini bosing.
|
||||
invitation-code-sent-text: Sizning taklifnoma kodingiz
|
||||
accept-invitation-text: Taklifni qabul qilish
|
||||
|
||||
onboard:
|
||||
index:
|
||||
title: Hisobingizni yarating
|
||||
welcome-title: <strong>{companyName} ga xush kelibsiz!</strong><br />
|
||||
welcome-text: Boshlash uchun quyida talab qilinadigan ma'lumotlarni to'ldiring.
|
||||
full-name: To'liq ism
|
||||
full-name-help-text: Sizning to'liq ismingiz
|
||||
your-email: Elektron pochta manzili
|
||||
your-email-help-text: Sizning elektron pochta manzilingiz
|
||||
phone: Telefon raqami
|
||||
phone-help-text: Sizning telefon raqamingiz
|
||||
organization-name: Tashkilot nomi
|
||||
organization-help-text: Sizning tashkilotingiz nomi, barcha xizmatlaringiz va resurslaringiz ushbu tashkilot ostida boshqariladi, keyinchalik xohlagancha yoki kerak bo'lganda ko'plab tashkilotlar yaratishingiz mumkin.
|
||||
password: Parol kiriting
|
||||
password-help-text: Sizning parolingiz, yaxshi ekanligiga ishonch hosil qiling.
|
||||
confirm-password: Parolingizni tasdiqlang
|
||||
confirm-password-help-text: Yuqorida kiritgan parolingizni tasdiqlash uchun.
|
||||
continue-button-text: Davom etish
|
||||
verify-email:
|
||||
header-title: Hisobni tekshirish
|
||||
title: Elektron pochta manzilingizni tasdiqlang
|
||||
message-text: <strong>Deyarli tayyor!</strong><br> Tasdiqlash kodi uchun elektron pochtangizni tekshiring.
|
||||
verification-code-text: Elektron pochta orqali olgan tasdiqlash kodini kiriting.
|
||||
verification-input-label: Tasdiqlash kodi
|
||||
verify-button-text: Tasdiqlash va davom etish
|
||||
didnt-receive-a-code: Hali kod olmadizmi?
|
||||
not-sent:
|
||||
message: Hali kod olmadizmi?
|
||||
alternative-choice: Hisobingizni tasdiqlash uchun quyidagi muqobil variantlardan foydalaning.
|
||||
resend-email: E-pochtani qayta yuborish
|
||||
send-by-sms: SMS orqali yuborish
|
||||
|
||||
install:
|
||||
installer-header: O'rnatuvchi
|
||||
failed-message-sent: O'rnatish muvaffaqiyatsiz tugadi! O'rnatishni qayta urinish uchun quyidagi tugmani bosing.
|
||||
retry-install: Qayta o'rnatish
|
||||
start-install: O'rnatishni boshlash
|
||||
|
||||
layout:
|
||||
header:
|
||||
menus:
|
||||
organization:
|
||||
settings: Tashkilot sozlamalari
|
||||
create-or-join: Tashkilot yaratish yoki unga qo'shilish
|
||||
explore-extensions: Kengaytmalarni o'rganish
|
||||
user:
|
||||
view-profile: Profilni ko'rish
|
||||
keyboard-shortcuts: Klaviatura yorliqlarini ko'rsatish
|
||||
changelog: O'zgarishlar jurnali
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.2
|
||||
# Base stage
|
||||
FROM dunglas/frankenphp:1.5.0-php8.2-bookworm AS base
|
||||
FROM dunglas/frankenphp:1.11-php8.2-bookworm AS base
|
||||
|
||||
# Install packages
|
||||
RUN apt-get update && apt-get install -y git bind9-utils mycli nodejs npm nano uuid-runtime \
|
||||
@@ -75,7 +75,7 @@ ENV QUEUE_CONNECTION=redis
|
||||
ENV CADDYFILE_PATH=/fleetbase/Caddyfile
|
||||
ENV CONSOLE_PATH=/fleetbase/console
|
||||
ENV OCTANE_SERVER=frankenphp
|
||||
ENV FLEETBASE_VERSION=0.7.22
|
||||
ENV FLEETBASE_VERSION=0.7.27
|
||||
|
||||
# Set environment
|
||||
ARG ENVIRONMENT=production
|
||||
@@ -158,14 +158,14 @@ CMD ["php", "artisan", "queue:work"]
|
||||
# Application dev stage
|
||||
FROM base AS app-dev
|
||||
ENTRYPOINT ["docker-php-entrypoint"]
|
||||
CMD ["sh", "-c", "php artisan octane:frankenphp --max-requests=250 --port=8000 --host=0.0.0.0 --watch"]
|
||||
CMD ["sh", "-c", "php artisan octane:frankenphp --max-requests=1000 --port=8000 --host=0.0.0.0 --watch"]
|
||||
|
||||
# Application release stage
|
||||
FROM base AS app-release
|
||||
ENTRYPOINT ["docker-php-entrypoint"]
|
||||
CMD ["sh", "-c", "php artisan octane:frankenphp --max-requests=250 --port=8000 --host=0.0.0.0"]
|
||||
CMD ["sh", "-c", "php artisan octane:frankenphp --max-requests=1000 --port=8000 --host=0.0.0.0"]
|
||||
|
||||
# Application stage
|
||||
FROM base AS app
|
||||
ENTRYPOINT ["/sbin/ssm-parent", "-c", ".ssm-parent.yaml", "run", "--", "docker-php-entrypoint"]
|
||||
CMD ["sh", "-c", "php artisan octane:frankenphp --max-requests=250 --port=8000 --host=0.0.0.0"]
|
||||
CMD ["sh", "-c", "php artisan octane:frankenphp --max-requests=1000 --port=8000 --host=0.0.0.0"]
|
||||
|
||||
Submodule packages/core-api updated: d964db3f42...04ae1ac455
Submodule packages/dev-engine updated: f11d032cb8...64a379ce12
Submodule packages/ember-core updated: 2999dcc4c0...54a45bbdaa
Submodule packages/ember-ui updated: 4f45d72386...c6694206e9
Submodule packages/fleetbase-extensions-indexer updated: db26b8add3...a6f2b4cde9
Submodule packages/fleetops updated: 0b5accee01...09487c1c50
Submodule packages/fleetops-data updated: f20408d663...08520b3981
Submodule packages/iam-engine updated: 5b12288f5b...99698152e2
Submodule packages/pallet updated: b343c621f1...9e7592eddb
Submodule packages/registry-bridge updated: 0e55d24ea1...c54b945dc1
Submodule packages/storefront updated: f7c6d42f8a...b5776acef6
@@ -125,7 +125,7 @@ docker compose up -d
|
||||
DB_SERVICE="database" # ← change if your docker‑compose uses a different name
|
||||
DB_WAIT_TIMEOUT=60 # seconds
|
||||
|
||||
echo "⏳ Waiting for “$DB_SERVICE” to become ready (timeout: ${DB_WAIT_TIMEOUT}s)…"
|
||||
echo "⏳ Waiting for $DB_SERVICE to become ready (timeout: ${DB_WAIT_TIMEOUT}s)..."
|
||||
DB_CONTAINER=$(docker compose ps -q "$DB_SERVICE")
|
||||
|
||||
if [ -z "$DB_CONTAINER" ]; then
|
||||
|
||||
Reference in New Issue
Block a user