├── .browserslistrc
├── .editorconfig
├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── README.md
├── eslint.config.js
├── index.html
├── jsconfig.json
├── package.json
├── public
└── favicon.ico
├── src
├── App.vue
├── assets
│ ├── logo.png
│ ├── logo.svg
│ └── theme.css
├── components
│ ├── AppFooter.vue
│ ├── README.md
│ └── cart
│ │ ├── ActiveCartDisplay.vue
│ │ ├── AvailableCarts.vue
│ │ ├── CartHeader.vue
│ │ ├── CartItem.vue
│ │ ├── CartItemList.vue
│ │ ├── CartSection.vue
│ │ ├── ConfigurationDetails.vue
│ │ ├── ItemPricing.vue
│ │ └── OSConfiguration.vue
├── config
│ └── sites.js
├── main.js
├── pages
│ ├── Me.vue
│ ├── README.md
│ ├── Servers.vue
│ ├── Settings.vue
│ ├── index.vue
│ └── plan
│ │ └── [plan].vue
├── plugins
│ ├── README.md
│ ├── index.js
│ └── vuetify.js
├── router
│ └── index.js
└── styles
│ ├── README.md
│ └── settings.scss
├── vite.config.mjs
└── yarn.lock
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 | not ie 11
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ "main" ]
9 | pull_request:
10 | branches: [ "main" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [18.x, 20.x, 22.x]
20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21 |
22 | steps:
23 | - uses: actions/checkout@v4
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v4
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | cache: 'npm'
29 | - run: yarn
30 | - run: yarn build
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | pnpm-debug.log*
14 |
15 | # Editor directories and files
16 | .idea
17 | .vscode
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OVH Cart
2 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import pluginVue from 'eslint-plugin-vue'
3 |
4 | export default [
5 | {
6 | name: 'app/files-to-lint',
7 | files: ['**/*.{js,mjs,jsx,vue}'],
8 | },
9 |
10 | {
11 | name: 'app/files-to-ignore',
12 | ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
13 | },
14 |
15 | js.configs.recommended,
16 | ...pluginVue.configs['flat/recommended'],
17 |
18 | {
19 | rules: {
20 | 'vue/multi-word-component-names': 'off',
21 | },
22 | }
23 | ]
24 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Ovh Cart
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "target": "es5",
5 | "module": "esnext",
6 | "baseUrl": "./",
7 | "moduleResolution": "bundler",
8 | "paths": {
9 | "@/*": [
10 | "src/*"
11 | ]
12 | },
13 | "lib": [
14 | "esnext",
15 | "dom",
16 | "dom.iterable",
17 | "scripthost"
18 | ]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ovhcart",
3 | "private": true,
4 | "type": "module",
5 | "version": "0.0.0",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview",
10 | "lint": "eslint . --fix"
11 | },
12 | "dependencies": {
13 | "@mdi/font": "7.4.47",
14 | "axios": "^1.8.4",
15 | "roboto-fontface": "*",
16 | "vue": "^3.4.31",
17 | "vuetify": "^3.6.14"
18 | },
19 | "devDependencies": {
20 | "@eslint/js": "^9.14.0",
21 | "@vitejs/plugin-vue": "^5.0.5",
22 | "eslint": "^9.14.0",
23 | "eslint-plugin-import": "^2.29.1",
24 | "eslint-plugin-n": "^16.6.2",
25 | "eslint-plugin-node": "^11.1.0",
26 | "eslint-plugin-promise": "^6.4.0",
27 | "eslint-plugin-vue": "^9.30.0",
28 | "sass": "1.77.8",
29 | "sass-embedded": "^1.77.8",
30 | "unplugin-fonts": "^1.1.1",
31 | "unplugin-vue-components": "^28.4.1",
32 | "unplugin-vue-router": "^0.10.0",
33 | "vite": "^5.4.0",
34 | "vite-plugin-vuetify": "^2.0.3",
35 | "vue-router": "^4.4.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/orvice/ovhcart/d82ddbb321ae402677eb9f35ecc1a4731287f04e/public/favicon.ico
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
44 |
45 |
51 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/orvice/ovhcart/d82ddbb321ae402677eb9f35ecc1a4731287f04e/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/assets/theme.css:
--------------------------------------------------------------------------------
1 | /* Theme variables */
2 | :root[data-theme="light"] {
3 | --background-color: #f5f5f5;
4 | --text-color: #212121;
5 | --primary-color: #1976d2;
6 | --secondary-color: #424242;
7 | --accent-color: #82b1ff;
8 | --error-color: #ff5252;
9 | --success-color: #4caf50;
10 | --warning-color: #fb8c00;
11 | --info-color: #2196f3;
12 | --surface-color: #ffffff;
13 | --card-border-color: #e0e0e0;
14 | }
15 |
16 | :root[data-theme="dark"] {
17 | --background-color: #121212;
18 | --text-color: #e0e0e0;
19 | --primary-color: #2196f3;
20 | --secondary-color: #757575;
21 | --accent-color: #536dfe;
22 | --error-color: #ff5252;
23 | --success-color: #4caf50;
24 | --warning-color: #fb8c00;
25 | --info-color: #2196f3;
26 | --surface-color: #1e1e1e;
27 | --card-border-color: #424242;
28 | }
29 |
30 | /* Apply theme variables to Vuetify */
31 | .v-application {
32 | background-color: var(--background-color) !important;
33 | color: var(--text-color) !important;
34 | }
35 |
36 | .v-main {
37 | background-color: var(--background-color) !important;
38 | }
39 |
40 | .v-card {
41 | background-color: var(--surface-color) !important;
42 | color: var(--text-color) !important;
43 | border-color: var(--card-border-color) !important;
44 | }
45 |
46 | /* Additional style overrides for dark/light themes can be added here */
47 |
48 | /* List items */
49 | .v-list-item {
50 | color: var(--text-color) !important;
51 | }
52 |
53 | .v-list {
54 | background-color: var(--surface-color) !important;
55 | color: var(--text-color) !important;
56 | }
57 |
58 | /* Buttons */
59 | .v-btn.v-btn--variant-elevated {
60 | background-color: var(--primary-color) !important;
61 | color: var(--surface-color) !important;
62 | }
63 |
64 | /* Tables */
65 | .v-table {
66 | background-color: var(--surface-color) !important;
67 | color: var(--text-color) !important;
68 | }
69 |
70 | /* Dialog */
71 | .v-dialog .v-card {
72 | background-color: var(--surface-color) !important;
73 | color: var(--text-color) !important;
74 | }
75 |
76 | /* Inputs */
77 | .v-field__field {
78 | color: var(--text-color) !important;
79 | }
80 |
81 | /* System preference dark mode listener */
82 | @media (prefers-color-scheme: dark) {
83 | :root[data-theme="auto"] {
84 | --background-color: #121212;
85 | --text-color: #e0e0e0;
86 | --primary-color: #2196f3;
87 | --secondary-color: #757575;
88 | --accent-color: #536dfe;
89 | --error-color: #ff5252;
90 | --success-color: #4caf50;
91 | --warning-color: #fb8c00;
92 | --info-color: #2196f3;
93 | --surface-color: #1e1e1e;
94 | --card-border-color: #424242;
95 | }
96 | }
97 |
98 | @media (prefers-color-scheme: light) {
99 | :root[data-theme="auto"] {
100 | --background-color: #f5f5f5;
101 | --text-color: #212121;
102 | --primary-color: #1976d2;
103 | --secondary-color: #424242;
104 | --accent-color: #82b1ff;
105 | --error-color: #ff5252;
106 | --success-color: #4caf50;
107 | --warning-color: #fb8c00;
108 | --info-color: #2196f3;
109 | --surface-color: #ffffff;
110 | --card-border-color: #e0e0e0;
111 | }
112 | }
--------------------------------------------------------------------------------
/src/components/AppFooter.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
16 |
17 |
18 |
22 | © 2016-{{ (new Date()).getFullYear() }}
Vuetify, LLC
23 | —
24 |
30 | MIT License
31 |
32 |
33 |
34 |
35 |
36 |
70 |
71 |
80 |
--------------------------------------------------------------------------------
/src/components/README.md:
--------------------------------------------------------------------------------
1 | # Components
2 |
3 | Vue template files in this folder are automatically imported.
4 |
5 | ## 🚀 Usage
6 |
7 | Importing is handled by [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components). This plugin automatically imports `.vue` files created in the `src/components` directory, and registers them as global components. This means that you can use any component in your application without having to manually import it.
8 |
9 | The following example assumes a component located at `src/components/MyComponent.vue`:
10 |
11 | ```vue
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 | ```
22 |
23 | When your template is rendered, the component's import will automatically be inlined, which renders to this:
24 |
25 | ```vue
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 | ```
36 |
--------------------------------------------------------------------------------
/src/components/cart/ActiveCartDisplay.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Active Cart
7 |
12 | {{ cartDetails.readOnly ? 'Read Only' : 'Active' }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Cart ID
24 | {{ cartDetails.cartId }}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Description
33 | {{ cartDetails.description || 'No description' }}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Expiration
42 | {{ formatDate(cartDetails.expire) }}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Items
51 | {{ cartDetails.items?.length || 0 }} items
52 |
53 |
54 |
55 |
56 |
57 |
58 |
70 |
71 |
72 |
73 |
74 |
75 |
82 | Refresh
83 |
84 |
85 |
93 | Checkout
94 |
95 |
103 | Clear
104 |
105 |
106 |
107 |
108 |
109 |
110 |
152 |
--------------------------------------------------------------------------------
/src/components/cart/AvailableCarts.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Available Carts
4 |
10 |
11 |
12 |
13 |
14 | Cart ID
15 | Description
16 | Expiration
17 | Items
18 | Actions
19 |
20 |
21 |
22 |
27 |
28 |
29 |
36 | {{ cart.cartId }}
37 |
38 |
39 | {{ cart.description || 'No description' }}
40 | {{ formatDate(cart.expire) }}
41 | {{ cart.itemsCount || 0 }}
42 |
43 |
51 | Set Active
52 |
53 |
58 | Active
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
96 |
--------------------------------------------------------------------------------
/src/components/cart/CartHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Shopping Cart
4 |
5 |
13 | Create New Cart
14 |
15 |
16 |
17 |
18 |
32 |
--------------------------------------------------------------------------------
/src/components/cart/CartItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Item #{{ index + 1 }}
7 |
13 | {{ itemDetails?.status || 'Loading...' }}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | Loading item details...
22 |
23 |
24 |
25 |
26 | {{ itemDetails?.description || itemDetails?.planCode }}
27 | {{ itemDetails?.planCode }}
28 |
29 |
36 | Quantity: {{ itemDetails?.settings?.quantity }}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | Product
45 | {{ getProductName(itemDetails?.productId) }}
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | Price
57 | {{ getItemPrice(itemDetails) }}
58 |
59 |
60 |
61 |
62 |
63 |
64 | Settings
65 | {{ itemDetails?.configurations?.length || 0 }} configurations
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
78 |
79 |
80 |
86 |
87 |
88 |
96 |
97 |
98 |
99 |
100 |
106 | Remove from Cart
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
197 |
--------------------------------------------------------------------------------
/src/components/cart/CartItemList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Cart Items
4 | $emit('configure-datacenter', { itemId, datacenterCode })"
13 | @configure-os="(osCode) => $emit('configure-os', { itemId, osCode })"
14 | @update-configuration="(label, value, configId) => $emit('update-configuration', { itemId, label, value, configId })"
15 | @delete-configuration="(configId) => $emit('delete-configuration', { itemId, configId })"
16 | @fetch-available-configurations="() => $emit('fetch-available-configurations', itemId)"
17 | />
18 |
19 |
20 |
21 |
48 |
--------------------------------------------------------------------------------
/src/components/cart/CartSection.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 | Loading cart data...
13 |
14 |
15 |
21 |
22 |
28 |
29 |
30 |
39 |
40 |
41 |
48 |
49 |
50 |
51 |
59 | Refresh Carts
60 |
61 |
62 |
63 |
64 |
65 |
145 |
--------------------------------------------------------------------------------
/src/components/cart/ConfigurationDetails.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Configuration Details
7 |
8 |
16 | Refresh Configs
17 |
18 |
19 |
20 |
21 |
22 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | {{ config.label }}
36 | {{ config.value }}
37 |
38 |
39 |
47 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
91 |
92 |
93 |
100 | Add Configuration
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | Edit Configuration
113 |
114 |
115 |
120 |
125 |
126 |
127 |
128 |
129 | Cancel
130 |
131 |
132 | Save
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
226 |
--------------------------------------------------------------------------------
/src/components/cart/ItemPricing.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pricing Summary
7 |
8 |
9 |
10 |
11 |
12 | Description
13 | Duration
14 | Price
15 |
16 |
17 |
18 |
19 | {{ price.description }}
20 | {{ formatDuration(price.duration) }}
21 | {{ price.price.text }} {{ price.price.currencySymbol }}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
58 |
--------------------------------------------------------------------------------
/src/components/cart/OSConfiguration.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Operating System Configuration
7 |
8 |
9 |
10 |
11 | Loading available operating systems...
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
Current OS
22 |
23 |
24 | {{ currentOS?.displayName || 'Not configured' }}
25 |
26 |
27 |
28 |
29 |
30 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
60 | Apply
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
160 |
161 |
166 |
--------------------------------------------------------------------------------
/src/config/sites.js:
--------------------------------------------------------------------------------
1 | export const availableSites = [
2 | { code: 'CZ', name: 'Czech Republic' },
3 | { code: 'DE', name: 'Germany' },
4 | { code: 'ES', name: 'Spain' },
5 | { code: 'EU', name: 'Europe' },
6 | { code: 'FI', name: 'Finland' },
7 | { code: 'FR', name: 'France' },
8 | { code: 'GB', name: 'United Kingdom' },
9 | { code: 'IE', name: 'Internationnal' },
10 | { code: 'IT', name: 'Italy' },
11 | { code: 'LT', name: 'Lithuania' },
12 | { code: 'MA', name: 'Morocco' },
13 | { code: 'NL', name: 'Netherlands' },
14 | { code: 'PL', name: 'Poland' },
15 | { code: 'PT', name: 'Portugal' },
16 | { code: 'SN', name: 'Senegal' },
17 | { code: 'TN', name: 'Tunisia' }
18 | ]
19 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * main.js
3 | *
4 | * Bootstraps Vuetify and other plugins then mounts the App`
5 | */
6 |
7 | // Plugins
8 | import { registerPlugins } from '@/plugins'
9 |
10 | // Components
11 | import App from './App.vue'
12 |
13 | // Composables
14 | import { createApp } from 'vue'
15 |
16 | const app = createApp(App)
17 |
18 | registerPlugins(app)
19 |
20 | app.mount('#app')
21 |
--------------------------------------------------------------------------------
/src/pages/Me.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | User Profile
6 |
7 |
15 | Refresh
16 |
17 |
18 |
19 |
20 |
21 |
25 | Loading user profile...
26 |
27 |
28 |
35 |
36 |
37 |
42 |
43 |
44 |
45 |
46 |
50 |
54 |
55 |
60 | Personal Information
61 |
62 |
63 |
64 |
65 |
66 | Name
67 | {{ userData.firstname }} {{ userData.name }}
68 |
69 |
70 | Email
71 | {{ userData.email }}
72 |
73 |
74 | Nickname
75 | {{ userData.nichandle }}
76 |
77 |
78 | Customer ID
79 | {{ userData.customerCode }}
80 |
81 |
82 | Area
83 | {{ userData.area }}
84 |
85 |
86 | State
87 |
88 |
92 | {{ userData.state }}
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
106 |
110 |
111 |
116 | Address Information
117 |
118 |
119 |
120 |
121 |
122 | Country
123 | {{ userData.country }}
124 |
125 |
126 | Currency
127 | {{ userData.currency?.code || 'N/A' }}
128 |
129 |
130 | Language
131 | {{ userData.language }}
132 |
133 |
134 | OVH Subsidiary
135 | {{ userData.ovhSubsidiary }}
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
149 | Account Settings
150 |
151 |
152 |
153 |
154 |
155 | OVHCompany
156 | {{ userData.ovhCompany }}
157 |
158 |
159 | Creation
160 | {{ formatDate(userData.creation) }}
161 |
162 |
163 | Legal Form
164 | {{ userData.legalform }}
165 |
166 |
167 | Two-Factor Auth
168 |
169 |
173 | {{ userData.hasTwoFA ? 'Enabled' : 'Disabled' }}
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
289 |
--------------------------------------------------------------------------------
/src/pages/README.md:
--------------------------------------------------------------------------------
1 | # Pages
2 |
3 | Vue components created in this folder will automatically be converted to navigatable routes.
4 |
5 | Full documentation for this feature can be found in the Official [unplugin-vue-router](https://github.com/posva/unplugin-vue-router) repository.
6 |
--------------------------------------------------------------------------------
/src/pages/Servers.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Server Plans
6 |
11 | Subsidiary: {{ selectedSite?.code || 'IE' }}
12 |
13 |
14 |
15 |
19 | Last updated: {{ formatRefreshTime(lastRefreshTime) }}
20 |
25 | {{ isFromCache ? 'Cache' : 'API' }}
26 |
27 |
28 |
29 |
37 | Refresh Servers
38 |
39 |
40 |
41 |
42 |
43 |
47 | Loading server catalog...
48 |
49 |
50 |
56 |
57 |
62 |
63 |
64 |
65 |
69 |
70 |
75 | Manually Add Server by Plan Code
76 |
77 |
78 |
79 | If you know the exact OVH plan code for a server, you can manually add it to your cart.
80 |
81 |
87 | Enter Plan Code
88 |
89 |
90 |
91 |
92 |
93 |
99 |
100 |
101 | {{ plan.planCode || 'Server Plan' }}
102 |
103 |
107 | {{ plan.invoiceName }}
108 |
109 |
110 | {{ plan.description }}
111 |
112 |
113 |
114 |
115 |
116 |
117 |
121 |
122 | Family
123 | {{ plan.family }}
124 |
125 |
126 |
127 |
128 |
129 |
133 |
134 | Type
135 | {{ plan.productType }}
136 |
137 |
138 |
139 |
140 |
141 |
145 |
146 | Memory
147 | {{ getAddonInfo(plan, 'memory') }}
148 |
149 |
150 |
151 |
152 |
153 |
157 |
158 | Storage
159 | {{ getAddonInfo(plan, 'storage') }}
160 |
161 |
162 |
163 |
164 |
165 |
169 |
170 | Bandwidth
171 | {{ getAddonInfo(plan, 'bandwidth') }}
172 |
173 |
174 |
175 |
176 |
177 |
181 |
182 | Price
183 | {{ getPlanPrice(plan) }}
184 |
185 |
186 |
187 |
191 |
198 | {{ prop.name }}: {{ prop.value }}
199 |
200 |
201 |
202 |
203 |
204 |
205 | Available Configurations
206 |
207 |
211 |
212 |
217 | {{ formatAddonCode(config.name) }}
218 |
219 |
220 |
221 | {{ config.name }}
222 |
223 |
224 | {{ config.isMandatory ? '(Required)' : '(Optional)' }}
225 |
226 | Options: {{ config.values.join(', ') }}
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
241 | Add to Cart
242 |
243 |
248 | View Details
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
263 |
264 |
265 | Add Server by Plan Code
266 |
267 |
268 |
269 |
278 |
279 |
289 |
290 |
300 |
301 |
302 |
303 |
304 |
309 | Cancel
310 |
311 |
317 | Add to Cart
318 |
319 |
320 |
321 |
322 |
323 |
324 |
328 |
329 |
330 | {{ selectedServer.planCode }}
331 |
337 | {{ selectedServer.family }}
338 |
339 |
340 |
341 | {{ selectedServer.description }}
342 |
343 |
344 |
345 |
349 | {{ prop.name }}
350 | {{ prop.value }}
351 |
352 |
353 | Price
354 | {{ getPlanPrice(selectedServer) }}
355 |
356 |
357 |
358 |
359 |
360 |
361 |
371 |
372 |
373 |
374 |
379 | Close
380 |
381 |
388 | Add to Cart
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
869 |
870 |
875 |
--------------------------------------------------------------------------------
/src/pages/Settings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Settings
4 |
5 |
6 |
16 |
17 |
23 |
24 | Get your API token from:
25 |
33 | OVH API Console
34 |
35 |
36 |
37 |
38 |
39 |
50 |
51 |
52 |
53 |
54 |
55 | {{ item?.raw?.name || 'Select site' }} {{ item?.raw?.code ? `(${item.raw.code})` : '' }}
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | {{ item?.name || '-' }}
67 | {{ item?.code || '' }}
68 |
69 |
70 |
71 |
72 |
73 | Current Settings
74 |
75 | API Endpoint: https://eu.api.ovh.com/v1 (Fixed)
76 | Selected Site: {{ localSelectedSite?.code || 'Not selected' }} - {{ localSelectedSite?.name || 'Not selected' }}
77 | Note: The API endpoint is fixed to eu.api.ovh.com regardless of site selection
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | Theme Settings
86 |
87 |
88 |
93 |
97 |
98 |
99 |
100 | Light
101 |
102 |
103 |
104 |
108 |
109 |
110 |
111 | Dark
112 |
113 |
114 |
115 |
119 |
120 |
121 |
122 | Auto (System)
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
135 | Save Settings
136 |
137 |
138 |
139 |
140 |
145 | Settings saved successfully!
146 |
147 |
148 |
149 |
150 |
248 |
--------------------------------------------------------------------------------
/src/pages/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | OVH Cart
7 |
8 |
12 |
17 |
18 | {{ tab.name }}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Shopping Cart
28 |
29 |
37 | Create New Cart
38 |
39 |
40 |
41 |
42 |
43 | Loading cart data...
44 |
45 |
51 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | Active Cart
64 |
69 | {{ activeCartDetails.readOnly ? 'Read Only' : 'Active' }}
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | Cart ID
81 | {{ activeCartDetails.cartId }}
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | Description
90 | {{ activeCartDetails.description || 'No description' }}
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | Expiration
99 | {{ formatDate(activeCartDetails.expire) }}
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | Items
108 | {{ activeCartDetails.items?.length || 0 }} items
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
Cart Items
117 |
118 |
119 |
120 |
121 | Item #{{ index + 1 }}
122 |
128 | {{ cartItemDetails[itemId]?.readOnly ? 'Read Only' : 'Active' }}
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | Loading item details...
137 |
138 |
139 |
140 |
141 | {{ cartItemDetails[itemId]?.description || cartItemDetails[itemId]?.planCode }}
142 | {{ cartItemDetails[itemId]?.planCode }}
143 |
144 |
151 | Quantity: {{ cartItemDetails[itemId]?.settings?.quantity }}
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 | Product
160 | {{ getProductName(cartItemDetails[itemId]?.productId) }}
161 |
162 |
163 |
164 |
165 |
166 | Plan Code
167 | {{ cartItemDetails[itemId]?.settings?.planCode }}
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 | Price
179 | {{ getItemPrice(cartItemDetails[itemId]) }}
180 |
181 |
182 |
183 |
184 |
185 |
186 | Settings
187 | {{ cartItemDetails[itemId]?.configurations?.length || 0 }} configurations
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 | Pricing Summary
200 |
201 |
202 |
203 |
204 |
205 | Type
206 | Price
207 |
208 |
209 |
210 |
211 | {{ priceItem.label }}
212 | {{ priceItem.price.text || `${priceItem.price.value} ${priceItem.price.currencyCode}` }}
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 | Datacenter Configuration
226 |
227 |
228 |
239 |
240 |
241 |
242 |
243 |
244 | {{ item.raw.name }}
245 | {{ item.raw.code }}
246 |
247 |
248 |
249 |
250 | Current datacenter:
251 |
252 | {{ getItemDatacenter(itemId) }}
253 |
254 | Not configured
255 |
256 |
257 |
266 | Apply Datacenter
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 | Operating System Configuration
278 |
279 |
280 |
291 |
292 |
293 |
294 |
295 |
296 | {{ item.raw.name }}
297 | {{ item.raw.code }}
298 |
299 |
300 |
301 |
302 | Current OS:
303 |
304 | {{ getItemOS(itemId) }}
305 |
306 | Not configured
307 |
308 |
309 |
318 | Apply OS
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 | Configuration Details
330 |
331 |
341 |
342 |
343 |
344 |
351 |
352 |
353 |
354 |
355 | Configuration ID
356 | Label
357 | Value
358 | Actions
359 |
360 |
361 |
362 |
363 | {{ config.id }}
364 | {{ config.label }}
365 | {{ config.value }}
366 |
367 |
368 |
378 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
406 |
407 |
414 | {{ configurationInfo[newConfig.label] }}
415 |
416 |
417 |
418 |
419 |
427 |
434 |
442 | Add
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
463 | Remove
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
482 | Refresh Cart Details
483 |
484 |
485 |
493 | Checkout
494 |
495 |
502 | Clear Active Cart
503 |
504 |
505 |
506 |
507 |
508 |
509 | Available Carts
510 |
516 |
517 |
525 |
526 |
531 | {{ item.readOnly ? 'Read Only' : 'Active' }}
532 |
533 |
539 |
540 |
541 |
542 |
543 | Expires: {{ formatDate(item.expire) }}
544 |
545 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
567 | Refresh Cart
568 |
569 |
570 |
571 |
572 |
573 |
574 |
582 |
583 |
584 |
585 |
586 |
589 |
590 |
591 |
592 |
593 |
598 |
599 |
600 |
601 |
602 | About OVH Cart
603 |
604 |
605 |
606 |
607 |
608 |
GitHub Repository
609 |
610 | OVH Cart is an open-source project hosted on GitHub.
611 |
618 | View on GitHub
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
633 |
634 | Create New Cart
635 |
636 |
637 |
646 |
647 |
656 |
657 |
666 |
667 |
673 |
674 |
675 |
676 |
677 |
682 | Cancel
683 |
684 |
690 | Create
691 |
692 |
693 |
694 |
695 |
696 |
700 |
701 | Add Server by Plan Code
702 |
703 |
704 |
713 |
714 |
724 |
725 |
735 |
736 |
737 |
738 |
739 |
744 | Cancel
745 |
746 |
752 | Add to Cart
753 |
754 |
755 |
756 |
757 |
758 |
762 |
763 | Server Details
764 |
765 |
766 |
767 |
768 |
769 |
770 |
774 |
775 | Confirm Deletion
776 |
777 | Are you sure you want to delete this configuration?
778 |
779 |
780 |
784 | Delete
785 |
786 |
789 | Cancel
790 |
791 |
792 |
793 |
794 |
795 |
800 | {{ successMessage }}
801 |
802 |
803 |
808 | {{ errorMessage }}
809 |
810 |
811 |
812 |
816 |
817 |
818 |
819 | Checkout Successful
820 |
821 |
822 |
823 | Your cart has been successfully checked out. Please use the link below to complete your payment.
824 |
825 |
826 |
832 | After payment, your order will be processed and you will receive confirmation details.
833 |
834 |
835 |
836 |
837 |
838 | Payment URL
839 |
840 |
841 |
842 |
849 |
855 |
856 |
857 |
858 |
859 |
860 |
861 |
866 | Close
867 |
868 |
875 | Open Payment Page
876 |
877 |
878 |
879 |
880 |
881 |
882 |
883 |
2306 |
--------------------------------------------------------------------------------
/src/pages/plan/[plan].vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Loading server availability...
5 |
6 |
9 |
10 |
Server Plan: {{ planData.planCode }}
11 |
12 |
13 |
14 |
Server
15 |
{{ planData.server }}
16 |
17 |
18 |
Memory
19 |
{{ planData.memory }}
20 |
21 |
22 |
Storage
23 |
{{ planData.storage }}
24 |
25 |
26 |
27 |
Datacenter Availability
28 |
29 |
35 |
{{ dc.datacenter.toUpperCase() }}
36 |
{{ formatAvailability(dc.availability) }}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
107 |
108 |
--------------------------------------------------------------------------------
/src/plugins/README.md:
--------------------------------------------------------------------------------
1 | # Plugins
2 |
3 | Plugins are a way to extend the functionality of your Vue application. Use this folder for registering plugins that you want to use globally.
4 |
--------------------------------------------------------------------------------
/src/plugins/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * plugins/index.js
3 | *
4 | * Automatically included in `./src/main.js`
5 | */
6 |
7 | // Plugins
8 | import vuetify from './vuetify'
9 | import router from '@/router'
10 |
11 | export function registerPlugins (app) {
12 | app
13 | .use(vuetify)
14 | .use(router)
15 | }
16 |
--------------------------------------------------------------------------------
/src/plugins/vuetify.js:
--------------------------------------------------------------------------------
1 | /**
2 | * plugins/vuetify.js
3 | *
4 | * Framework documentation: https://vuetifyjs.com`
5 | */
6 |
7 | // Styles
8 | import '@mdi/font/css/materialdesignicons.css'
9 | import 'vuetify/styles'
10 |
11 | // Composables
12 | import { createVuetify } from 'vuetify'
13 |
14 | // https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
15 | export default createVuetify({
16 | theme: {
17 | defaultTheme: 'dark',
18 | },
19 | })
20 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * router/index.ts
4 | *
5 | * Automatic routes for `./src/pages/*.vue`
6 | */
7 |
8 | // Composables
9 | import { createRouter, createWebHistory } from 'vue-router/auto'
10 | import { routes } from 'vue-router/auto-routes'
11 |
12 | const router = createRouter({
13 | history: createWebHistory(import.meta.env.BASE_URL),
14 | routes,
15 | })
16 |
17 | // Workaround for https://github.com/vitejs/vite/issues/11804
18 | router.onError((err, to) => {
19 | if (err?.message?.includes?.('Failed to fetch dynamically imported module')) {
20 | if (!localStorage.getItem('vuetify:dynamic-reload')) {
21 | console.log('Reloading page to fix dynamic import error')
22 | localStorage.setItem('vuetify:dynamic-reload', 'true')
23 | location.assign(to.fullPath)
24 | } else {
25 | console.error('Dynamic import error, reloading page did not fix it', err)
26 | }
27 | } else {
28 | console.error(err)
29 | }
30 | })
31 |
32 | router.isReady().then(() => {
33 | localStorage.removeItem('vuetify:dynamic-reload')
34 | })
35 |
36 | export default router
37 |
--------------------------------------------------------------------------------
/src/styles/README.md:
--------------------------------------------------------------------------------
1 | # Styles
2 |
3 | This directory is for configuring the styles of the application.
4 |
--------------------------------------------------------------------------------
/src/styles/settings.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * src/styles/settings.scss
3 | *
4 | * Configures SASS variables and Vuetify overwrites
5 | */
6 |
7 | // https://vuetifyjs.com/features/sass-variables/`
8 | // @use 'vuetify/settings' with (
9 | // $color-pack: false
10 | // );
11 |
--------------------------------------------------------------------------------
/vite.config.mjs:
--------------------------------------------------------------------------------
1 | // Plugins
2 | import Components from 'unplugin-vue-components/vite'
3 | import Vue from '@vitejs/plugin-vue'
4 | import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
5 | import ViteFonts from 'unplugin-fonts/vite'
6 | import VueRouter from 'unplugin-vue-router/vite'
7 |
8 | // Utilities
9 | import { defineConfig } from 'vite'
10 | import { fileURLToPath, URL } from 'node:url'
11 |
12 | // https://vitejs.dev/config/
13 | export default defineConfig({
14 | plugins: [
15 | VueRouter(),
16 | Vue({
17 | template: { transformAssetUrls }
18 | }),
19 | // https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme
20 | Vuetify({
21 | autoImport: true,
22 | styles: {
23 | configFile: 'src/styles/settings.scss',
24 | },
25 | }),
26 | Components(),
27 | ViteFonts({
28 | google: {
29 | families: [{
30 | name: 'Roboto',
31 | styles: 'wght@100;300;400;500;700;900',
32 | }],
33 | },
34 | }),
35 | ],
36 | define: { 'process.env': {} },
37 | resolve: {
38 | alias: {
39 | '@': fileURLToPath(new URL('./src', import.meta.url))
40 | },
41 | extensions: [
42 | '.js',
43 | '.json',
44 | '.jsx',
45 | '.mjs',
46 | '.ts',
47 | '.tsx',
48 | '.vue',
49 | ],
50 | },
51 | server: {
52 | port: 3000,
53 | },
54 | css: {
55 | preprocessorOptions: {
56 | sass: {
57 | api: 'modern-compiler',
58 | },
59 | },
60 | },
61 | })
62 |
--------------------------------------------------------------------------------