diff --git a/AppLibs/AppLibs/Libs/AsyncGateAttribute.cs b/AppLibs/AppLibs/Libs/AsyncGateAttribute.cs index 3ee9a34..f58bbe4 100644 --- a/AppLibs/AppLibs/Libs/AsyncGateAttribute.cs +++ b/AppLibs/AppLibs/Libs/AsyncGateAttribute.cs @@ -10,13 +10,14 @@ using System.Threading.Tasks; namespace AppLibs.Libs { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] - public sealed class AsyncGateAttribute : Attribute, IAsyncActionFilter + public sealed class AsyncGateAttribute : Attribute, IAsyncActionFilter, IOrderedFilter { + public int Order { get; set; } = 0; public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { if (!HttpMethods.IsGet(context.HttpContext.Request.Method)) { - // Nếu không phải GET, cho qua luôn, không cần kiểm tra gì thêm + await next(); return; } diff --git a/AppLibs/AppLibs/Libs/Crypt/UUID7.cs b/AppLibs/AppLibs/Libs/Crypt/UUID7.cs new file mode 100644 index 0000000..ba90869 --- /dev/null +++ b/AppLibs/AppLibs/Libs/Crypt/UUID7.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace AppLibs.Libs.Crypt +{ + public class UUID7 + { + public static Guid NewUuid7() + { + // Unix time in milliseconds (48 bits) + long ms = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + Span b = stackalloc byte[16]; + // put timestamp big-endian into first 6 bytes + b[0] = (byte)((ms >> 40) & 0xFF); + b[1] = (byte)((ms >> 32) & 0xFF); + b[2] = (byte)((ms >> 24) & 0xFF); + b[3] = (byte)((ms >> 16) & 0xFF); + b[4] = (byte)((ms >> 8) & 0xFF); + b[5] = (byte)(ms & 0xFF); + + // 10 bytes ngẫu nhiên + Span rnd = stackalloc byte[10]; + RandomNumberGenerator.Fill(rnd); + + // version = 0b0111 (UUIDv7) + b[6] = (byte)((rnd[0] & 0x0F) | 0x70); + b[7] = rnd[1]; + + // variant (RFC 4122) 10xx + b[8] = (byte)((rnd[2] & 0x3F) | 0x80); + b[9] = rnd[3]; + b[10] = rnd[4]; + b[11] = rnd[5]; + b[12] = rnd[6]; + b[13] = rnd[7]; + b[14] = rnd[8]; + b[15] = rnd[9]; + + // Guid constructor expects specific byte order; dùng Guid(byte[]) là OK để lưu binary16 + return new Guid(b); + } + } +} diff --git a/TWA-App/Controllers/ContactController.cs b/TWA-App/Controllers/ContactController.cs index ccca78e..df49cc7 100644 --- a/TWA-App/Controllers/ContactController.cs +++ b/TWA-App/Controllers/ContactController.cs @@ -5,8 +5,10 @@ namespace TWA_App.Controllers { public class ContactController : Controller { + [PageInfor("6000", "Contact")] public async Task Index() { + ViewData["PageID"] = "6000"; return await this.ViewAsync(); } } diff --git a/TWA-App/Controllers/ServicesController.cs b/TWA-App/Controllers/ServicesController.cs index 4b59777..eaf30f9 100644 --- a/TWA-App/Controllers/ServicesController.cs +++ b/TWA-App/Controllers/ServicesController.cs @@ -1,10 +1,22 @@ using AppLibs.Libs; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Text.RegularExpressions; +using TWA_App.Models; +using TWA_App.Models.IServices; namespace TWA_App.Controllers { public class ServicesController : Controller { + + private readonly IDataService iDataService; + public ServicesController(IDataService data) + { + iDataService = data; + } + [PageInfor("3000", "News A", "/Services/*")] [Layout(LayoutOptions.Default)] public async Task Index(string? id) @@ -19,6 +31,13 @@ namespace TWA_App.Controllers { ViewData["PageID"] = result.ID; ViewData["Title"] = result.Name; + Service s = await iDataService.GetDataCodeAsync(int.Parse(result.ID), u => u.Id); + var htmlContent = Regex.Replace(s.Content, @"~\/([^\s""'>]+)", match => + { + var relativePath = match.Groups[1].Value; // ví dụ: "images/logo.png" + return Url.AbsoluteContent(relativePath); + }); + ViewData["Content"] = htmlContent; int pageIndex = list.SubItem.IndexOf(result); for (int i = pageIndex - 1; i >= 0; i--) { diff --git a/TWA-App/Json/navlist.json b/TWA-App/Json/navlist.json index c92b5ab..aceb1f4 100644 --- a/TWA-App/Json/navlist.json +++ b/TWA-App/Json/navlist.json @@ -65,7 +65,7 @@ "url": "", "sub-item": [ { - "name": "Air Cargo", + "name": "Cargo & Passenger GSSA", "id": "G3001", "group": 1 }, @@ -78,50 +78,25 @@ }, { "id": "3002", - "name": "Aircraft Charter", - "url": "/Services/Aircraft-Charter", - "flexpage": true - }, - { - "id": "3003", - "name": "Cargo Tracking", - "url": "/Services/Cargo-Tracking", - "flexpage": true - }, - { - "id": "3004", - "name": "Groupage", - "url": "/Services/Groupage", - "flexpage": true - }, - { - "name": "Air Passenger", - "id": "G3008", - "group": 1 - }, - { - "id": "3008", "name": "Passenger GSSA", "url": "/Services/Passenger-GSSA", "flexpage": true }, + { + "name": "Aircraft Charter", + "id": "G3008", + "group": 1 + }, + { + "id": "3008", + "name": "Cargo Charter Service", + "url": "/Services/Cargo-Charter-Service", + "flexpage": true + }, { "id": "3006", - "name": "Ticketing & Reservation", - "url": "/Services/Ticketing-Reservation", - "flexpage": true - }, - { - "id": "3007", - "name": "Check-in Services", - "url": "/Services/Check-In-Services", - "flexpage": true - }, - - { - "id": "3009", - "name": "Customer Service & Support", - "url": "/Services/Customer-Service-Support", + "name": "Passenger Charter Service", + "url": "/Services/Passenger-Charter-Service", "flexpage": true }, { @@ -131,8 +106,8 @@ }, { "id": "3010", - "name": "Aviation Support", - "url": "/Services/Aviation-Support", + "name": "Logistics Service", + "url": "/Services/Logistics-Service", "flexpage": true }, { @@ -140,6 +115,29 @@ "name": "Courier Service", "url": "/Services/Courier-Service", "flexpage": true + }, + { + "name": "Aviation Support", + "id": "G3011", + "group": 1 + }, + { + "id": "3012", + "name": "Operating", + "url": "/Services/Operating", + "flexpage": true + }, + { + "id": "3013", + "name": "Warehousing", + "url": "/Services/Warehousing", + "flexpage": true + }, + { + "id": "3014", + "name": "Flight Support", + "url": "/Services/Flight-Support", + "flexpage": true } ] }, diff --git a/TWA-App/Json/servicesData.json b/TWA-App/Json/servicesData.json index 02f5550..537251c 100644 --- a/TWA-App/Json/servicesData.json +++ b/TWA-App/Json/servicesData.json @@ -1,23 +1,65 @@ [ { - "id": "cargo-gssa", + "id": "3001", "lang": "en", - "content-page": "", - "styles": [ - { - "href": "/assets/css/pages/services-gssa.css" - } - ], - "scripts": [ - { - "src": "/assets/js/vendor/swiper.min.js", - "defer": true - }, - { - "src": "/assets/js/pages/services-gssa.js", - "defer": true, - "module": true - } - ] + "content-page": "

We Are Air Cargo GSSA

If You’ve Ever Heard of A Cargo GSSA Before, This Is How It Works

Being a Cargo GSSA/GSA means

  • General Sales & Service Agents (GSSAs)/General Sales Agents (GSAs) represent airlines commercializing air freight capacity and supervise complex local cargo operations and administration and financial services for a assigned specific territory or region
  • Represent for Airlines typically work with freight forwarders and handling agents (GHAs) to deliver transportation services to shippers.
  • A extremely helpful contract with minimum cost, resources and top efficiency for Airlines in looking to expand into new markets or streamline costs in an existing location

Why Any Airlines Should Operate As Cargo GSSAs/GSAs Model

Lower Cost-Risk Market Entry

A GSSA enables an airline to enter a market without immediate investment in local sales networks and infrastructure as well as reducing initial fixed costs, operational costs such as sales offices, commercial staffing, and infrastructure. That lead to the variable costs are converted into service-based fees paid to the GSSA, replacing fixed overhead with scalable expenditure

Accelerated Route Launch (Time-To-Market)

With established sales networks and integrated IT systems, GSSAs shorten the commercial setup timeline for new routes, enabling airlines to generate revenue from day one of operations

Local Market Expertise And Relationship Network

A GSSAs possess deep knowledge and established relationships of customer demand, cargo flows, taxation, customs regulations, airport authorities essential for effective route rollout. And also maintaining strong ties with shippers, freight forwarders and airport authorities—key drivers of load factor optimization and revenue growth

Operational Risk And Crisis Management To Keep Continuity During Disruptions

A GSSA sustains cargo flow and manages on‑site crisis response in case of flight cancellations, airport congestion, schedule changes, local incidents or political disruptions,... like handle local customer communications, record every related evidence, on-sit crisis knowledge consulting to mitigate brand impact

Scalability And Innovation

A GSSAs also often provide technology solutions, market analytics, and auxiliary services (e.g., warehousing, last-mile delivery), allowing airlines to rapidly leverage value-added offerings and expand their footprint

Commercial Flexibility-Agility & Product Testing

An airline can experiment with pricing structures, service conditions, and flight frequencies through a GSSA before committing to a direct market presence with long-term commitments or in-market organizational setup

The Responsibilities Of A Cargo GSSA Include

Sales and market development; management of carriage contracts; handling commercial documentation and customs procedures; oversight of airport cargo operations; and reporting revenue and market metrics to the airline. These functions ensure commercial effectiveness, regulatory compliance, and optimization of the airline’s cargo operations

Trans World Aviation - TWA

Airline’s Trusted Cargo GSSA

Since 2014, TWA has been a leading force in GSSA Air Cargo, delivering comprehensive logistics solutions across Vietnam and the Indochina region. Backed by decades of expertise, we have grown into a market leader, providing airlines with a reliable partner to manage operations with both efficiency and profitability

Our IATA- and aviation association–certified team brings specialized knowledge across all facets of GSSA services, from handling perishables and supervising aircraft operations to ensuring strict compliance with airport security and customs regulations

Our unwavering focus on quality, precision, and innovation has established us as a trusted partner for airlines competing in today’s demanding air cargo market. We deliver the full spectrum of GSSA capabilities—spanning sales and marketing, cargo handling, accounting, and tailored logistics solutions—ensuring airlines benefit from a seamless, end-to-end service platform.

What We Deliver

  • AWB stock management and allocation
  • Freight weight balance / Load control by IATA certified staff
  • Cargo supervision and documentation for both inbound and outbound
  • Irregular / Claim processing
  • Reservation/ Capacity Control
  • ULD Control/Build-up control
  • Coordinating with Catering/Fuel/Ramp Supplier
  • Monitor on Aircraft loading / unloading
  • Ground Handling Support and Flight Supervision
  • Warehouse/handling
  • Automatic Postflight
  • Revenue Accounting
  • Competitor Rate Monitoring
  • Statistical Reporting
  • Cargo Sales & Marketing
", + "styles": [], + "scripts": [] + }, + { + "id": "3002", + "lang": "en", + "content-page": "

PAX

Our PAX GSSA Services

\"Air
  • Call Centre services, including handling of all airline reservations
  • FIT and group booking enquiries
  • Tour operator series and allotment handling and controlling
  • Handling enquiries of final consumers
  • Creation of monthly sales statistics; including revenue control & reporting
  • Dispatch of price sheets and product information
  • Telephone sales activities, product & destination advice
", + "styles": [], + "scripts": [] + }, + { + "id": "3012", + "lang": "en", + "content-page": "

PAX

Our PAX GSSA Services

\"Air
  • Call Centre services, including handling of all airline reservations
  • FIT and group booking enquiries
  • Tour operator series and allotment handling and controlling
  • Handling enquiries of final consumers
  • Creation of monthly sales statistics; including revenue control & reporting
  • Dispatch of price sheets and product information
  • Telephone sales activities, product & destination advice
", + "styles": [], + "scripts": [] + }, + { + "id": "3013", + "lang": "en", + "content-page": "

PAX

Our PAX GSSA Services

\"Air
  • Call Centre services, including handling of all airline reservations
  • FIT and group booking enquiries
  • Tour operator series and allotment handling and controlling
  • Handling enquiries of final consumers
  • Creation of monthly sales statistics; including revenue control & reporting
  • Dispatch of price sheets and product information
  • Telephone sales activities, product & destination advice
", + "styles": [], + "scripts": [] + }, + { + "id": "3014", + "lang": "en", + "content-page": "

PAX

Our PAX GSSA Services

\"Air
  • Call Centre services, including handling of all airline reservations
  • FIT and group booking enquiries
  • Tour operator series and allotment handling and controlling
  • Handling enquiries of final consumers
  • Creation of monthly sales statistics; including revenue control & reporting
  • Dispatch of price sheets and product information
  • Telephone sales activities, product & destination advice
", + "styles": [], + "scripts": [] + }, + { + "id": "3008", + "lang": "en", + "content-page": "

Cargo Charter Services

Our Cargo Charter Services

TWA provides cost effective and quick cargo charter logistics solution when scheduled operators are not able to deliver the cargo due to size, weight or the nature of the cargo, or simply the scheduled operators’ transportation time or/and the airport they operate cannot meet with the Shipper’s expectation. TWA staff has many years of experience providing professional cargo charter solutions on the following fields: Commercial Cargo; Automotive spare parts; Perishables; BIG (oversized) shipments; Project Cargo (Energy, Oil & Gas projects, etc.); Relief cargo operation; AOG

\"Overview

Air Charter – Delivering Excellence For

  • Freight Forwarders / General Sales Agents / Charities and Relief Organizations
  • Oil and Automotive Industry / Government Departments
\"Stakeholder

Supplying aircraft at a moment's notice

Regardless of the nature of your business, Progressive can rapidly source the right aircraft for the job.

  • Light jet and turbo-prop aircraft for small loads
  • Larger aircraft for bulky and outsize freight delivered within hours
\"Light

Quick response times

We are perfectly geared up to provide fast and competitive quotes 24 hours a day. Our staff are always available to find you the right aircraft for the job.

\"24/7

Remote Destinations

We go where scheduled flights won’t. We are experts at arranging charter flights to the parts of the world that are hard to reach and generally require specialized arrangements and planning. We can organize ad hoc flights or more extensive charter programs to:

  • War Zones
  • Regions of political instability
  • Oil fields
  • Natural disaster zones
  • Countries hit by strikes
  • Locations with inadequate infrastructure
\"Map

Delivering vital supplies

Many of the aircraft available for charter are ideally suited to remote locations. This includes:

  • Humanitarian aid
  • Post deliveries
  • Industry charters
  • Generator turbines
  • Consumer goods
\"Vital

Experts for charters to remote locations

We can arrange charters for you into areas that others can't. Obtaining hard to get permits is another of the valued assets we offer our client.

\"Permit

Heavy and Oversized

Making light work of heavy loads. Many of our clients are unable to split their cargo over different scheduled services or send oversized freight on lengthy journeys by land or sea. For this reason, we are prepared to help with:

  • Oil industry assemblies, including large pipes and drilling equipment
  • Power station parts
  • Marine diesel engines engineering constructions
  • Aircraft parts including engines for grounded aircraft
  • Satellites for space missions
  • Vehicle transportation, such as jeeps and communications trailers
  • Film & music stage sets
\"Outsize

Finding solutions for difficult cargo

We utilize longstanding relationships and mutual respect with the operators of such aircraft. We carefully monitor availability and negotiate the best charter prices in the industry.

\"Special

Experts for heavy and outsize cargo

Our brokers have vast experience of dealing with large cargo consignments and are always available to help you find the right charter solution. Our philosophy does not include voicemail — our experts are on the end of the phone 24/7 so our services can be called upon at any time of the day or night.

\"24/7
", + "styles": [], + "scripts": [] + }, + { + "id": "3006", + "lang": "en", + "content-page": "

Passenger Charter Services

Our Passenger Charter Services

TWA is the solution for passenger charter when the scheduled operators are not able to fulfill the Customers’ need due to transportation time, operated airport, number of passengers and/or number of personal effects / special equipment’s or due to any other reason(s). TWA staff has many years of experience in providing cost effective, quick, reliable and professional passenger charter solution on the following fields: VIP Charter; Executive Travel; Promotion- Flight; Small Business-Charters; Sprot Charters; Group Charters; Incentive Groups; Music - Bands Orchestras; Government Flight; Worker’s Transportation; Ambulance; Charter Chains

\"Overview

What We Cover

  • VIP Charter
  • Executive Travel
  • Promotion- Flight
  • Small Business-Charters
  • Sprot Charters
  • Group Charters
  • Incentive Groups
  • Music - Bands Orchestras
  • Government Flight
  • Worker’s Transportation
  • Ambulance
  • Charter Chains
\"Passenger
\"Group
", + "styles": [], + "scripts": [] + }, + { + "id": "3010", + "lang": "en", + "content-page": "

Logistics Services

End-to-End, Customizable Logistics Solutions

TWA always innovate to target only the goal that delivering total logistics solutions that could customized to our customer’s requirements. With more than 10 years of experience in this industry and our meticulous approach to solution design and execution, we gain the most valuable trust that is recognized by our customers. From basic pick and pack and warehousing and distribution services to the more complex vendor hub, cross-dock, and reverse logistics, we have what it takes to take on your logistics needs.

\"Overview

Why TWA Logistics

TWA solution success is characterized by its extensive coverage in all areas of logistics and our target to detail aimed to completely satisfy our customers. With our relentless approach to innovation and product enhancement, we can truly offer you a 360° approach to logistics that could make you more focus on the resources of your business.

\"360°

Our Logistics Model includes

  • Logistics Distribution Centers (LDC)
  • Bonded Warehousing
  • Inventory Management
  • Distribution and Transportation (DNT)
  • Customs Brokerage
  • Importer of Record (IOR)
  • Exporter of Record (EOR)
\"LDC,
", + "styles": [], + "scripts": [] + }, + { + "id": "3011", + "lang": "en", + "content-page": "

Courier Services

Same Day, Scheduled & Warehousing Delivery

Fast, dependable courier solutions with on-demand, recurring schedules, and integrated warehousing across our regional centers.

Same Day On-Demand Delivery and many more

Do you need packages or parcels delivered today? Are your transport needs more immediate, and you need packages or parcels delivered right away? If you answered yes to either question, you need Same Day – On Demand – Delivery.

\"Courier
\"Recurring

Scheduled Delivery

Do you find you or your business needing the same delivery repeatedly? Why not plan ahead and use our Scheduled Delivery service? By scheduling pick-ups and drop-offs, you don't have to worry about coordinating repetitive deliveries.

Our Delivery Services Include Warehousing

Often are the case businesses order items in large quantities but find they do not have adequate space to store those items. Our Warehouse Delivery service allows you to store your items at one of our facilities then when you need one or all items, we will deliver.

Since our establishment, TWA Courier Service has maintained a simple commitment: fast, dependable service. It takes more than talk to deliver on a commitment to service. From our 11 regional centers, our team of transportation experts stand ready to meet your needs across town, or across the country. They're supported by a fleet of drivers and vehicles and aided by computer-coordinated trafficking and a proof of delivery system. Put it all together and you have TWA's assurance of timely and reliable service and information.

  • 11 regional centers
  • Experienced transportation experts
  • Fleet of drivers and vehicles
  • Computer-coordinated trafficking
  • Proof of delivery system
  • Timely & reliable service and information
\"Warehouse
", + "styles": [], + "scripts": [] } ] \ No newline at end of file diff --git a/TWA-App/Models/Service.cs b/TWA-App/Models/Service.cs new file mode 100644 index 0000000..292da26 --- /dev/null +++ b/TWA-App/Models/Service.cs @@ -0,0 +1,33 @@ +using Newtonsoft.Json; + +namespace TWA_App.Models +{ + public class Service + { + [JsonProperty(PropertyName = "id")] + public int Id { get; set; } + + [JsonProperty(PropertyName = "content-page")] + public string Content { get; set; } + + [JsonProperty(PropertyName = "lang")] + public string Lang { get; set; } + + + [JsonProperty(PropertyName = "scripts")] + public List Scripts { get; set; } + + [JsonProperty(PropertyName = "styles")] + public List Styles { get; set; } + + public Service() + { + Id = 0; + Content = ""; + Scripts = new List(); + Styles = new List(); + Lang = ""; + } + + } +} diff --git a/TWA-App/Program.cs b/TWA-App/Program.cs index bf20497..1c29f1d 100644 --- a/TWA-App/Program.cs +++ b/TWA-App/Program.cs @@ -16,6 +16,10 @@ builder.Services.AddSingleton>(sp => { return new DataService("Json/news.json"); }); +builder.Services.AddSingleton>(sp => +{ + return new DataService("Json/servicesData.json"); +}); // Add services to the container. builder.Services.AddControllers(options => { diff --git a/TWA-App/TWA-App.csproj b/TWA-App/TWA-App.csproj index 1911cb7..b863735 100644 --- a/TWA-App/TWA-App.csproj +++ b/TWA-App/TWA-App.csproj @@ -9,17 +9,17 @@ - - - - - + + + + + @@ -74,9 +74,14 @@ + + + + + Always diff --git a/TWA-App/Views/Contact/Index.cshtml b/TWA-App/Views/Contact/Index.cshtml index 83d7dd0..d180628 100644 --- a/TWA-App/Views/Contact/Index.cshtml +++ b/TWA-App/Views/Contact/Index.cshtml @@ -1,3 +1,198 @@ -@section jsLib { +@inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment Env + +
+
+ Background + + +
+
+
+

Global Company - @ViewData["Title"]

+
+
+
+ +
+
+
+
+
+
+

Branches Offices

+
+ +
+
+
+ + Sai Gon - Headquarter + +
+
+
+ Call Us + or fill out the form below +
+ + +
+ +
+ + (84) 1900-777-888 +

Don't hesitate to contact us!

+
+
+
+ +
+ Working time +

Mon - Fri: 9:00 - 19:00 / Closed on Weekend

+
+
+
+ +
+ Company Headquarters +

39B Truong Son Street, Tan Son Nhat Ward, Ho Chi Minh City

+
+
+
+
+
+
+ + Ha Noi - Branch + +
+
+

+ The B13A Form is required by the Canadian Border Services Agency (CBSA) to enforce the necessary export controls in Canada. +

+

+ The form must be prepared and signed by a Canadian shipper (exporter) or their forwarding agent. The shipment description, value, weight, destination, and other basic information must be indicated for processing use by Customs. B13A information may be submitted electronically, by mail, or by fax. For more information, please review Info Source, published by the Treasury Board of Canada, or contact us directly. +

+

+ The CBSA is directly responsible for any updates to this form. Please see our external resources section. +

+
+ +
+
+
+
+
+ + Da Nang - Branch + +
+
+

+ The B13A Form is required by the Canadian Border Services Agency (CBSA) to enforce the necessary export controls in Canada. +

+

+ The form must be prepared and signed by a Canadian shipper (exporter) or their forwarding agent. The shipment description, value, weight, destination, and other basic information must be indicated for processing use by Customs. B13A information may be submitted electronically, by mail, or by fax. For more information, please review Info Source, published by the Treasury Board of Canada, or contact us directly. +

+

+ The CBSA is directly responsible for any updates to this form. Please see our external resources section. +

+
+ +
+
+
+
+
+ + Cam Ranh - Branch + +
+
+

+ The B13A Form is required by the Canadian Border Services Agency (CBSA) to enforce the necessary export controls in Canada. +

+

+ The form must be prepared and signed by a Canadian shipper (exporter) or their forwarding agent. The shipment description, value, weight, destination, and other basic information must be indicated for processing use by Customs. B13A information may be submitted electronically, by mail, or by fax. For more information, please review Info Source, published by the Treasury Board of Canada, or contact us directly. +

+

+ The CBSA is directly responsible for any updates to this form. Please see our external resources section. +

+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+

Contact With Us

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + + + +
+
+ +
+
+ +
+
+
+
+
+ + +
+ +@section jsLib { } \ No newline at end of file diff --git a/TWA-App/Views/Services/Index.cshtml b/TWA-App/Views/Services/Index.cshtml index 6a97d43..36b87bc 100644 --- a/TWA-App/Views/Services/Index.cshtml +++ b/TWA-App/Views/Services/Index.cshtml @@ -21,99 +21,13 @@
-
-

- We Are Air Cargo GSSA -

-

If You’ve Ever Heard of A Cargo GSSA Before, This Is How It Works

- Air Cargo GSSA illustration -
-
-
-
-

Being a Cargo GSSA/GSA means

-
-
    -
  • - - General Sales & Service Agents (GSSAs)/General Sales Agents (GSAs) represent airlines commercializing air freight capacity and supervise complex local cargo operations and administration and financial services for a assigned specific territory or region -
  • -
  • - - Represent for Airlines typically work with freight forwarders and handling agents (GHAs) to deliver transportation services to shippers. -
  • -
  • - - A extremely helpful contract with minimum cost, resources and top efficiency for Airlines in looking to expand into new markets or streamline costs in an existing location -
  • -
-
-
-
- -
-
-
-
-

Why any Airlines should operate as Cargo GSSAs/GSAs model

-
-
-
-
-
- -
-

Cost Reduction

-

- Using a GSSA eliminates the need for airlines to establish local offices or hire dedicated sales teams in each region. This reduces overhead costs while ensuring a strong market presence -

-
-
-
-
-
- -
-

Local Expertise and Market Knowledge

-

-GSSAs possess in-depth knowledge of local markets and maintain strong relationships with key stakeholders, enabling them to maximize revenue for airlines by tapping into regional opportunities​

-
-
-
-
-
- -
-

Flexibility and Innovation

-

- GSSAs provide flexible solutions that adapt to changing market conditions. From digital booking systems to real-time data analytics, GSSAs help airlines and brokers optimize routes, reduce costs and ensure efficient cargo handling -

-
-
-
-
-
-
-

- Our GSSA Services at TWA -

-
-

- Air Cargo Green Capabilities offers a range of specialized GSSA services to help airlines and brokers streamline their cargo operations and maximize revenue: -

-
    -
  • GSSA Counseling: Air Cargo Green Capabilities provides airlines with innovative solutions to optimize costs and improve operational efficiency. By implementing eco-friendly approaches, we help airlines reduce environmental impact and capture a larger share in premium cargo segments, such as pharmaceuticals and oversized goods.
  • -
  • GSSA Pharma: Focusing on the safe transport of pharmaceutical goods, GSSA Pharma offers specialized risk assessment and logistics solutions to ensure the integrity of sensitive shipments. Our collaboration with major global organizations like UNICEF and the Red Cross enables us to support humanitarian projects while expanding the reach of pharma logistics.
  • -
  • GSSA for Airlines: We help airlines improve their cargo operations by maximizing the use of high-demand cargo segments like pharma, e-commerce, and oversized goods. Air Cargo Green Capabilities assists airlines with capacity management and long-term planning, ensuring they meet growing market demands while optimizing revenue.
  • -
  • GSSA for Charter Brokers: This service supports charter brokers in expanding their market reach and improving operational flexibility. We help brokers discover new charter opportunities and manage the logistics of complex operations, ensuring they can meet the needs of their clients efficiently and profitably.
+ @Html.Raw(ViewBag.Content)
- -@Html.Raw(ViewBag.Content) @section jsLib { diff --git a/TWA-App/wwwroot/css/atg-lib/atg-core.css b/TWA-App/wwwroot/css/atg-lib/atg-core.css index c0c5c49..72ffcf1 100644 --- a/TWA-App/wwwroot/css/atg-lib/atg-core.css +++ b/TWA-App/wwwroot/css/atg-lib/atg-core.css @@ -11,7 +11,7 @@ height: auto !important } -.no-wrap{ +.no-wrap { flex-wrap: nowrap !important } @@ -44,7 +44,7 @@ html { font-family: "Mulish", sans-serif !important; color: #333; } - + body { margin: 0; font-size: 1rem; @@ -111,7 +111,7 @@ h1, h2, h3, h4, h5, h6, p { height: 100% !important; } -.h-a{ +.h-a { height: auto } @@ -119,8 +119,8 @@ h1, h2, h3, h4, h5, h6, p { width: 100% !important } -.w-n{ - width:none !important +.w-n { + width: none !important } .cfull, .con { @@ -137,11 +137,11 @@ h1, h2, h3, h4, h5, h6, p { max-width: 100%; } - .w-s-100{ + .w-s-100 { width: 100% !important } - .w-s-n{ + .w-s-n { width: none !important } } @@ -150,7 +150,7 @@ h1, h2, h3, h4, h5, h6, p { .con .r { max-width: 720px; margin-left: auto; - margin-right:auto; + margin-right: auto; } } @@ -164,7 +164,6 @@ h1, h2, h3, h4, h5, h6, p { .con .r { max-width: 1110px; } - } @media (min-width:1400px) { @@ -201,6 +200,11 @@ h1, h2, h3, h4, h5, h6, p { margin-left: 0; } +.r-n-g.n { + margin-right: -20px; + margin-left: -20px +} + .r > .c { padding-right: 0; padding-left: 0; @@ -768,10 +772,11 @@ h1, h2, h3, h4, h5, h6, p { gap: 3rem !important; } -.o-1{ +.o-1 { order: 1 } -.o-2{ + +.o-2 { order: 2 } @@ -1067,7 +1072,6 @@ h1, h2, h3, h4, h5, h6, p { .gap-s-5 { gap: 3rem !important; } - } @media (min-width:768px) { @@ -1354,16 +1358,19 @@ h1, h2, h3, h4, h5, h6, p { .ml-m-3 { margin-left: 1rem !important } - .mr-m-3{ + + .mr-m-3 { margin-right: 1rem !important } - .pt-m-0{ - padding-top: 0!important + .pt-m-0 { + padding-top: 0 !important } - .pb-m-0{ - padding-bottom: 0!important + + .pb-m-0 { + padding-bottom: 0 !important } + .pl-m-5 { padding-left: 3rem !important; } @@ -1371,6 +1378,7 @@ h1, h2, h3, h4, h5, h6, p { .pr-m-5 { padding-right: 3rem !important; } + .pr-m-4 { padding-right: 1.5rem !important; } @@ -1398,6 +1406,7 @@ h1, h2, h3, h4, h5, h6, p { .gap-m-5 { gap: 3rem !important; } + .o-m-1 { order: 1 } @@ -1406,7 +1415,7 @@ h1, h2, h3, h4, h5, h6, p { order: 2 } - .text-m-right{ + .text-m-right { text-align: right } } @@ -1680,7 +1689,7 @@ h1, h2, h3, h4, h5, h6, p { align-self: stretch !important; } - .mt-l-0{ + .mt-l-0 { margin-top: 0 !important } @@ -1691,21 +1700,25 @@ h1, h2, h3, h4, h5, h6, p { .ml-l-3 { margin-left: 1rem !important } + .ml-l-5 { margin-left: 3rem !important } - .mr-l-5{ + + .mr-l-5 { margin-right: 3rem !important } + .mr-l-0 { margin-right: 0 !important } - .mr-l-3{ + + .mr-l-3 { margin-right: 1rem !important } - .mb-l-0{ - margin-bottom: 0!important; + .mb-l-0 { + margin-bottom: 0 !important; } .pr-l-5 { @@ -1740,7 +1753,7 @@ h1, h2, h3, h4, h5, h6, p { gap: 3rem !important; } - .h-l-100{ + .h-l-100 { height: 100% } } @@ -2014,7 +2027,7 @@ h1, h2, h3, h4, h5, h6, p { align-self: stretch !important; } - .mt-x-0{ + .mt-x-0 { margin-top: 0 !important } @@ -2022,11 +2035,11 @@ h1, h2, h3, h4, h5, h6, p { margin-left: auto !important; } - .ml-x-5{ + .ml-x-5 { margin-left: 3rem !important } - .mr-x-5{ + .mr-x-5 { margin-right: 3rem !important } @@ -2448,11 +2461,13 @@ html { font-size: 16px; } } + @media (min-width: 1400px) { html { font-size: 18px; } } + h1, h2, h3, h4, h5, h6 { font-family: "Playfair Display", serif; font-weight: 800 @@ -2535,8 +2550,8 @@ h3 { } -@media (min-width: 576px){ - :root{ +@media (min-width: 576px) { + :root { --margin-base: 50px } } @@ -2717,10 +2732,10 @@ h3 { border-radius: var(--radius) } - .con-image img { - transition: .35s all ease-in-out - } + .con-image img { + transition: .35s all ease-in-out + } - .con-image:hover img{ - transform: scale(1.1) - } \ No newline at end of file + .con-image:hover img { + transform: scale(1.1) + } diff --git a/TWA-App/wwwroot/css/site.css b/TWA-App/wwwroot/css/site.css index bed4ae5..08a510a 100644 --- a/TWA-App/wwwroot/css/site.css +++ b/TWA-App/wwwroot/css/site.css @@ -179,6 +179,7 @@ h5{ .according .item { font-weight: 500; padding: 20px; + transition: all .3s ease-in-out } .according .item::before, .according .item::after { @@ -197,10 +198,11 @@ h5{ transform: rotate(90deg); } - .according .item:hover{ + .according .item:hover, .according .item.active{ + font-size: 1rem; color: var(--color-primary) } - .according .item::before:hover, .according .item::after:hover{ + .according .item::before:hover, .according .item::after:hover, .according .item.active::before, .according .item.active::after { background: var(--color-primary) } @@ -2129,6 +2131,7 @@ video.bg-video { padding: 25px; background: var(--text-color-lW3); border-radius: var(--radius); + z-index: 2; transition: top .3s ease-in-out } .sidebarR .nav-item { @@ -2675,4 +2678,43 @@ video.bg-video { top: -10px; bottom: -10px; } - /*********************End Section*******************/ \ No newline at end of file + /*********************End Section*******************/ + + +.section-contact{ + padding-top:40px +} +.contact-form .hd1 { + font-size: 1.2rem; + font-weight: 600; +} + +.contact-form .hd2 { + font-size: 1.4rem; + font-weight: 700; +} + + .contact-group { + margin-bottom: 15px + } + .contact-group i{ + display: flex; + align-items: center; + justify-content: center; + width:45px; + height: 45px; + background: var(--color-primary); + color: white; + margin-right: 15px; + border-radius: var(--radius) + } + + .contact-group div{ + width: calc(100% - 60px) + } + + .contact-group .title{ + font-weight: 700; + margin-bottom: 2px; + color: var(--text-color-dark) + } \ No newline at end of file diff --git a/TWA-App/wwwroot/images/3000/3001/cargo-charter-operations.png b/TWA-App/wwwroot/images/3000/3001/cargo-charter-operations.png new file mode 100644 index 0000000..564a2d4 Binary files /dev/null and b/TWA-App/wwwroot/images/3000/3001/cargo-charter-operations.png differ diff --git a/TWA-App/wwwroot/images/3000/3001/passenger.png b/TWA-App/wwwroot/images/3000/3001/passenger.png new file mode 100644 index 0000000..1551405 Binary files /dev/null and b/TWA-App/wwwroot/images/3000/3001/passenger.png differ diff --git a/TWA-App/wwwroot/images/3000/3001/stackholder-group-1.png b/TWA-App/wwwroot/images/3000/3001/stackholder-group-1.png new file mode 100644 index 0000000..8052875 Binary files /dev/null and b/TWA-App/wwwroot/images/3000/3001/stackholder-group-1.png differ diff --git a/TWA-App/wwwroot/images/3000/3001/stackholder-group.png b/TWA-App/wwwroot/images/3000/3001/stackholder-group.png new file mode 100644 index 0000000..d3dcdd3 Binary files /dev/null and b/TWA-App/wwwroot/images/3000/3001/stackholder-group.png differ diff --git a/TWA-App/wwwroot/images/3000/3001/twa-service-scope.png b/TWA-App/wwwroot/images/3000/3001/twa-service-scope.png new file mode 100644 index 0000000..481d0e7 Binary files /dev/null and b/TWA-App/wwwroot/images/3000/3001/twa-service-scope.png differ diff --git a/TWA-App/wwwroot/images/3000/3001/warehouse.png b/TWA-App/wwwroot/images/3000/3001/warehouse.png new file mode 100644 index 0000000..68faa67 Binary files /dev/null and b/TWA-App/wwwroot/images/3000/3001/warehouse.png differ diff --git a/TWA-App/wwwroot/images/6000/contact-form.png b/TWA-App/wwwroot/images/6000/contact-form.png new file mode 100644 index 0000000..a0a1413 Binary files /dev/null and b/TWA-App/wwwroot/images/6000/contact-form.png differ diff --git a/TWA-App/wwwroot/js/libs/js-AElementFixed.js b/TWA-App/wwwroot/js/libs/js-AElementFixed.js index 9376146..a59e24b 100644 --- a/TWA-App/wwwroot/js/libs/js-AElementFixed.js +++ b/TWA-App/wwwroot/js/libs/js-AElementFixed.js @@ -28,10 +28,10 @@ export default class AElementFixed extends window.AObject { this._snapLoopId = null; // rAF: monitor chạm đáy CHA khi đang snap this._needIntroPose = true;// hiệu ứng intro khi snap this._snapWidth = null; // lock width (px) khi portal - + this._isInit = false; this._bind(); - this._initObservers(); this._onResize(); + } update(opts = {}) { @@ -59,10 +59,17 @@ export default class AElementFixed extends window.AObject { _onResize() { const desktop = window.innerWidth >= this.o.breakpoint; - if (!desktop) { + if (desktop) { + if (!this._isInit) { + this._initObservers(); + this._isInit = true; + } + } else { + this._isInit = false; this._stopInViewLoop(); this._stopSnapMonitor(); this._unsnapAbsolute(); + this._teardownObservers(); } } @@ -102,7 +109,6 @@ export default class AElementFixed extends window.AObject { const vpBottom = e.rootBounds ? e.rootBounds.bottom : document.documentElement.clientHeight; const EPS = 0.75; if (e.isIntersecting) { - console.log(e.target.style.bottom); if (e.target.style.bottom === '0px') { this._startInViewLoop(); @@ -258,7 +264,7 @@ export default class AElementFixed extends window.AObject { this.el.style.left = ''; this.el.style.top = ''; this.el.style.bottom = ''; - + this.el.style.width = ''; this._restoreToHostBeforeProbe(); } } diff --git a/TWA-App/wwwroot/js/libs/js-AMenu.js b/TWA-App/wwwroot/js/libs/js-AMenu.js index 15dca3e..5dee714 100644 --- a/TWA-App/wwwroot/js/libs/js-AMenu.js +++ b/TWA-App/wwwroot/js/libs/js-AMenu.js @@ -130,7 +130,7 @@ export default class AMenu extends window.AObject { el.classList.remove("active"); }) } else { - this.dropdown.closeDropdown(); + this.dropdown.checkCloseDropdown(); } }).bind(this)); }).bind(this), 100); diff --git a/TWASys-App/Controllers/LoginController.cs b/TWASys-App/Controllers/LoginController.cs new file mode 100644 index 0000000..65fe103 --- /dev/null +++ b/TWASys-App/Controllers/LoginController.cs @@ -0,0 +1,17 @@ +using AppLibs.Libs; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace TWASys_App.Controllers +{ + [AllowAnonymous] + public class LoginController : Controller + { + [Layout(LayoutOptions.Custom, "Login")] + [PageInfor("10001", "Login")] + public async Task Index() + { + return await this.ViewAsync(); + } + } +} diff --git a/TWASys-App/Controllers/StorageController.cs b/TWASys-App/Controllers/StorageController.cs index c7fc5be..cdd74f2 100644 --- a/TWASys-App/Controllers/StorageController.cs +++ b/TWASys-App/Controllers/StorageController.cs @@ -1,13 +1,9 @@ using AppLibs.Libs; -using TWASys_App.Models; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Connections.Features; -using System.Diagnostics.Metrics; -using System.Security.Cryptography; -using System.Text.Encodings; -using System.Text; using AppLibs.Libs.Crypt; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.WebUtilities; +using System.Security.Cryptography; +using TWASys_App.Models; using TWASys_App.Models.ServerInfo; namespace TWASys_App.Controllers @@ -83,36 +79,40 @@ namespace TWASys_App.Controllers return Json(await model.UpdateAsync()); } + [HttpPost] + public async Task SetStorageData([FromForm] StorageModel model) + { + return Json(await model.AddAsync()); + } + [HttpGet] public async Task GenerationAccessToken() { - var t1 = Task.Run(() => + var t1 = Task.Run(() => { - var g = Guid.NewGuid(); - return Base64UrlTextEncoder.Encode(g.ToByteArray().Concat(BitConverter.GetBytes(DateTime.UtcNow.ToFileTime())).ToArray()); + return UUID7.NewUuid7(); }); var t2 = Task.Run(() => { - var g = Guid.NewGuid(); - var bytes = BitConverter.GetBytes(DateTime.UtcNow.Ticks); + var tokenValue = Base64UrlTextEncoder.Encode( + Guid.NewGuid().ToByteArray() + .Concat(BitConverter.GetBytes(DateTime.UtcNow.ToFileTimeUtc())) + .ToArray() + ); + using var sha = SHA256.Create(); + var idToken = WebEncoders.Base64UrlEncode(sha.ComputeHash( + System.Text.Encoding.UTF8.GetBytes(tokenValue) + )); - Array.Resize(ref bytes, 16); - - - byte[] data = new byte[32]; - bytes.CopyTo(data, 0); - g.ToByteArray().CopyTo(data, 16); - var hash = new CRC64(); - - return hash.HashToString(data); + return idToken; }); await Task.WhenAll(t1, t2); return Json(new { - idToken = t2.Result.ToLower(), - tokenValue = t1.Result + idToken = t1.Result.ToString(), + tokenValue = t2.Result.ToLower() }); } diff --git a/TWASys-App/DBModels/DBManagement.cs b/TWASys-App/DBModels/DBManagement.cs index 23c371e..86854f9 100644 --- a/TWASys-App/DBModels/DBManagement.cs +++ b/TWASys-App/DBModels/DBManagement.cs @@ -4,7 +4,7 @@ namespace TWASys_App.DBModels { public class DBManagement { - public static string GetConnectionString() + public static string GetConnectionString(bool allowVar = false) { string fp = Path.GetFullPath("Keys/db/"); @@ -28,6 +28,7 @@ namespace TWASys_App.DBModels SslCa = Path.Combine(fp, "ca-cert.pem"), SslCert = Path.Combine(fp, "client-cert.pem"), SslKey = Path.Combine(fp, "client-key.pem"), + AllowUserVariables = allowVar }; //172.168.192.204 return builder.ConnectionString; diff --git a/TWASys-App/DBModels/Files.cs b/TWASys-App/DBModels/Files.cs new file mode 100644 index 0000000..40cda59 --- /dev/null +++ b/TWASys-App/DBModels/Files.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TWASys_App.DBModels +{ + public class Files + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public ulong IdFiles { get; set; } + + [ForeignKey("IdFolders")] + public ulong IdFolders { get; set; } // BIGINT FK → Folders.idFolders + + public string Code { get; set; } = ""; // VARCHAR(100) + public string Name { get; set; } = ""; // VARCHAR(100) + public string Path { get; set; } = ""; // TEXT + public string Options { get; set; } = ""; // LONGTEXT (JSON, metadata) + + public DateTime CreateDate { get; set; } = DateTime.UtcNow; // DATETIME (UTC khuyên dùng) + public DateTime? LastModified { get; set; } = null; // DATETIME NULL + public int Status { get; set; } // INT(11) + + public Folder? Folder { get; set; } = null!; // nav + } +} diff --git a/TWASys-App/DBModels/Folder.cs b/TWASys-App/DBModels/Folder.cs new file mode 100644 index 0000000..0e3d411 --- /dev/null +++ b/TWASys-App/DBModels/Folder.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TWASys_App.DBModels +{ + public class Folder + { + + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + + public ulong IdFolders { get; set; } // BIGINT PK + public ulong? IdParent { get; set; } // BIGINT FK, null = root + + public string Name { get; set; } = ""; // VARCHAR(45) + public string Code { get; set; } = ""; // VARCHAR(100) + public string Path { get; set; } = ""; // TEXT + public string Options { get; set; } = ""; // LONGTEXT + + public DateTime CreateDate { get; set; } = DateTime.UtcNow; // DATETIME + public DateTime? LastModified { get; set; } = null; // DATETIME NULL + public int Status { get; set; } // INT(11) + + public Folder? Parent { get; set; } = null; + public ICollection Children { get; set; } = new List(); + } +} diff --git a/TWASys-App/DBModels/Folders_has_StorageArea.cs b/TWASys-App/DBModels/Folders_has_StorageArea.cs new file mode 100644 index 0000000..ee4e10d --- /dev/null +++ b/TWASys-App/DBModels/Folders_has_StorageArea.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace TWASys_App.DBModels +{ + public class Folders_has_StorageArea + { + [ForeignKey("")] + public ulong IdFolders { get; set; } + + [ForeignKey("")] + public long IdStorage { get; set; } + + public Folder Folder { get; set; } = null!; + public StorageArea Storage { get; set; } = null!; + } +} diff --git a/TWASys-App/DBModels/LocalServer.cs b/TWASys-App/DBModels/LocalServer.cs index e3a5fff..a32f4e2 100644 --- a/TWASys-App/DBModels/LocalServer.cs +++ b/TWASys-App/DBModels/LocalServer.cs @@ -7,66 +7,30 @@ namespace TWASys_App.DBModels { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [Column("idLocalServer")] - public int IdLocalServer { get; set; } = 0; + public long IdLocalServer { get; set; } = 0; - [StringLength(20)] - [Column("ipPrivateServer")] public string IpPrivateServer { get; set; } = ""; - - [StringLength(100)] - [Column("ipPrivateServer6")] - public string IpPrivateServer6 { get; set; } = ""; - - [StringLength(100)] - [Column("ipPublicServer")] + public string IpPrivateServerv6 { get; set; } = ""; public string IpPublicServer { get; set; } = ""; - [StringLength(45)] - [Column("pathServer")] public string PathServer { get; set; } = ""; - [StringLength(100)] - [Column("serialNumber")] public string SerialNumber { get; set; } = ""; - // Ảnh gốc ghi "osVesion" (sai chính tả). Giữ nguyên để khớp DB. - [StringLength(100)] - [Column("osVesion")] + public string OsVersion { get; set; } = ""; - [StringLength(100)] - [Column("osName")] public string OsName { get; set; } = ""; - - [StringLength(45)] - [Column("osArch")] public string OsArch { get; set; } = ""; - // Ảnh gốc ghi "osKernal". Giữ nguyên để khớp DB. - [StringLength(100)] - [Column("osKernal")] - public string OsKernel { get; set; } = ""; - - [Column("socketNum")] + public string OsKernal { get; set; } = ""; public int SocketNum { get; set; } = 0; - [StringLength(150)] - [Column("cpuName")] public string CpuName { get; set; } = ""; + public float TotalRam { get; set; } = 0; - [Column("totalRam")] - public int TotalRam { get; set; } = 0; - - [StringLength(100)] - [Column("biosVendor")] - public string BiosVendor { get; set; } = ""; - - [StringLength(100)] - [Column("productUUID")] + public string BiosVender { get; set; } = ""; public string ProductUuid { get; set; } = ""; - - [Column("status")] public int Status { get; set; } = 0; } diff --git a/TWASys-App/DBModels/MicrosoftAccount.cs b/TWASys-App/DBModels/MicrosoftAccount.cs new file mode 100644 index 0000000..e702143 --- /dev/null +++ b/TWASys-App/DBModels/MicrosoftAccount.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TWASys_App.DBModels +{ + public class MicrosoftAccount + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long IdCloudAccount { get; set; } + public string AccName { get; set; } = null!; + public string ClientID { get; set; } = null!; // GUID string + public string TenantID { get; set; } = null!; + public string? ClientSecret { get; set; } // encrypted at rest + public string? SiteID { get; set; } + public string? DriveID { get; set; } + public string? PathSharePoint { get; set; } + public string? RefreshToken { get; set; } // encrypted + public string? AccessToken { get; set; } // optional cache + public DateTime? ExpiresAt { get; set; } + public string? Scopes { get; set; } + public DateTime CreateDate { get; set; } + public DateTime? LastModified { get; set; } + public int Status { get; set; } + } +} \ No newline at end of file diff --git a/TWASys-App/DBModels/RefreshToken.cs b/TWASys-App/DBModels/RefreshToken.cs new file mode 100644 index 0000000..5d77b61 --- /dev/null +++ b/TWASys-App/DBModels/RefreshToken.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TWASys_App.DBModels +{ + public class RefreshToken + { + [Key] + public string IdRefreshToken { get; set; } = ""; + + [ForeignKey("IdServerAuthorization")] + public ulong IdServerAuthorization { get; set; } + public string __RefreshToken { get; set; } = ""; // refreshToken + public DateTime CreateDate { get; set; } + public DateTime ExpireDate { get; set; } + public int Status { get; set; } + } +} diff --git a/TWASys-App/DBModels/RefreshToken_Log.cs b/TWASys-App/DBModels/RefreshToken_Log.cs new file mode 100644 index 0000000..892a5d7 --- /dev/null +++ b/TWASys-App/DBModels/RefreshToken_Log.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TWASys_App.DBModels +{ + public class RefreshToken_Log + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public ulong IdRefreshTokenLog { get; set; } + public string IdRefreshToken { get; set; } = null!; + public DateTime DateRenew { get; set; } // UTC + public int Count { get; set; } = 1; + public int LifeTime { get; set; } + public int Status { get; set; } = 1; + + public RefreshToken RefreshToken { get; set; } = null!; + } +} diff --git a/TWASys-App/DBModels/ServerAuthorization.cs b/TWASys-App/DBModels/ServerAuthorization.cs new file mode 100644 index 0000000..215af62 --- /dev/null +++ b/TWASys-App/DBModels/ServerAuthorization.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TWASys_App.DBModels +{ + public class ServerAuthorization + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public ulong IdServerAuthorization { get; set; } + + [ForeignKey("IdStorageServer")] + public long IdStorageServer { get; set; } + public DateTime CreateDate { get; set; } + public int Count { get; set; } + public int Status { get; set; } + + [NotMapped()] + public ICollection Tokens { get; set; } = new List(); + } +} diff --git a/TWASys-App/DBModels/ServerAuthorization_has_Token.cs b/TWASys-App/DBModels/ServerAuthorization_has_Token.cs new file mode 100644 index 0000000..8db2985 --- /dev/null +++ b/TWASys-App/DBModels/ServerAuthorization_has_Token.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace TWASys_App.DBModels +{ + public class ServerAuthorization_has_Token + { + [ForeignKey("")] + public ulong IdServerAuthorization { get; set; } + + [ForeignKey("")] + public string IdToken { get; set; } = ""; + + public int Count { get; set; } + public int Status { get; set; } + + public ServerAuthorization ServerAuthorization { get; set; } = null!; + public Token Token { get; set; } = null!; + } +} \ No newline at end of file diff --git a/TWASys-App/DBModels/StorageArea.cs b/TWASys-App/DBModels/StorageArea.cs new file mode 100644 index 0000000..07d8c16 --- /dev/null +++ b/TWASys-App/DBModels/StorageArea.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TWASys_App.DBModels +{ + public class StorageArea + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long IdStorage { get; set; } // PK (AUTO_INCREMENT nếu DB đang để vậy) + + public ulong? IdEmp { get; set; } = null; // FK -> Emp (nếu có) + + + public DateTime CreateDate { get; set; } = DateTime.UtcNow; + + public double TotalSize { get; set; } = 0; // map FLOAT MySQL -> double + + public int Status { get; set; } = 0; + } +} diff --git a/TWASys-App/DBModels/StorageArea_has_StorageServer.cs b/TWASys-App/DBModels/StorageArea_has_StorageServer.cs new file mode 100644 index 0000000..9d0f083 --- /dev/null +++ b/TWASys-App/DBModels/StorageArea_has_StorageServer.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace TWASys_App.DBModels +{ + public class StorageArea_has_StorageServer + { + + [ForeignKey("IdStorage")] + public long IdStorage { get; set; } + + [ForeignKey("IdStorageServer")] + public long IdStorageServer { get; set; } + + [Column("priority", TypeName = "int")] + public int Priority { get; set; } = 0; + + [Column("createDate", TypeName = "datetime")] + public DateTime CreateDate { get; set; } = DateTime.UtcNow; + + [Column("status", TypeName = "int")] + public int Status { get; set; } = 0; + + } +} diff --git a/TWASys-App/DBModels/StorageServer.cs b/TWASys-App/DBModels/StorageServer.cs new file mode 100644 index 0000000..b7a7adc --- /dev/null +++ b/TWASys-App/DBModels/StorageServer.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TWASys_App.DBModels +{ + public class StorageServer + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long IdStorageServer { get; set; } + public long? IdEmp { get; set; } + + [ForeignKey("IdTypeStorageServer")] + public long IdTypeStorageServer { get; set; } + + public string StorageName { get; set; } = ""; + + public DateTime? CreateDate { get; set; } + + + public string ControllerID { get; set; } = ""; + + public int Status { get; set; } = 0; + } +} diff --git a/TWASys-App/DBModels/StorageServer_has_LocalServer.cs b/TWASys-App/DBModels/StorageServer_has_LocalServer.cs index e627498..175cf77 100644 --- a/TWASys-App/DBModels/StorageServer_has_LocalServer.cs +++ b/TWASys-App/DBModels/StorageServer_has_LocalServer.cs @@ -2,22 +2,22 @@ namespace TWASys_App.DBModels { - public class StorageServerHasLocalServer + public class StorageServer_has_LocalServer { - - [Column("idStorageServer")] - public int IdStorageServer { get; set; } = 0; - [Column("idLocalServer")] - public int IdLocalServer { get; set; } = 0; + [ForeignKey("IdStorageServer")] + public long IdStorageServer { get; set; } = 0; - [Column("createDate")] + [ForeignKey("IdLocalServer")] + public long IdLocalServer { get; set; } = 0; + + public DateTime CreateDate { get; set; } = DateTime.UtcNow; // nên lưu UTC - [Column("modifyDate")] + public DateTime? ModifyDate { get; set; } = null; // nên lưu UTC - [Column("status")] + public int Status { get; set; } = 0; } } diff --git a/TWASys-App/DBModels/StorageServer_has_MicrosoftAccount.cs b/TWASys-App/DBModels/StorageServer_has_MicrosoftAccount.cs new file mode 100644 index 0000000..c17cee8 --- /dev/null +++ b/TWASys-App/DBModels/StorageServer_has_MicrosoftAccount.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace TWASys_App.DBModels +{ + public class StorageServer_has_MicrosoftAccount + { + [ForeignKey("")] + public long IdStorageServer { get; set; } + [ForeignKey("")] + public long IdCloudAccount { get; set; } + public DateTime CreateDate { get; set; } = DateTime.UtcNow; + public int Status { get; set; } = 1; + + public StorageServer StorageServer { get; set; } = null!; + public MicrosoftAccount CloudAccount { get; set; } = null!; + } +} diff --git a/TWASys-App/DBModels/StorageServers_ValidationDomain.cs b/TWASys-App/DBModels/StorageServers_ValidationDomain.cs new file mode 100644 index 0000000..8c7810f --- /dev/null +++ b/TWASys-App/DBModels/StorageServers_ValidationDomain.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TWASys_App.DBModels +{ + public class StorageServers_ValidationDomain + { + [ForeignKey("")] + public long IdStorageServer { get; set; } + + [ForeignKey("")] + public long IdValidationDomain { get; set; } + + [Column("modifyDate", TypeName = "datetime")] + public DateTime? ModifyDate { get; set; } = null; + + [Column("createDate", TypeName = "datetime")] + public DateTime CreateDate { get; set; } = DateTime.UtcNow; + + [Column("status", TypeName = "int")] + public int Status { get; set; } = 0; + + } +} diff --git a/TWASys-App/DBModels/SyncFile_Log.cs b/TWASys-App/DBModels/SyncFile_Log.cs new file mode 100644 index 0000000..74ab31d --- /dev/null +++ b/TWASys-App/DBModels/SyncFile_Log.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TWASys_App.DBModels +{ + public class SyncFile_Log + { + public ulong Id { get; set; } + + [ForeignKey("")] + public long IdFiles { get; set; } + + [ForeignKey("")] + public long IdStorageServer { get; set; } + [ForeignKey("")] + public long IdLocalServer { get; set; } + + [ForeignKey("")] + public long IdCloudAccount { get; set; } // FK -> CloudAccounts.id + public DateTime SyncDate { get; set; } = DateTime.UtcNow; // UTC + public string? PathOnServer { get; set; } // đường dẫn lưu trên đích + public int Status { get; set; } // 0=pending,1=ok,2=retry,3=failed,... + + // nav + public Files File { get; set; } = null!; + } +} diff --git a/TWASys-App/DBModels/Token.cs b/TWASys-App/DBModels/Token.cs new file mode 100644 index 0000000..7f1ec51 --- /dev/null +++ b/TWASys-App/DBModels/Token.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TWASys_App.DBModels +{ + public class Token + { + [Key] + public string IdToken { get; set; } = ""; + public string AccessToken { get; set; } = ""; + public DateTime CreateDate { get; set; } + public DateTime ExpireDate { get; set; } + public int Status { get; set; } + } +} diff --git a/TWASys-App/DBModels/TypeStorageServer.cs b/TWASys-App/DBModels/TypeStorageServer.cs index cff1fdc..73e26d9 100644 --- a/TWASys-App/DBModels/TypeStorageServer.cs +++ b/TWASys-App/DBModels/TypeStorageServer.cs @@ -1,10 +1,12 @@ using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace TWASys_App.DBModels { public class TypeStorageServer { [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int IdTypeStorageServer { get; set; } public string TypeName { get; set; } diff --git a/TWASys-App/DBModels/ValidationDomain.cs b/TWASys-App/DBModels/ValidationDomain.cs index f278cae..42f072e 100644 --- a/TWASys-App/DBModels/ValidationDomain.cs +++ b/TWASys-App/DBModels/ValidationDomain.cs @@ -1,11 +1,13 @@ -using System.ComponentModel.DataAnnotations; + using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace TWASys_App.DBModels { public class ValidationDomain { [Key] - public int IdValidationDomain { set; get; } + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long IdValidationDomain { set; get; } public string Protocol { set; get; } public int PortNumber { set; get; } diff --git a/TWASys-App/Dapper.AExtentions/BatchInsert.cs b/TWASys-App/Dapper.AExtentions/BatchInsert.cs new file mode 100644 index 0000000..a002229 --- /dev/null +++ b/TWASys-App/Dapper.AExtentions/BatchInsert.cs @@ -0,0 +1,329 @@ +using Dapper; +using Microsoft.AspNetCore.Identity; +using MySqlConnector; +using System.ComponentModel.DataAnnotations.Schema; +using System.Data; +using System.Globalization; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.Intrinsics.Arm; +using System.Text; + +namespace TWASys_App.Dapper.AExtentions +{ + public class DataValue + { + public object? Data { set; get; } + + public string Name { set; get; } = ""; + + public string PKName { set; get; } = ""; + } + + public class BatchInsert + { + private IDbConnection _connect; + private IDbTransaction _transaction; + private Dictionary _dataKey; + private List _list; + private StringBuilder queryBatch = new(); + public int IdTable = 100; + public BatchInsert(IDbConnection connect, IDbTransaction transaction) + { + _connect = connect; + _transaction = transaction; + _dataKey = new Dictionary(); + _list = new List(); + } + + public void AddRow(T obj) + { + var dt = new DataValue + { + Name = "", + PKName = "", + Data = obj + }; + _list.Add(dt); + + var p = DapperCommand.QueryInsert(obj, _dataKey, IdTable); + IdTable++; + dt.Name = p.Name; + dt.PKName = p.KeyVar; + if (p.IsKeyAI) + { + _dataKey.TryAdd(p.KeyVar, dt); + } + queryBatch.AppendLine(p.Query); + } + + public string GetQuery() + { + string query = string.Empty; + foreach (var (key, value) in _dataKey) + { + query += $"Select @tb{value.Name}_{key} As {key}; \n"; + } + queryBatch.AppendLine(query); + return queryBatch.ToString(); + } + + public async Task ExcuteQuery() + { + + var dynamic = ToDynamicParametersSeqPrefix(_list); + var str = GetQuery(); + Console.WriteLine(str); + using var grid = await _connect.QueryMultipleAsync(str, dynamic, _transaction); + await MapAutoIncrementIdsAsync(grid, _dataKey.Values); + + } + + public static async Task MapAutoIncrementIdsAsync(SqlMapper.GridReader grid,IEnumerable items) + { + foreach (var dv in items) + { + var obj = dv?.Data; + if (obj is null) continue; + + var (pk, isAuto) = FindPrimaryKey(obj.GetType()); + if (pk is null || !isAuto) continue; // không phải auto-inc → không đọc scalar + + // nếu object đã có ID (không rỗng) thì bỏ qua để không lệch thứ tự result set + var cur = pk.GetValue(obj); + if (!IsEmptyId(cur)) continue; + + // đọc scalar từ SELECT kế tiếp và gán lại cho property khóa + var raw = await grid.ReadSingleAsync(); + var targetType = Nullable.GetUnderlyingType(pk.PropertyType) ?? pk.PropertyType; + + if (raw is IDictionary row) + { + if (!row.TryGetValue(dv?.PKName, out raw)) + throw new InvalidOperationException($"Missing column {dv?.PKName}"); + } + + // MySqlDecimal → decimal + if (raw?.GetType().FullName == "MySql.Data.Types.MySqlDecimal") + { + var t = raw.GetType(); + raw = (decimal)t.GetProperty("Value")!.GetValue(raw)!; + } + + // Giờ o phải là số: long/ulong/int/decimal/… + var target = Nullable.GetUnderlyingType(pk.PropertyType) ?? pk.PropertyType; + pk.SetValue(obj, ConvertNumericTo(target, raw)); + + } + } + static object ConvertNumericTo(Type target, object? x) + { + // ưu tiên xử lý 2 nhánh chính + if (target == typeof(ulong) || target == typeof(uint) || target == typeof(ushort) || target == typeof(byte)) + { + ulong u = x switch + { + ulong v => v, + long v => v < 0 ? throw new OverflowException() : (ulong)v, + int v => v < 0 ? throw new OverflowException() : (ulong)v, + decimal v => v < 0 ? throw new OverflowException() : checked((ulong)v), + _ => Convert.ToUInt64(x, System.Globalization.CultureInfo.InvariantCulture) + }; + return target == typeof(ulong) ? u : + target == typeof(uint) ? checked((uint)u) : + target == typeof(ushort) ? checked((ushort)u) : + checked((byte)u); + } + else + { + long s = x switch + { + long v => v, + ulong v => v > (ulong)long.MaxValue ? throw new OverflowException() : (long)v, + int v => v, + decimal v => checked((long)v), + _ => Convert.ToInt64(x, System.Globalization.CultureInfo.InvariantCulture) + }; + return target == typeof(long) ? s : + target == typeof(int) ? checked((int)s) : + target == typeof(short) ? checked((short)s) : + checked((sbyte)s); + } + } + + + + public static DynamicParameters ToDynamicParametersSeqPrefix( + IEnumerable inputs // 0 coi là "trống" (trừ property tên Status) + ) + { + var dp = new DynamicParameters(); + var seen = new HashSet(StringComparer.Ordinal); + + foreach (var src in inputs) + { + var t = src.Data?.GetType(); + + var props = t?.GetProperties(BindingFlags.Public | BindingFlags.Instance); + + foreach (var p in props) + { + if (!p.CanRead) continue; + if (IsNotMapped(p)) continue; + if (IsKeyOrForeignKey(p, src.Data)) + continue; + + + var paramName = "tb" + src.Name + "_" + p.Name; + var val = p.GetValue(src.Data); + dp.Add("@" + paramName, val); + } + } + return dp; + } + private static (PropertyInfo? Key, bool IsAutoInc) FindPrimaryKey(Type t) + { + PropertyInfo? key = null; bool auto = false; + + foreach (var p in t.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (HasAttr(p, "System.ComponentModel.DataAnnotations.KeyAttribute", "KeyAttribute", + "Dapper.Contrib.Extensions.KeyAttribute")) + { key = p; auto = IsDatabaseGeneratedIdentity(p); break; } + + if (HasAttr(p, "Dapper.Contrib.Extensions.ExplicitKeyAttribute", "ExplicitKeyAttribute")) + { key = p; auto = false; break; } + } + + // fallback theo tên "Id" nếu không có attribute + key ??= t.GetProperty("Id", BindingFlags.Public | BindingFlags.Instance); + + // nếu không tìm được attribute, đoán auto-inc cho kiểu số + if (key != null && !auto) + { + var k = Nullable.GetUnderlyingType(key.PropertyType) ?? key.PropertyType; + auto = k == typeof(int) || k == typeof(long) || k == typeof(uint) || k == typeof(ulong); + } + return (key, auto); + } + + private static bool IsDatabaseGeneratedIdentity(PropertyInfo p) + { + // [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + foreach (var ca in p.CustomAttributes) + { + var full = ca.AttributeType.FullName ?? ca.AttributeType.Name; + if (full is "System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedAttribute" + or "DatabaseGeneratedAttribute") + { + if (ca.ConstructorArguments.Count == 1 && ca.ConstructorArguments[0].ArgumentType.IsEnum) + return Convert.ToInt32(ca.ConstructorArguments[0].Value) == 1; // Identity = 1 + } + } + return false; + } + + private static bool IsNotMapped(PropertyInfo p) + { + return HasAttr(p, "System.ComponentModel.DataAnnotations.Schema.NotMappedAttribute", "NotMappedAttribute"); + } + private static bool IsKey(PropertyInfo p, object? obj) + { + if( HasAttr(p, + "System.ComponentModel.DataAnnotations.KeyAttribute", "KeyAttribute", + "Dapper.Contrib.Extensions.KeyAttribute")) + { + var val = p.GetValue(obj); + ulong t = 0; + if (val == null && string.IsNullOrWhiteSpace(val?.ToString())) + { + return true; + } + else + { + if (ulong.TryParse(val?.ToString(), out t) && t == 0) + { + return true; + } + } + } + return false; + } + + + private static bool IsKeyOrForeignKey(PropertyInfo p, object? obj) + { + return IsKey(p, obj) || IsForeignKeyProperty(p, obj); + } + + private static bool HasAttr(MemberInfo m, params string[] names) + { + return m.CustomAttributes.Any(ca => + { + var full = ca.AttributeType.FullName ?? ca.AttributeType.Name; + var shortName = ca.AttributeType.Name; + return names.Contains(full) || names.Contains(shortName); + }); + } + + private static bool IsForeignKeyProperty(PropertyInfo p, object? obj) + { + // [ForeignKey] ngay trên property + if (HasAttr(p, "System.ComponentModel.DataAnnotations.Schema.ForeignKeyAttribute", "ForeignKeyAttribute")) + { + var val = p.GetValue(obj); + ulong t = 0; + if (val == null && string.IsNullOrWhiteSpace(val?.ToString())) + { + return true; + } + else + { + if (ulong.TryParse(val?.ToString(), out t) && t == 0) + { + return true; + } + + } + } + + //// [ForeignKey("PropName")] đặt trên navigation khác, trỏ tới property này + //var my = p.Name; + //foreach (var nav in allProps) + //{ + // if (ReferenceEquals(nav, p)) continue; + // var fkAttr = nav.CustomAttributes.FirstOrDefault(ca => + // (ca.AttributeType.FullName ?? ca.AttributeType.Name) is + // "System.ComponentModel.DataAnnotations.Schema.ForeignKeyAttribute" or "ForeignKeyAttribute"); + // if (fkAttr == default) continue; + + // var nameArg = fkAttr.ConstructorArguments.FirstOrDefault().Value?.ToString(); + // if (string.IsNullOrWhiteSpace(nameArg)) + // nameArg = fkAttr.NamedArguments.FirstOrDefault(a => a.MemberName == "Name").TypedValue.Value?.ToString(); + + // if (string.IsNullOrWhiteSpace(nameArg)) continue; + + // var names = nameArg.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) + // .Select(s => s.Trim()); + // if (names.Any(n => string.Equals(n, my, StringComparison.OrdinalIgnoreCase))) + // return true; + //} + return false; + } + private static bool IsEmptyId(object? v) + { + if (v is null) return true; + var t = Nullable.GetUnderlyingType(v.GetType()) ?? v.GetType(); + if (!t.IsValueType) return false; + try { return v.Equals(Activator.CreateInstance(t)); } catch { return false; } + } + } + public sealed class PropMeta + { + public required PropertyInfo Prop { get; init; } + public required Func Getter { get; init; } + public required Type UnderlyingType { get; init; } + public required bool IsNotMapped { get; init; } + public required bool IsKeyOrForeignKey { get; init; } + } +} diff --git a/TWASys-App/Dapper.AExtentions/DapperCommand.cs b/TWASys-App/Dapper.AExtentions/DapperCommand.cs index d1dda09..a05b556 100644 --- a/TWASys-App/Dapper.AExtentions/DapperCommand.cs +++ b/TWASys-App/Dapper.AExtentions/DapperCommand.cs @@ -1,12 +1,80 @@ using Dapper; +using Newtonsoft.Json.Linq; +using System.Data; using System.Data.Common; +using System.Net.WebSockets; using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Xml.Linq; namespace TWASys_App.Dapper.AExtentions { public static class DapperCommand { - public static async Task Insert(this DbConnection connection, T obj, bool identityInsert = true) + + public static dynamic QueryInsert(T obj) + { + return QueryInsert(obj, new Dictionary()); + } + + public static dynamic QueryInsert(T obj, Dictionary data, int IdTable = 100) + { + var type = typeof(T); + var tableName = TableMapper.GetTableName(type); + var allProperties = PropertiesCache.TypePropertiesCache(type); + var keyProperties = PropertiesCache.KeyPropertiesCache(type); + var fkProperties = PropertiesCache.FKPropertiesCache(type); + var computedProperties = PropertiesCache.ComputedPropertiesCache(type); + var columns = PropertiesCache.GetColumnNamesCache(type); + + var insertProperties = allProperties.Except(computedProperties).Except(fkProperties).ToList(); + var insertPropertiesString = string.Empty; + var stringKeyProperty = string.Empty; + var keyAI = string.Empty; + var flag = false; + var q = string.Empty; + if (keyProperties.Count > 0) + { + flag = true; + insertProperties = insertProperties.Except(keyProperties).ToList(); + stringKeyProperty = GetColumnsString(keyProperties.ToList(), columns); + keyAI = $@"SET @{GetColumnsString(keyProperties.ToList(), columns, IdTable.ToString(), "")} := LAST_INSERT_ID();"; + } + insertPropertiesString = GetColumnsString(insertProperties, columns); + var fkPropertiesString = string.Empty; + var fkValuesString = string.Empty; + if (data.Count > 0) + { + fkPropertiesString = GetColumnsString(fkProperties, columns); + + + fkValuesString = string.Join(", ", fkProperties.Select(u => { + var ok = data.TryGetValue(u.Name, out var v); + if (ok) + { + return $"@tb{v?.Name}_{u.Name}"; + } + else + { + return $"@tb{IdTable}_{u.Name}"; + } + })); + fkPropertiesString += (string.IsNullOrEmpty(fkPropertiesString))?"": ", "; + fkValuesString += (string.IsNullOrEmpty(fkValuesString)) ? "" : ", "; + } + q = $@"INSERT INTO {FormatTableName(tableName)} ({fkPropertiesString + insertPropertiesString}) VALUES ({fkValuesString + GetColumnsString(insertProperties, columns, IdTable.ToString(), "@")}); + {keyAI}"; + + return new + { + IsKeyAI = flag, + Query = q, + Name = IdTable.ToString(), + KeyVar = stringKeyProperty + }; + } + public static async Task Insert(this DbConnection connection, T obj, IDbTransaction? tx = null) { var type = typeof(T); var tableName = TableMapper.GetTableName(type); @@ -17,13 +85,14 @@ namespace TWASys_App.Dapper.AExtentions var insertProperties = allProperties.Except(computedProperties).ToList(); var insertPropertiesString = string.Empty; - if (identityInsert) + + if (keyProperties.Count > 0) { insertProperties = insertProperties.Except(keyProperties).ToList(); insertPropertiesString = GetColumnsString(insertProperties, columns); var insertedId = await connection.QueryFirstAsync($@" INSERT INTO {FormatTableName(tableName)} ({insertPropertiesString}) VALUES ({GetColumnsString(insertProperties, columns, "@")}); - SELECT LAST_INSERT_ID()", obj); + SELECT LAST_INSERT_ID()", obj, tx); keyProperties[0].SetValue(obj, insertedId); } @@ -32,7 +101,7 @@ namespace TWASys_App.Dapper.AExtentions insertPropertiesString = GetColumnsString(insertProperties, columns); await connection.QueryFirstAsync($@" INSERT INTO {FormatTableName(tableName)}({insertPropertiesString}) - VALUES ({GetColumnsString(insertProperties, columns, "@")})", obj); + VALUES ({GetColumnsString(insertProperties, columns, "@")})", obj, tx); } } @@ -71,17 +140,24 @@ namespace TWASys_App.Dapper.AExtentions { return string.Join(prefix, properties.Select(p => $"{columnNames[p.Name]} = @{p.Name}")); } - private static string GetColumnsString(IEnumerable properties, IReadOnlyDictionary columnNames, string tablePrefix = null) + + + private static string GetColumnsString(IEnumerable properties, IReadOnlyDictionary columnNames, string tbName, string tablePrefix = "") { + if (tbName != string.Empty) tbName = "tb" + tbName + "_"; if (tablePrefix == "target.") { return string.Join(", ", properties.Select(property => $"{tablePrefix}{columnNames[property.Name]} as {property.Name}")); } else if(tablePrefix == "@") { - return string.Join(", ", properties.Select(property => $"{tablePrefix}{property.Name}")); + return string.Join(", ", properties.Select(property => $"{tablePrefix}{tbName + property.Name}")); } - return string.Join(", ", properties.Select(property => $"{tablePrefix}{columnNames[property.Name]}")); + return string.Join(", ", properties.Select(property => $"{tablePrefix}{tbName + columnNames[property.Name].Replace("__", "")}")); + } + private static string GetColumnsString(IEnumerable properties, IReadOnlyDictionary columnNames, string tablePrefix = null) + { + return GetColumnsString(properties, columnNames, "", tablePrefix); } private static string FormatTableName(string table) { diff --git a/TWASys-App/Dapper.AExtentions/PropertiesCache.cs b/TWASys-App/Dapper.AExtentions/PropertiesCache.cs index c708b26..6764559 100644 --- a/TWASys-App/Dapper.AExtentions/PropertiesCache.cs +++ b/TWASys-App/Dapper.AExtentions/PropertiesCache.cs @@ -1,4 +1,6 @@ using System.Collections.Concurrent; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Reflection; @@ -7,10 +9,12 @@ namespace TWASys_App.Dapper.AExtentions public class PropertiesCache { private static readonly ConcurrentDictionary> KeyProperties = new(); + private static readonly ConcurrentDictionary> FKProperties = new(); private static readonly ConcurrentDictionary> TypeProperties = new(); private static readonly ConcurrentDictionary> ComputedProperties = new(); private static readonly ConcurrentDictionary> ColumnNames = new(); + public static List TypePropertiesCache(Type type) { if (TypeProperties.TryGetValue(type.TypeHandle, out var cachedProps)) @@ -60,6 +64,28 @@ namespace TWASys_App.Dapper.AExtentions return result; } + public static bool IsPkAutoIncrement(PropertyInfo p) + { + + // ===== Fallback by-name (nếu không thể tham chiếu typed) + var attrs = p.GetCustomAttributes(true); + bool hasKeyByName = attrs.Any(a => a.GetType().Name is "KeyAttribute"); + + var dbgenByName = attrs.FirstOrDefault(a => a.GetType().Name is "DatabaseGeneratedAttribute"); + bool isIdentityByName = false; + if (dbgenByName != null) + { + // Tìm property "DatabaseGeneratedOption" (enum) và đọc giá trị "Identity" + var prop = dbgenByName.GetType().GetProperty("DatabaseGeneratedOption"); + var val = prop?.GetValue(dbgenByName)?.ToString(); // ví dụ "Identity" + isIdentityByName = string.Equals(val, "Identity", StringComparison.OrdinalIgnoreCase); + } + + if (hasKeyByName && isIdentityByName) return true; + + return false; + } + public static List KeyPropertiesCache(Type type) { if (KeyProperties.TryGetValue(type.TypeHandle, out var cachedProps)) @@ -68,19 +94,42 @@ namespace TWASys_App.Dapper.AExtentions } var allProperties = TypePropertiesCache(type); - var keyProperties = allProperties.Where(p => p.GetCustomAttributes(true).Any(a => a.GetType().Name == "KeyAttribute")).ToList(); + var keyProperties = allProperties.Where(IsPkAutoIncrement).ToList(); + KeyProperties[type.TypeHandle] = keyProperties; // ghi cache khi miss + return keyProperties; + } - if (keyProperties.Count == 0) + + public static List FKPropertiesCache(Type type) + { + if (FKProperties.TryGetValue(type.TypeHandle, out var cachedProps)) { - var idProp = allProperties.Find(p => string.Equals(p.Name, "id", StringComparison.CurrentCultureIgnoreCase)); - if (idProp != null) + return cachedProps.ToList(); + } + var allProperties = TypePropertiesCache(type); + var keyProperties = allProperties.Where(HasForeignKeyAttribute).ToList(); + FKProperties[type.TypeHandle] = keyProperties; + return keyProperties; + } + static bool HasForeignKeyAttribute(PropertyInfo p) + { + if (p.GetCustomAttribute() != null) return true; + + // FK đặt trên navigation và chỉ ra tên prop FK + var t = p.DeclaringType!; + foreach (var nav in t.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + + // Fallback by-name nếu không tham chiếu DataAnnotations (tùy dự án) + var attr = nav.GetCustomAttributes(true).FirstOrDefault(a => a.GetType().Name == "ForeignKeyAttribute"); + if (attr != null) { - keyProperties.Add(idProp); + var val = attr.GetType().GetProperty("Name")?.GetValue(attr); + if (val == null && string.IsNullOrWhiteSpace(val?.ToString())) + return true; } } - - KeyProperties[type.TypeHandle] = keyProperties; - return keyProperties; + return false; } public static List ComputedPropertiesCache(Type type) @@ -95,7 +144,7 @@ namespace TWASys_App.Dapper.AExtentions return computedProperties; } - private static IReadOnlyDictionary GetColumnNames(IEnumerable props) + public static IReadOnlyDictionary GetColumnNames(IEnumerable props) { var ret = new Dictionary(); foreach (var prop in props) diff --git a/TWASys-App/Models/Login/LoginCheckFilter.cs b/TWASys-App/Models/Login/LoginCheckFilter.cs new file mode 100644 index 0000000..5222bb2 --- /dev/null +++ b/TWASys-App/Models/Login/LoginCheckFilter.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.AspNetCore.Mvc.Filters; +using System.Text.Encodings.Web; + +namespace TWASys_App.Models.Login +{ + public sealed class LoginCheckFilter : IAsyncActionFilter, IOrderedFilter + { + public int Order => int.MinValue; + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + + var http = context.HttpContext; + + // 1) Bỏ qua nếu endpoint cho phép anonymous + var endpoint = http.GetEndpoint(); + if (endpoint?.Metadata.GetMetadata() != null || + context.Filters.OfType().Any()) + { + await next(); + return; + } + + // 2) Nếu chưa đăng nhập → redirect (web) hoặc 401 (API/Ajax) + var uid = http.Session.GetString("userID"); + if (string.IsNullOrEmpty(uid)) + { + // API/Ajax → trả 401 JSON; Web → redirect login + var accepts = http.Request.Headers.Accept.ToString(); + var isApi = accepts.Contains("application/json", StringComparison.OrdinalIgnoreCase); + + if (isApi) + { + context.Result = new JsonResult(new MessageHeader + { + ID = 1000, + Message = "Required Login", + Status = 0 + }); + } + else + { + var returnUrl = UrlEncoder.Default.Encode(http.Request.Path + http.Request.QueryString); + context.Result = new RedirectResult($"/login?returnUrl={returnUrl}"); + } + return; + + } + await next(); + } + + } +} diff --git a/TWASys-App/Models/ModelBase.cs b/TWASys-App/Models/ModelBase.cs index 054a645..84a691d 100644 --- a/TWASys-App/Models/ModelBase.cs +++ b/TWASys-App/Models/ModelBase.cs @@ -1,4 +1,6 @@ -namespace TWASys_App.Models +using MySqlConnector; + +namespace TWASys_App.Models { public abstract class ModelBase: IPaging { @@ -12,6 +14,6 @@ public int PageNumber { get; set; } public int MaxRow { get; set; } - + protected MySqlConnection _connection = null!; } } diff --git a/TWASys-App/Models/Shared.cs b/TWASys-App/Models/Shared.cs new file mode 100644 index 0000000..a3c2ec2 --- /dev/null +++ b/TWASys-App/Models/Shared.cs @@ -0,0 +1,12 @@ +namespace TWASys_App.Models +{ + public class Shared + { + private static List _list = new List { "80633.jpg", "806372.jpg", "806459.jpg" }; + public static string RandomImages() + { + var r = new Random(); + return _list[r.Next(0, _list.Count - 1)]; + } + } +} diff --git a/TWASys-App/Models/StorageModel.cs b/TWASys-App/Models/StorageModel.cs new file mode 100644 index 0000000..c240216 --- /dev/null +++ b/TWASys-App/Models/StorageModel.cs @@ -0,0 +1,159 @@ + +using Microsoft.AspNetCore.Razor.Language.Intermediate; +using Microsoft.VisualBasic; +using MySqlConnector; +using System.Data.Common; +using System.Xml.Linq; +using TWASys_App.Dapper.AExtentions; +using TWASys_App.DBModels; + +namespace TWASys_App.Models +{ + public class StorageModel : ModelBase + { + public long StorageTypeID { set; get; } + + public string ControllerName { set; get; } = ""; + + public string StorageName { set; get; } = ""; + + public ICollection ValidationDomains { set; get; } = new List(); + + public string IpPublic { set; get; } = ""; + + public string IpPrivatev4 { set; get; } = ""; + + public string IpPrivatev6 { set; get; } = ""; + + public string OsName { set; get; } = ""; + + public string OsPlatform { set; get; } = ""; + + public string OsVersion { set; get; } = ""; + + public string OsKernel { set; get; } = ""; + + public string OsArch { set; get; } = ""; + + public string BiosSN { set; get; } = ""; + public string BiosVendor { set; get; } = ""; + + public string BiosUUID { set; get; } = ""; + + public int SocketN { set; get; } + + public string CpuName { set; get; } = ""; + + public float TotalRam { set; get; } + + public string TokenID { set; get; } = ""; + + public string TokenValue { set; get; } = ""; + + public string RTokenID { set; get; } = ""; + + public string RTokenValue { set; get; } = ""; + + public override async Task AddAsync() + { + await using var con = new MySqlConnector.MySqlConnection(DBManagement.GetConnectionString(true)); + + await con.OpenAsync(); + var trans = await con.BeginTransactionAsync(); + var f = new MessageHeader(); + try + { + var localServer = new LocalServer + { + IpPrivateServer = IpPrivatev4, + IpPrivateServerv6 = IpPrivatev6, + IpPublicServer = IpPublic, + PathServer = "", + SerialNumber = BiosSN, + OsVersion = OsVersion, + OsName = OsName, + OsArch = OsArch, + OsKernal = OsKernel, + SocketNum = SocketN, + CpuName = CpuName, + TotalRam = TotalRam, + BiosVender = BiosVendor, + ProductUuid = BiosUUID, + Status = 0 + };//idTypeStorageServer + var storageServer = new StorageServer + { + IdEmp = null, + IdTypeStorageServer = StorageTypeID, + StorageName = StorageName, + CreateDate = DateTime.UtcNow, + ControllerID = ControllerName, + Status = 0 + }; + var ss_lc = new StorageServer_has_LocalServer + { + IdStorageServer = storageServer.IdStorageServer, + IdLocalServer = localServer.IdLocalServer, + CreateDate = DateTime.UtcNow, + ModifyDate = null, + Status = 0 + }; + + var serverAuthorization = new ServerAuthorization + { + IdStorageServer = storageServer.IdStorageServer, + CreateDate = DateTime.UtcNow, + Count = 1, + Status = 0 + }; + var token = new Token + { + IdToken = TokenID, + AccessToken = TokenValue, + CreateDate = DateTime.UtcNow, + ExpireDate = DateTime.UtcNow.AddMonths(3), + Status = 0 + }; + var rT = new RefreshToken + { + IdRefreshToken = RTokenID, + IdServerAuthorization = serverAuthorization.IdServerAuthorization, + __RefreshToken = RTokenValue, + CreateDate = DateTime.UtcNow, + ExpireDate = DateTime.UtcNow.AddMonths(9), + Status = 0 + }; + BatchInsert bi = new BatchInsert(con, trans); + bi.AddRow(localServer); + bi.AddRow(storageServer); + bi.AddRow(ss_lc); + bi.AddRow(serverAuthorization); + bi.AddRow(token); + bi.AddRow(rT); + await bi.ExcuteQuery(); + await trans.CommitAsync(); + f.Status = 1; + f.Message = "OK"; + } + catch (DbException ex) + { + await trans.RollbackAsync(); + await trans.DisposeAsync(); + f.Status = 0; + f.Message = ex.Message; + f.ID = 61031; + } + return f; + } + + public override Task DeleteAsync() + { + throw new NotImplementedException(); + } + + public override Task UpdateAsync() + { + throw new NotImplementedException(); + } + } +} diff --git a/TWASys-App/Program.cs b/TWASys-App/Program.cs index 857a8de..b6cb972 100644 --- a/TWASys-App/Program.cs +++ b/TWASys-App/Program.cs @@ -1,8 +1,29 @@ - -using AppLibs.Libs; +using AppLibs.Libs; +using Microsoft.AspNetCore.Http.Features; +using System.Net; +using System.Text; await WSNavigation.LoadJson(); var builder = WebApplication.CreateBuilder(args); +builder.Services.Configure(o => { + o.MultipartBodyLengthLimit = 200_000_000; // 200 MB +}); +builder.Services.AddControllers(options => +{ + options.Filters.Add(); +}).AddJsonOptions(options => +{ + options.JsonSerializerOptions.PropertyNamingPolicy = null; + options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; +}); +builder.Services.AddDistributedMemoryCache(); // IDistributedCache in-memory +builder.Services.AddSession(o => +{ + o.IdleTimeout = TimeSpan.FromHours(8); + o.Cookie.HttpOnly = true; + o.Cookie.IsEssential = true; + o.Cookie.SecurePolicy = CookieSecurePolicy.Always; +}); builder.Services.AddControllersWithViews().AddRazorRuntimeCompilation(); // Add services to the container. @@ -23,11 +44,10 @@ app.UseRouting(); app.UseAuthorization(); app.MapStaticAssets(); - +app.UseSession(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}") .WithStaticAssets(); - app.Run(); diff --git a/TWASys-App/TWASys-App.csproj b/TWASys-App/TWASys-App.csproj index 179f8e5..2365058 100644 --- a/TWASys-App/TWASys-App.csproj +++ b/TWASys-App/TWASys-App.csproj @@ -1,50 +1,56 @@ - + - - net9.0-windows10.0.19041.0 - enable - enable - TWASys_App - - - + + net9.0-windows;net9.0 + enable + enable + TWASys_App + + + + - - - - - - - - - - - - - - - - + + $(DefineConstants);WINDOWS + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - Always - - - Always - - - Always - - + + + + + + + Always + + + Always + + + Always + + diff --git a/TWASys-App/Views/Home/Index.cshtml b/TWASys-App/Views/Home/Index.cshtml index bcfd79a..08216db 100644 --- a/TWASys-App/Views/Home/Index.cshtml +++ b/TWASys-App/Views/Home/Index.cshtml @@ -6,3 +6,7 @@

Welcome

Learn about building Web apps with ASP.NET Core.

+ +@section jsLib { + +} \ No newline at end of file diff --git a/TWASys-App/Views/Login/Index.cshtml b/TWASys-App/Views/Login/Index.cshtml index c061a67..7f9a50e 100644 --- a/TWASys-App/Views/Login/Index.cshtml +++ b/TWASys-App/Views/Login/Index.cshtml @@ -1,43 +1,122 @@ -
-
- -
-
\ No newline at end of file + +@section jsLib { + +} diff --git a/TWASys-App/Views/Partial/Header.cshtml b/TWASys-App/Views/Partial/Header.cshtml index fed8075..f7847b4 100644 --- a/TWASys-App/Views/Partial/Header.cshtml +++ b/TWASys-App/Views/Partial/Header.cshtml @@ -1,7 +1,7 @@ 
- +
- + +
+
+
+ +
+
@@ -103,9 +109,9 @@ const tmp = `
- +
- +
@@ -140,7 +146,7 @@ const tmp = `
- +
@@ -158,7 +164,7 @@ const tmp = `
- +
@@ -203,30 +209,51 @@ const tmp = `
Server Authenicator
- +
- +
- +
- +
- +
- +
- +
- +
+
+
+
Storage Data Summary
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
@@ -262,24 +289,99 @@ window.L6101 = function () { var ov = new AOverlay(document.body); ov.createOverlay(); - + const $v = id => (document.getElementById(id)?.value ?? '').trim(); var ele1 = new ASpinButton(document.getElementById("totalSize")); var ele2 = new AMultiTag(document.getElementById("listStorage")); var slider = new AModal(tmp); slider.createModal("CustomForm", "slider-right"); var Scrollbar = window.Scrollbar; - Scrollbar.init(slider.CustomContainer.querySelector(".slider-scrollbar"), window.scroll_options); - + const mScroll = Scrollbar.init(slider.CustomContainer.querySelector(".slider-scrollbar"), window.scroll_options); slider.overlay.isCloseOverlay(true); var tabs = new ATab(slider.CustomContainer.querySelector(".atabs"), slider.CustomContainer.querySelector(".StorageTabs")); var wrd1 = new AWizard(slider.CustomContainer.querySelector(".AWizard")); var as = new ASelect(slider.CustomContainer.querySelectorAll(".aselect")); + window.testV = as; var asIdType = as.get(0); + wrd1.on("onBeforeBack", (evt) => { + mScroll.scrollTo(0, 0); + }); + wrd1.on("onAfterNext", (evt) => { + if (evt.indexPage > 0) { + if (as.get(0).isLockElement() || as.get(1).isLockElement()) { + return; + } + switch (asIdType.getSelectedItem().vSearch) { + case "Microsoft Cloud": + break; + case "Physical Server": + if (evt.indexPage > 3) { + (async () => { + + const fd = new FormData(); + fd.append('StorageTypeID', asIdType.getSelectedItem()?.id ?? ''); + fd.append('ControllerName', $v('controllerName')); + fd.append('StorageName', $v('storageName')); + + fd.append('IpPublic', $v('ipPublic')); + fd.append('IpPrivatev4', $v('ipPrivatev4')); + fd.append('IpPrivatev6', $v('ipPrivatev6')); + + fd.append('OsName', $v('osName')); + fd.append('OsPlatform', $v('osPlatform')); + fd.append('OsVersion', $v('osVersion')); + fd.append('OsKernel', $v('osKernel')); + fd.append('OsArch', $v('osArch')); + + fd.append('BiosSN', $v('biosSerial')); + fd.append('BiosVendor', $v('biosVender')); + fd.append('BiosUUID', $v('biosUUID')); + + const i = 0; + as.get(1).multiSelectedItem.forEach((item) => { + fd.append(`ValidationDomains[${i}].IdValidationDomain`, item.getAttribute("data-value")); + }); + + fd.append('SocketN', $v('socketN')); + fd.append('CpuName', $v('cpuName')); + fd.append('TotalRam', $v('totalRam')); + + fd.append('TokenID', $v('idToken')); + fd.append('TokenValue', $v('vToken')); + fd.append('RTokenID', $v('idRefreshToken')); + fd.append('RTokenValue', $v('refreshTokenValue')) + const response = await fetch("/Storage/SetStorageData", { + method: 'POST', + body: fd + }); + })(); + + } + break; + } + } + }); wrd1.on("onBeforeNext", (evt) => { + mScroll.scrollTo(0, 0); if (evt.indexPage == 0) { - if (asIdType.isLockElement() && asVD.isLockElement()) { + if (as.get(0).isLockElement() || as.get(1).isLockElement()) { + wrd1.isStopNextPage = true; + return; + } else { + wrd1.isStopNextPage = false; + } + let flag = false; + if ($v('controllerName').length == 0) { + flag = true; + } + if ($v('storageName').length == 0) { + flag = true; + } + if (as.get(1).multiSelectedItem.length == 0) { + flag = true; + } + if (flag) { wrd1.isStopNextPage = true; return; } else { @@ -289,9 +391,11 @@ window.L6101 = function () { case "Microsoft Cloud": wrd1.showPage(1); wrd1.hidePage(2); + wrd1.hidePage(3); break; case "Physical Server": wrd1.showPage(2); + wrd1.showPage(3); wrd1.hidePage(1); let promise = new Promise(async function (resolve, reject) { const response = await fetch(`/Storage/GetSysInfo`, { @@ -305,10 +409,43 @@ window.L6101 = function () { const autoF = new AAutoFill(document.querySelector(".awStorageConnector")); autoF.fill(data); } - }); + }); break; } - }e + } else if (evt.indexPage == 1) + { + + } + else if (evt.indexPage == 2) { + + (async () => { + const response = await fetch(`/Storage/GenerationAccessToken`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }); + const data = await response.json(); + if (data) { + document.getElementById("idToken").value = data.idToken; + document.getElementById("vToken").value = data.tokenValue; + } + })(); + (async () => { + const response = await fetch(`/Storage/GenerationAccessToken`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }); + const data = await response.json(); + if (data) { + document.getElementById("idRefreshToken").value = data.idToken; + document.getElementById("refreshTokenValue").value = data.tokenValue; + } + })(); + } + }, true); var asVD = as.get(1); @@ -321,6 +458,7 @@ window.L6101 = function () { let promise = new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); xhr.open("GET", window.GetAbsoluteURL("/Storage/GetTypeStorage")); + xhr.setRequestHeader('Accept', 'application/json; charset=UTF-8'); var f = function (ev) { if (ev.currentTarget.readyState == 4) { if (ev.currentTarget.status == 200) { @@ -344,6 +482,7 @@ window.L6101 = function () { let promise1 = new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); xhr.open("GET", window.GetAbsoluteURL("/Storage/GetValidationDomain")); + xhr.setRequestHeader('Accept', 'application/json; charset=UTF-8'); var f = function (ev) { if (ev.currentTarget.readyState == 4) { if (ev.currentTarget.status == 200) { @@ -374,7 +513,7 @@ window.L6101 = function () { asAddStorage.on("click_btAddStorage", (e) => { const xhr = new XMLHttpRequest(); xhr.open("POST", "/Storage/AddStorageServer"); - xhr.setRequestHeader('Content-type', 'application/json; charset=UTF-8'); + xhr.setRequestHeader('Accept', 'application/json; charset=UTF-8'); var data = { "Name": document.getElementById("inpType").value }; diff --git a/TWASys-App/wwwroot/js/js-page/asyncLayout.js b/TWASys-App/wwwroot/js/js-page/asyncLayout.js index bcd57d4..fabbb93 100644 --- a/TWASys-App/wwwroot/js/js-page/asyncLayout.js +++ b/TWASys-App/wwwroot/js/js-page/asyncLayout.js @@ -14,6 +14,7 @@ class AsyncLayout extends ALayout { super.dispose(); } renderMenu() { + console.log("ÁDASD"); this.isLoaded = true; var asyncStyleSheets = [ 'https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600;700&display=swap', diff --git a/TWASys-App/wwwroot/js/js-page/asyncLoginLayout.js b/TWASys-App/wwwroot/js/js-page/asyncLoginLayout.js new file mode 100644 index 0000000..ca60052 --- /dev/null +++ b/TWASys-App/wwwroot/js/js-page/asyncLoginLayout.js @@ -0,0 +1,39 @@ +import ALayout from '/js/libs/js-ALayout.js' + +class LoginLayout extends ALayout { + constructor() { + super(); + this.isLoaded = false; + } + dispose() { + this.isLoaded = false; + window.app.removeSytemEventParent(window.app.lName); + window.app.removeCustomEventParent(window.app.lName); + super.dispose(); + } + renderMenu() { + this.isLoaded = true; + var asyncStyleSheets = [ + 'https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600;700&display=swap', + '/css/atg-font/atg-admin-font.css', + '/css/atg-lib/waves.min.css', + '/css/pages/login.css' + ]; + window.app.loadCSS(asyncStyleSheets); + window.app.initNavs("Login"); + + var sOption = { + damping: (window.getOS() == "Android") ? .06 : .04, + thumbMinSize: 25, + renderByPixel: true, + alwaysShowTracks: true, + continuousScrolling: true + }; + + window.Scrollbar.use(window.OverscrollPlugin); + window.app.initScrollBar(); + } +} + +window.ALayout.set("Login", new LoginLayout()); +window.AScript.set("asyncLoginLayout", true); \ No newline at end of file diff --git a/TWASys-App/wwwroot/js/libs/js-AWizard.js b/TWASys-App/wwwroot/js/libs/js-AWizard.js index 61b1da1..eb9ac1a 100644 --- a/TWASys-App/wwwroot/js/libs/js-AWizard.js +++ b/TWASys-App/wwwroot/js/libs/js-AWizard.js @@ -106,12 +106,12 @@ export default class AWizard extends ATab { btnNext_Click(e, idx) { this.trigger("onBeforeNext", { "currentPage": this.ctabs[idx], "indexPage": idx, "currentButton": e.currentTarget }); if (this.isStopNextPage) return; - console.log(idx); this.selectedTab(this.checkSelectedNext(idx)); - this.trigger("onAfterNext"); + this.trigger("onAfterNext", { "currentPage": this.ctabs[idx], "indexPage": idx + 1, "currentButton": e.currentTarget }); } btnBack_Click(e, idx) { + this.trigger("onBeforeBack", { "currentPage": this.ctabs[idx], "indexPage": idx, "currentButton": e.currentTarget }); this.selectedTab(this.checkSelectedBack(idx)); } btnFinish_Click(e) { diff --git a/TWASys-App/wwwroot/js/libs/js-core.js b/TWASys-App/wwwroot/js/libs/js-core.js index c02fcda..51284d7 100644 --- a/TWASys-App/wwwroot/js/libs/js-core.js +++ b/TWASys-App/wwwroot/js/libs/js-core.js @@ -541,6 +541,7 @@ class AApp extends window.AObject { this.metaPage = document.head.querySelector("meta[name=idPage]"); this.pageName = this.metaPage.getAttribute("pageName"); this.lName = this.metaPage.getAttribute("layName"); + console.log(this.lName); var tmp = document.querySelector(container); this.mainApp = tmp.querySelector("[main-content]") ? tmp.querySelector("[main-content]") : tmp; var f = function (ev) { @@ -688,6 +689,7 @@ class AApp extends window.AObject { doc: doc, dynamicF: a.dynamicF }; + this.setContentPage(obj, url); } else { this.getPage(url, result) @@ -721,7 +723,9 @@ class AApp extends window.AObject { document.title = page.title + " - " + this.pageName; var meta = document.head.querySelector("meta[name=idPage]"); document.body.setAttribute("page", page.idPage); + meta.content = page.idPage; + this.lName = page.lName; meta.setAttribute("layName", page.lName); this.loadContentPage(page.html); window.history.pushState({"url":url}, page.title, url); @@ -821,6 +825,7 @@ class AApp extends window.AObject { }).bind(this)(); } loadedLayout() { + console.log(this.lName); if (this.lName !== "None") { const l = window.ALayout.get(this.lName); if (!l.isLoaded) { @@ -831,7 +836,6 @@ class AApp extends window.AObject { } } setLayout(o) { - var oP = new DOMParser(); var pHtml = oP.parseFromString(o.Content, 'text/html'); (function () {